H__D  

  本例是在【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

 

posted on 2019-08-21 12:27  H__D  阅读(5198)  评论(0编辑  收藏  举报