有些web应用需要连接多个数据源,本文讲解一下如何使用多个数据源,大题思路是这样的,系统启动的时候创建多个数据源,然后具体执行sql的时候去切换数据源执行对应的sql。如何切换数据源呢?spring提供了一个AbstractRoutingDataSource抽象类,只要继承这个类就可以了,这个类需要设置多个数据源,每个数据源有一个key对应,继承这个类必须实现determineCurrentLookupKey()方法,这个方法返回一个Object值,这个值应该是数据源的key,执行sql的时候会调用这个方法 获取key,然后根据这个key获取到的数据源执行sql。下面看具体的例子。 前面说了determineCurrentLookupKey()方法的返回值决定,选择什么数据库,方法执行的时候如何动态设置返回值呢?为了不影响其他线程的使用,使用线程本地变量是最好的,这样只会影响当前线程。看如下类
public class DataSourceRouter { //默认数据源的key public final static String DEFAULT = "default"; //数据源1的key public final static String KEY_ONE = "key_one"; private final static ThreadLocal<String> threadLocal = ThreadLocal.withInitial(() -> DEFAULT); /** * 获取线程本地变量 * * @return */ static String getCurrentDataSourceKey() { return threadLocal.get(); } /** * @param key 数据源对应的key * @param supplier sql执行的方法 * @param <T> * @return sql执行的结果 */ public static <T> T doWithKey(String key, Supplier<T> supplier) { threadLocal.set(key); T t = supplier.get(); threadLocal.set(DEFAULT); return t; } }这里通过doWithKey执行sql,通过determineCurrentLookupKey动态获取当前数据源。 下面来实现AbstractRoutingDataSource
public class DynamicDataSource extends AbstractRoutingDataSource { @Autowired ApplicationContext applicationContext; /** * 是否在创建数据源时,立即初始化连接 */ private boolean initConnectionsOnCreate = false; /** * 返回配置的数据源的key,这样下面执行的sql就是使用该数据源的 * * @return */ @Override protected Object determineCurrentLookupKey() { return DataSourceRouter.getCurrentDataSourceKey(); } public void init() { DruidDataSource defaultTargetDataSource = applicationContext.getBean("defaultDataSource", DruidDataSource.class); DruidDataSource dataSource1 = applicationContext.getBean("dataSourceTemplate", DruidDataSource.class); dataSource1.setUrl("jdbc:mysql://localhost:3306/social?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC"); dataSource1.setUsername("root"); dataSource1.setPassword("998973"); if (initConnectionsOnCreate) { try { defaultTargetDataSource.init(); dataSource1.init(); } catch (SQLException e) { throw new RuntimeException(e); } } Map<Object, Object> map = new HashMap<>(); map.put(DataSourceRouter.DEFAULT, defaultTargetDataSource); map.put(DataSourceRouter.KEY_ONE, dataSource1); setTargetDataSources(map); //默认先执行配置文件中的targetDataSources属性的数据源,这里设置的数据源不会生效,必须调用afterPropertiesSet afterPropertiesSet(); } public void setInitConnectionsOnCreate(boolean initConnectionsOnCreate) { this.initConnectionsOnCreate = initConnectionsOnCreate; } }这里说明了,配置文件中配置的targetDataSources是必配的,对象创建的时候会读取这个配置然后调用afterPropertiesSet加载数据源,因为我们这里的数据源可能通过数据库等其他途径获取,所以没有写在配置文件中,这里需要手动调用一下afterPropertiesSet重新设置一下数据源。 配置文件
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd"> <!-- 数据源模板,动态增加数据源时需要用到,scope是prototype,非单例对象 --> <bean id="dataSourceTemplate" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close"> <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/> <property name="filters" value="stat"/> <property name="maxActive" value="20"/> <property name="initialSize" value="1"/> <property name="maxWait" value="60000"/> <property name="minIdle" value="1"/> <property name="timeBetweenEvictionRunsMillis" value="60000"/> <property name="minEvictableIdleTimeMillis" value="300000"/> <property name="testWhileIdle" value="true"/> <property name="testOnBorrow" value="false"/> <property name="testOnReturn" value="false"/> <property name="poolPreparedStatements" value="true"/> <property name="maxOpenPreparedStatements" value="20"/> <property name="asyncInit" value="true"/> </bean> <bean id="defaultDataSource" parent="dataSourceTemplate"> <property name="url" value="jdbc:mysql://localhost:3306/easytour?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC"/> <property name="username" value="root"/> <property name="password" value="998973"/> </bean> <!--统一的dataSource--> <bean id="dynamicDataSource" class="com.zhan.design.config.datasource.DynamicDataSource" init-method="init"> <!-- 设置true时,随便一个key,找不到就走默认数据源,很可能带来不好的效果 --> <property name="lenientFallback" value="false"/> <property name="targetDataSources"> <map></map> </property> <!--设置默认的dataSource--> <property name="defaultTargetDataSource" ref="defaultDataSource"> </property> <!--是否在创建数据源时,立即初始化连接--> <property name="initConnectionsOnCreate" value="true"/> </bean> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dynamicDataSource"/> <!--扫描mybatis配置文件,在哪里可以做细配置--> <property name="configLocation" value="classpath:mybatis-config.xml"/> <!--扫描映射文件所在目录--> <property name="mapperLocations" value="classpath:com/zhan/design/mapper/**/*.xml"/> </bean> <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <!--扫描接口的基础包,会把该包下面的所有接口注册为spring的bean--> <property name="basePackage" value="com.zhan.design.dao"/> <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/> </bean> <!--配置spring的事务--> <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dynamicDataSource"/> </bean> <!--开启事务注解--> <tx:annotation-driven transaction-manager="txManager"/> </beans>这样就完成了 下面看下具体使用的例子
@GetMapping("/test") public Map<String, String> test() { //通过调用doWithKey就完成对数据源的切换了 String label1 = DataSourceRouter.doWithKey(DataSourceRouter.DEFAULT, () -> dictionaryService.getByTypeAndKey("1", "1")); String label2 = DataSourceRouter.doWithKey(DataSourceRouter.KEY_ONE, () -> dictionaryService.getByTypeAndKey("1", "1")); Map<String, String> map = new HashMap<>(); map.put("default_label", label1); map.put("one_label", label2); return map; }至此就完成了。