本例是在【Mybatis】MyBatis之配置多数据源(十)的基础上进行拓展,查看本例请先学习第十章
实现原理
1、扩展Spring的AbstractRoutingDataSource抽象类(该类充当了DataSource的路由中介, 能有在运行时, 根据某种key值来动态切换到真正的DataSource上。)
从AbstractRoutingDataSource的源码中,有一个数据源查找属性(dataSourceLookup),和一个 查询数据源方法 (resolveSpecifiedDataSource):
1 private DataSourceLookup dataSourceLookup = new JndiDataSourceLookup(); 2 3 4 protected DataSource resolveSpecifiedDataSource(Object dataSource) throws IllegalArgumentException { 5 if (dataSource instanceof DataSource) { 6 return (DataSource) dataSource; 7 } 8 else if (dataSource instanceof String) { 9 return this.dataSourceLookup.getDataSource((String) dataSource); 10 } 11 else { 12 throw new IllegalArgumentException( 13 "Illegal data source value - only [javax.sql.DataSource] and String supported: " + dataSource); 14 } 15 }
2、类中数据源查找类使用的是JndiDataSourceLookup,此类查找的是JNDI数据源,参考:【Tomcat】Tomcat 配置JNDI数据源(三)
3、我们可以自定义动态数据源查找类来,来控制自定义数据源
实现案例
1、搭建项目,参考:【Mybatis】MyBatis之配置多数据源(十)
2、自定义数据源查找类
1 package com.test.datasource; 2 3 import java.beans.PropertyVetoException; 4 import java.sql.SQLException; 5 import java.util.Map; 6 import java.util.concurrent.ConcurrentHashMap; 7 8 import javax.sql.DataSource; 9 10 import org.springframework.jdbc.datasource.lookup.DataSourceLookup; 11 import org.springframework.jdbc.datasource.lookup.DataSourceLookupFailureException; 12 import org.springframework.util.Assert; 13 14 import com.mchange.v2.c3p0.ComboPooledDataSource; 15 16 public class DynamicDataSourceLookup implements DataSourceLookup { 17 18 /** 19 * 数据源集合 20 */ 21 private final Map<String, DataSource> dataSources = new ConcurrentHashMap<>(); 22 23 /** 24 * 根据数据源名称,获取数据源 25 */ 26 @Override 27 public DataSource getDataSource(String dataSourceName) throws DataSourceLookupFailureException { 28 29 Assert.notNull(dataSourceName, "DataSourceName must not be null"); 30 DataSource dataSource = this.dataSources.get(dataSourceName); 31 if (dataSource == null) { 32 // datasource not in map.. create one and add to map 33 dataSource = createDataSource(dataSourceName); 34 if(dataSource != null) { 35 addDataSource(dataSourceName, dataSource); 36 } 37 } 38 // 记录数据源状态 39 if(dataSource instanceof ComboPooledDataSource) { 40 ComboPooledDataSource ds = (ComboPooledDataSource) dataSource; 41 try { 42 System.out.println("ds.getMaxPoolSize():" + ds.getMaxPoolSize());// 最大连接数 43 System.out.println("ds.getMaxPoolSize():" + ds.getMaxPoolSize());// 最大连接数 44 System.out.println("ds.getMinPoolSize():" + ds.getMinPoolSize());// 最小连接数 45 System.out.println("ds.getNumBusyConnections():" + ds.getNumBusyConnections());// 正在使用连接数 46 System.out.println("ds.getNumIdleConnections():" + ds.getNumIdleConnections());// 空闲连接数 47 System.out.println("ds.getNumConnections():" + ds.getNumConnections()); 48 } catch (SQLException e) { 49 // TODO Auto-generated catch block 50 e.printStackTrace(); 51 }// 总连接数 52 } 53 return dataSource; 54 } 55 56 /** 57 * 创建数据源 58 * @param dataSourceName 59 * @return 60 */ 61 private DataSource createDataSource(String dataSourceName) { 62 63 ComboPooledDataSource dataSource = null; 64 65 // 可根据其他业务数据,创建数据源 66 try { 67 dataSource = new ComboPooledDataSource(); 68 dataSource.setUser("admin"); 69 dataSource.setPassword("123456"); 70 dataSource.setJdbcUrl("jdbc:mysql://127.0.0.1:3306/test_mybatis?allowPublicKeyRetrieval=true"); 71 dataSource.setDriverClass("com.mysql.jdbc.Driver"); 72 dataSource.setDataSourceName(dataSourceName); 73 74 75 } catch (PropertyVetoException e) { 76 throw new RuntimeException("Error creating data source"); 77 } 78 return dataSource; 79 } 80 81 /** 82 * 添加数据源到 数据源集合中 83 * @param dataSourceName 84 * @param dataSource 85 */ 86 public void addDataSource(String dataSourceName, DataSource dataSource) { 87 Assert.notNull(dataSourceName, "DataSourceName must not be null"); 88 Assert.notNull(dataSource, "DataSource must not be null"); 89 this.dataSources.put(dataSourceName, dataSource); 90 } 91 92 /** 93 * 从数据源集合移除数据源 94 * @param dataSourceName 95 */ 96 public void removeDataSource(String dataSourceName) { 97 Assert.notNull(dataSourceName, "DataSourceName must not be null"); 98 this.dataSources.remove(dataSourceName); 99 } 100 101 }
3、编辑动态数据源类,从写determineTargetDataSource方法,查询数据源时,先从自定义数据源中查找,然后从内部数据源中查找
1 package com.test.datasource; 2 3 import javax.sql.DataSource; 4 5 import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource; 6 7 /** 8 * 动态数据源(依赖于spring) 9 * @author chenheng 10 * @date 2019-08-03 17:27:35 11 * 12 */ 13 public class DynamicDataSource extends AbstractRoutingDataSource { 14 15 @Override 16 protected Object determineCurrentLookupKey() { 17 return DataSourceHolder.getDataSourceKey(); 18 } 19 20 @Override 21 protected DataSource determineTargetDataSource() { 22 23 Object lookupKey = determineCurrentLookupKey();
24 DataSource dataSource = null; 25 // 自定义外部数据源类中,查询数据源 26 if(lookupKey != null) { 27 dataSource = resolveSpecifiedDataSource(key); 28 } 29 // 在内部数据源中,查询数据源 30 if(dataSource == null){ 31 dataSource= super.determineTargetDataSource(); 32 } 33 return dataSource; 34 } 35 }
4、注入dynamicDataSourceLookup,并且在dataSource注入属性dataSourceLookup
1 <bean id="dynamicDataSourceLookup" class="com.test.datasource.DynamicDataSourceLookup"> 2 </bean> 3 4 5 <!-- 数据源:Spring用来控制业务逻辑。数据源、事务控制、aop --> 6 <bean id="dataSource" class="com.test.datasource.DynamicDataSource"> 7 <property name="targetDataSources"> 8 <map key-type="java.lang.String"> 9 <entry key="dataSource1" value-ref="dataSource1"></entry> 10 <entry key="dataSource2" value-ref="dataSource2"></entry> 11 </map> 12 </property> 13 <!-- 默认目标数据源为你主库数据源 --> 14 <property name="defaultTargetDataSource" ref="dataSource1"/> 15 <property name="dataSourceLookup" ref="dynamicDataSourceLookup"/> 16 </bean>
5、编辑是一个动态数据源DAO,来使用自定动态数据源
1 package com.test.dao; 2 3 4 import javax.sql.DataSource; 5 6 import org.springframework.beans.factory.annotation.Autowired; 7 import org.springframework.jdbc.core.support.JdbcDaoSupport; 8 import org.springframework.stereotype.Repository; 9 10 11 @Repository 12 public class DynamicDao extends JdbcDaoSupport { 13 14 @Autowired 15 public DynamicDao(DataSource dataSource) { 16 this.setDataSource(dataSource); 17 } 18 19 }
6、在Service中使用
1 package com.test.service; 2 3 import java.util.List; 4 5 import org.springframework.beans.factory.annotation.Autowired; 6 import org.springframework.jdbc.core.BeanPropertyRowMapper; 7 import org.springframework.stereotype.Service; 8 9 import com.test.dao.DynamicDao; 10 import com.test.datasource.DataSourceHolder; 11 import com.test.pojo.Employee; 12 13 @Service 14 public class DynamicService { 15 16 @Autowired 17 DynamicDao dynamicDao; 18 19 public List<Employee> getEmployee() { 20 DataSourceHolder.setDataSourceKey("自定义"); 21 return dynamicDao.getJdbcTemplate().query("select id, last_name lastName, gender, email from employee", 22 new BeanPropertyRowMapper<>(Employee.class)); 23 } 24 25 }
数据流转顺序:
1.在 DataSourceHolder中,设置数据源名称
2.Spring 调用determineCurrentLookupKey()方法<DynamicDataSource中重写AbstractRoutingDataSource类中的方法> ,从DataSourceHolder取出当前的数据库名称,并返回
3.AbstractRoutingDataSource类中determineTargetDataSource()方法调用determineCurrentLookupKey(),先匹配外部数据库,然后匹配内部数据库;
4.匹配到指定的数据库,并建立链接,即为切换到相应的数据库;
5.在指定的数据库中执行相应的sql