当前位置: 首页>编程语言>正文

spring 动态方法调用 spring动态加载数据源

1、  背景

事务控制代码;2,一般系统都是使用mybatis框架做数据库操作,这样会导致系统代码风格不统一。所以,今天我要介绍的方法是基于Spring+Mybatis框架的多数据源处理。

2、  Spring数据源路由

   Spring2.0后增加一个AbstractRoutingDataSource类用来做数据源路由,实现数据源切换的功能就是自定义一个类扩展AbstractRoutingDataSource抽象类,通过重写抽象类中的方法determineCurrentLookupKey()来确定具体的数据源,具体实现代码如下:



1 public class DynamicDataSource extends AbstractRoutingDataSource {
2     @Resource(name = "dynamicDataSourceSelector")
3     private DataSourceSelector dynamicDataSourceSelector;
4 
5     @Override
6     protected Object determineCurrentLookupKey() {
7         return dynamicDataSourceSelector.getRouteKey();
8     }
9 }



     通过自定义的一个DataSourceSelector来设置需要路由的数据源Key,实现代码如下(选择过程可以按照需求自行变换):



1 public class DataSourceSelector {
 2    
 3    private static ThreadLocal<String> localRouteKey = new ThreadLocal<>();
 4    public void setRouteKey(String routeKey){
 5       localRouteKey.set(routeKey);
 6    }
 7    
 8    public String getRouteKey(){
 9       return localRouteKey.get();
10    }
11 
12 }



     在xml文件中配置多个数据源:



1 <!-- 配置数据源 -->
 2 <!-- 数据源1 -->
 3 <bean id="dynamicBaseDataSource1" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
 4     <property name="url" value="jdbc:mysql://localhost:3306?useUnicode=true&characterEncoding=UTF-8"/>
 5     <property name="username" value="root"/>
 6     <property name="password" value="root"/>
 7 </bean>
 8 <!-- 数据源2 -->
 9 <bean id="dynamicBaseDataSource2" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
10     <property name="url" value="jdbc:mysql://112.74.223.43:3306?useUnicode=true&characterEncoding=UTF-8"/>
11     <property name="username" value="root"/>
12     <property name="password" value="******"/>
13 </bean>
14 <!-- 数据源3 -->
15 <bean id="dynamicBaseDataSource3" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
16     <property name="url" value="jdbc:mysql://21.123.45.14:3306?useUnicode=true&characterEncoding=UTF-8"/>
17     <property name="username" value="root"/>
18     <property name="password" value="******"/>
19 </bean>



     还需要配置多个数据源对应的Key的映射关系:



1 <bean id="dynamicDataSource" class="com.guigui.datasource.DynamicDataSource">
 2     <property name="targetDataSources">
 3         <map>
 4             <!-- 多个数据源Key-value列表 -->
 5             <entry key="dynamicDS1" value-ref="dynamicBaseDataSource1"/>
 6             <entry key="dynamicDS2" value-ref="dynamicBaseDataSource2"/>
 7             <entry key="dynamicDS3" value-ref="dynamicBaseDataSource3"/>
 8         </map>
 9     </property>
10 </bean>



     SessionFactory以及事务等配置如下:



1 <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
 2     <property name="basePackage" value="com.guigui.dynamic.dao"/>
 3     <property name="sqlSessionFactoryBeanName" value="dynamicSqlSessionFactory"/>
 4 </bean>
 5 
 6 <bean id="dynamicDataSourceSelector" class="com.guigui.datasource.DataSourceSelector" />
 7 
 8 <!-- 事务管理相关配置... -->
 9 <bean id="dynamicTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
10     <property name="dataSource" ref="dynamicDataSource"/>
11 </bean>
12 
13 <aop:config>
14     <aop:pointcut id="dynamicTxOperation" expression="execution(* com.guigui.dynamic.service.*Service.*(..))" />
15     <aop:advisor id="dynamicAdvisor" pointcut-ref="dynamicTxOperation" advice-ref="dynamicAdvice"/>
16 </aop:config>
17 
18 <tx:advice id="dynamicAdvice" transaction-manager="dynamicTransactionManager">
19     <tx:attributes>
20         <tx:method name="*InTrx" propagation="REQUIRED" />
21         <tx:method name="*InNewTrx" propagation="REQUIRES_NEW" />
22         <tx:method name="*NoTrx" propagation="NOT_SUPPORTED" />
23         <tx:method name="*" propagation="SUPPORTS" />
24     </tx:attributes>
25 </tx:advice>



    配置好以后就可以使用多数据源切换的功能了,通过DataSourceSelector中的setRouteKey()方法进行数据源切换,切换之后对数据库的操作就是当前数据源的了。

But!!  这种方式也会存在一些让人不是很爽的地方,细心的同学们可能已经发现了,那就是我们的多个数据源都是配置在Spring的xml配置文件里面的,这就导致了我们每次新增加一个数据源都得修改一次xml文件,并且进行一次版本发布,想想就很不爽啊~~~ 而且,随着如果系统中连接的数据源越来越多,我们的配置文件也会越来越长,代码也会很难看!那么能不能把这些变化的数据源信息做成配置的呢?虽然不是很容易,但是方法还是有的,这就是今天的主题:动态注入。

3、  Spring动态注入Bean

       由于Spring传统的注入Bean的方式是通过加载xml配置文件来依次注入配置文件中定义的Bean,如果数据源的Bean通过其他方式配置,就需要在代码中进行动态注入。数据源的配置方式可以是任意方式,只要能够在代码中读取到即可,本文通过从数据库中读取数据源配置内容来实现多数据源路由。

       动态注入步骤:

  1. 从数据库中读取数据源配置列表,遍历数据源配置列表,并且对每条配置单独进行处理;
  2. 每条配置均需构造一个数据源的Bean并注入到Spring容器:
1 <!-- 配置数据源 -->
2 <!-- 其他多个数据源配置从配置表中读取,并在应用启动时进行加载(动态注入Spring容器) -->
3 <bean id="dynamicBaseDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
4     <property name="url" value="jdbc:mysql://localhost:3306?useUnicode=true&characterEncoding=UTF-8"/>
5     <property name="username" value="root"/>
6     <property name="password" value="root"/>
7 </bean>
  1. 需要将新构造的数据源Bean加到动态数据源的targetDataSources这个Map结构的属性中,并将动态数据源Bean重新注册:
1 <!-- 其他多个数据源配置从配置表中读取,并在应用启动时进行加载(动态注入Spring容器) -->
2 <entry key="defaultDS" value-ref="dynamicBaseDataSource"/>
  1. 由于事务管理相关配置依赖了原有的动态数据源,而动态数据源已经更新,所以相应的事务管理配置也要更新;同样的,事务相关的拦截器advisor、advice由于依赖事务管理器也都需要更新。 数据源动态注入代码:
1 public class DynamicInjectDataSource {
 2 
 3     @Autowired
 4     private DatasourceConfigMapper datasourceConfigMapper;
 5 
 6     private static final String URL_PREFIX = "jdbc:mysql://";
 7     private static final String URL_SURFIX = "?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true&zeroDateTimeBehavior=convertToNull";
 8     private static final String DESTORY_METHOD = "close";
 9     private static final String DYNAMIC_DATASOURCE = "dynamicDataSource";
10 
11     public void startUp() throws Exception {
12         this.dynamicInject();
13     }
14 
15     private void dynamicInject() throws Exception {
16         ConfigurableApplicationContext configurableApplicationContext = (ConfigurableApplicationContext) SpringContextHolder.getContext();
17         DefaultListableBeanFactory defaultListableBeanFactory = (DefaultListableBeanFactory) configurableApplicationContext.getBeanFactory();
18         ManagedMap<String, BeanDefinition> dataSourceMap = new ManagedMap<>();
19         List<DatasourceConfig> dataSourceConfigList = datasourceConfigMapper.selectAllDataSource();
20         if (CollectionUtils.isEmpty(dataSourceConfigList)) {
21             System.out.println("未查询到相关数据源!");
22             throw new Exception("初始化动态数据源失败!");
23         }
24         for (DatasourceConfig config : dataSourceConfigList) {
25             String beanId = config.getBeanId();
26             System.out.println("开始注册Mysql数据源:" + config.getDsKey());
27             // 如果存在则需要重新注册,防止有修改需要刷新
28             if (defaultListableBeanFactory.containsBean(beanId)) {
29                 defaultListableBeanFactory.removeBeanDefinition(beanId);
30             }
31             // 注册新的Bean
32             BeanDefinitionBuilder dataSourceBuilder = BeanDefinitionBuilder.genericBeanDefinition(BasicDataSource.class);
33             dataSourceBuilder.setDestroyMethodName(DESTORY_METHOD);
34             dataSourceBuilder.addPropertyValue("url", URL_PREFIX + config.getUrl() + URL_SURFIX);
35             dataSourceBuilder.addPropertyValue("username", config.getUserName());
36             dataSourceBuilder.addPropertyValue("password", config.getPassword());
37             dataSourceBuilder.addPropertyValue("maxActive", config.getMaxactive());
38             defaultListableBeanFactory.registerBeanDefinition(beanId, dataSourceBuilder.getRawBeanDefinition());
39             // 动态添加数据源
40             dataSourceMap.put(config.getDsKey(), dataSourceBuilder.getRawBeanDefinition());
41         }
42 
43         /* 重新注册动态数据源**/
44         Map<String, Object> dynamicDSPropertiesMap = new HashMap<>();
45         dynamicDSPropertiesMap.put("targetDataSources", dataSourceMap);
46         BeanDefinition dynamicDataSourceBean = this.reRegisterBeanDefinition(DYNAMIC_DATASOURCE, dynamicDSPropertiesMap);
47 
48         /* 重新注册事务管理器**/
49         Map<String, Object> dynamicDSManagerProsMap = new HashMap<>();
50         dynamicDSManagerProsMap.put("dataSource", dynamicDataSourceBean);
51         BeanDefinition dynamicManageBean = this.reRegisterBeanDefinition("dynamicTransactionManager", dynamicDSManagerProsMap);
52 
53         /* 重新注册Advice**/
54         Map<String, Object> dynamicAdviceProsMap = new HashMap<>();
55         dynamicAdviceProsMap.put("transactionManager", dynamicManageBean);
56         this.reRegisterBeanDefinition("dynamicAdvice", dynamicAdviceProsMap);
57 
58         /* 重新注册Advisor**/
59         Map<String, Object> dynamicAdvisorProsMap = new HashMap<>();
60         dynamicAdvisorProsMap.put("adviceBeanName", "dynamicAdvice");
61         this.reRegisterBeanDefinition("dynamicAdvisor", dynamicAdvisorProsMap);
62 
63     }
64 
65     /**
66      * 重新注册Bean通用方法
67      *
68      * @param beanName   bean名称
69      * @param properties 属性
70      */
71     private BeanDefinition reRegisterBeanDefinition(String beanName, Map<String, Object> properties) {
72         ConfigurableApplicationContext configurableApplicationContext = (ConfigurableApplicationContext) SpringContextHolder.getContext();
73         DefaultListableBeanFactory defaultListableBeanFactory = (DefaultListableBeanFactory) configurableApplicationContext.getBeanFactory();
74         BeanDefinition regBean = defaultListableBeanFactory.getBeanDefinition(beanName);
75         Set<String> propertyKeys = properties.keySet();
76         // 重新设置Bean的属性
77         for (String propertyKey : propertyKeys) {
78             regBean.getPropertyValues().removePropertyValue(propertyKey);
79             regBean.getPropertyValues().add(propertyKey, properties.get(propertyKey));
80         }
81         // 删除原有Bean
82         if (defaultListableBeanFactory.containsBean(beanName)) {
83             defaultListableBeanFactory.removeBeanDefinition(beanName);
84         }
85         // 重新注册Bean
86         defaultListableBeanFactory.registerBeanDefinition(beanName, regBean);
87         return regBean;
88     }
89 }

      其中存储数据源配置的表结构如下:

         

spring 动态方法调用 spring动态加载数据源,spring 动态方法调用 spring动态加载数据源_数据库,第1张

4、基于配置的动态数据源路由测试

       在数据库中我配置了两个数据源,一个是我本地创建的数据库,另外一个是我VPS上部署的数据库。

       

spring 动态方法调用 spring动态加载数据源,spring 动态方法调用 spring动态加载数据源_数据源_02,第2张

 

       在应用启动的时候会将这两个数据源加载到Spring容器,并且可以通过ds_key来路由具体的数据源。测试程序分别打印出两个数据源的数据库里面的一张表的字段列表。

以下是具体测试代码:

 



1 @Service("dynamicServiceImpl")
 2 public class DynamicServiceImpl implements IDynamicService {
 3     @Resource(name = "dynamicDataSourceSelector")
 4     private DataSourceSelector dynamicDataSourceSelector;
 5     @Autowired
 6     private DynamicMapper dynamicMapper;
 7     @Override
 8     public void dynamicRouting(String routingKey, String tableName, String schema) {
 9         // 路由数据源
10         System.out.println("路由到数据源:" + routingKey);
11         dynamicDataSourceSelector.setRouteKey(routingKey);
12         // 从当前数据源中进行查找
13         System.out.println("显示数据源 " + routingKey + "的表: " + schema + "." + tableName + " 字段列表:");
14         List<String> colnums = dynamicMapper.selectAllColumns(schema, tableName);
15         // 打印字段列表
16         StringBuilder sb = new StringBuilder();
17         sb.append("[");
18         for (int i = 0; i < colnums.size(); i++) {
19             sb.append(colnums.get(i)).append(",");
20             if (i == colnums.size() - 1) {
21                 sb.delete(sb.length() - 1, sb.length());
22                 sb.append("]");
23             }
24         }
25         System.out.println(sb.toString());
26         System.out.println();
27     }
28 
29 }



1 @Test
2 public void testDynamicSource() {
3     // 路由DSVps数据源
4     dynamicServiceImpl.dynamicRouting("DSVps", "article", "myblog");
5 
6     // 路由DSLocal数据源
7     dynamicServiceImpl.dynamicRouting("DSLocal", "khmessage", "weiyaqi");
8 }



       测试结果如下:

       

spring 动态方法调用 spring动态加载数据源,spring 动态方法调用 spring动态加载数据源_bc_03,第3张

   通过上面测试结果我们可以看到,在Spring的xml配置中不需要配置这些数据源,我们也做到了在这些数据源之间来回切换,而且数据源的个数我们也可以任意增加(只需要在数据库表中添加一条配置的记录即可),而我们的xml配置却依旧保持不变并且很简洁,配置一个默认的数据源,其他的都通过数据库配置读取并且动态注入:



1 <!-- 配置数据源 -->    
 2 <!-- 其他多个数据源配置从配置表中读取,并在应用启动时进行加载(动态注入Spring容器) -->
 3 <bean id="dynamicBaseDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
 4     <property name="url" value="jdbc:mysql://localhost:3306?useUnicode=true&characterEncoding=UTF-8"/>
 5     <property name="username" value="root"/>
 6     <property name="password" value="root"/>
 7 </bean>
 8 
 9 <!-- 配置数据源路由,targetDataSources.key作为数据源唯一标识 -->
10 <bean id="dynamicDataSource" class="com.guigui.datasource.DynamicDataSource">
11     <property name="targetDataSources">
12         <map>
13             <!-- 其他多个数据源配置从配置表中读取,并在应用启动时进行加载(动态注入Spring容器) -->
14             <entry key="defaultDS" value-ref="dynamicBaseDataSource"/>
15         </map>
16     </property>
17 </bean>



       新增了数据源后,由于配置和应用是分开的,也不需要重新发布应用了。如果想更进一步不重启应用就能达到刷新数据源的目的,可以通过其他方式如定时任务或者页面调用等方式触发DynamicInjectDataSource. startUp()方法来完成数据源刷新。

     以上便是本次要介绍的全部内容,如果有什么问题,欢迎各位读者指正,感激不尽!

动态数据源路由demo源码已上传至GitHub: https://github.com/guishenyouhuo/dynamicdatasource


https://www.xamrdz.com/lan/52v1959790.html

相关文章: