前言
之前研究了下SpringBoot下多数据源的配置处理,感觉蛮有意思的,今天特地总结分享下。
我们知道,当一些项目较大时,有可能出现分库等技术操作,这时候就有可能需要使用多数据源了,为保证程序SQL能在多个数据源之间来回切换并正常执行,就需要对数据源代码上进行一些处理,这里我已SpringBoot下的配置为例。
正文
如何配置是简单的,但是要了解如何配置,我们应该下些功夫。
在spring-jdbc这个jar包里,我们可以找到这个抽象类AbstractRoutingDataSource.class,这个类是处理多数据源配置的关键类,我们来看一下。
这个类的主要作用是是的我们可以切换数据源key以切换数据源,key值的切换一般通过线程Context实现。
targetDataSources: 是一个Map集合,用于存放多个数据源。
resolvedDataSources:dataSource的Map集合,用于确定多个数据源。
defaultTargetDataSource:默认使用的数据源。
lenientFallback:可以用来回滚到原来的数据源的设置。
dataSourceLookup:数据源循环查看实现,有多个实现。
resolvedDefaultDataSource:确定的要使用哪一个数据源。
我们来看一些相关代码。
先看属性设置完成后执行的这个方法。
可以看到,resolvedDataSources从targetDataSources拿到值并转换为DataSource集合,resolvedDefaultDataSource会对defaultTargetDataSource进行转化处理。
再看这个决定使用哪个数据源的方法。
可以看到会先拿到当前的LookupKey,即当前要使用的数据源的key,拿到key后在尝试拿到当前key对应的数据源,如果没有数据源并且设置了回滚属性,会继续使用默认的数据源而不切换,如果没有设置回滚又拿不到数据源,dataSource==null,那么就会抛出异常,如果拿到数据源了,dataSource!=null,那么就会进行数据源的切换,返回一个切换后的数据源。
这儿lenientFallback(仁慈回滚)的作用,如果设置为false,拿不到数据源会出错,便于我们分析问题。
再看下 determineCurrentLookupKey 这个方法,就是获取要使用的数据源的key,是个抽象方法,需要我们进行实现。(具体什么时候切换数据源,就改变key值,我们一般使用线程绑定的上下文来对key值进行控制)
我们再来看下把把我们的对象转为数据源对象的这个方法。
可以看到如果传入的是String,会使用DataSourceLookup去获取数据源,默认使用JndiDataSourceLookup。
DataSourceLookup有4种实现,可以看下。
BeanFactoryDataSourceLookup是从SpringBean里获取DataSource。
JndiDataSourceLookup是通过JndiTemplate获取DataSource。
MapDataSourceLookup可以自定义DataSourceMap,然后根据Key值获取。下图中的dataSources就是我们可以提前主动set进去的数据。
SingleDataSourceLookup指的单一数据源,一般很少使用。这个dataSource我们也是可以主动设置的。
可以看到,我们有多种方式去放置数据源,targetDataSources这个Map集合可以直接放置数据源集合,也可以放置数据源bean名字(但需要指定DataSourceLookup为BeanFactoryDataSourceLookup去解析),也可以放置自定义数据源集合的key(但需要指定DataSourceLookup为MapDataSourceLookup去解析)等等。
因此我们要实现多数据源,需要对AbstractRoutingDataSource进行实现。
多数据源配置
新建DynamicDataSource类,内容如下:
1 | public class DynamicDataSource extends AbstractRoutingDataSource { |
看一下lookupKey的实现,我们新创建一个DatabaseContextHolder类,用一个ThreadLocal来对当前正在使用的数据源key进行管理。如下:
1 | public class DatabaseContextHolder { |
databaseTypes 这个set我们后面可以用来判断是否有此数据源。
DatabaseType枚举如下(用静态String等也可以代替):
1 | public enum DatabaseType { |
可以看到有两个数据源key值,MASTER和SLAVE。
然后我们要使多数据源生效,需要配置多数据源,如下:
1 |
|
配置文件内容如下:
1 | spring.datasource.multiple.master-datasource.driverClassName=com.mysql.cj.jdbc.Driver |
我们说下这个Config类。
masterDataSource和slaveDataSource很好理解,就是获取配置文件属性生成相应的Bean,这儿要注意@Primary注解,这个注解指定默认使用哪个Bean,因为DataSource有两个实现,master和slave,如果不指定,Spring会不知道选用哪一个。
要使用多数据源,就需要对两个数据源进行管理,我们在Config这个类里创建一个返回DynamicDataSource的方法,如下:
1 | /** |
可以看到我们把MASTER数据源和SLAVE数据源放到了targetDataSources 里面,同时在
DatabaseContextHolder的databaseTypes里面保存一份key值,用于一些处理。整体上这个类还是比较好理解的。
如何让程序执行SQL使用使用动态数据源呢?这时候就要处理SqlSessionFactory了,我们创建获取SqlSessionFactory的Bean的方法,传入动态数据源获取SqlSessionFactory,如下:
1 | /** |
这个类是是数据源切换的关键类。
为了管理支持多数据源事务,我们的事务Bean也是要传入动态数据源的,如下:
1 | /** |
这样随着数据源的切换,事务也是切换后的数据源的。
如何切换数据源?其实只要控制DatabaseContextHolder里面ThreadLocal的值就可以控制数据源切换,但是我们想切换数据源时,总不能每个方法都在方法开始前设置,在结束前清除吧,那样太麻烦了。
我们可以借助Aspect切面来处理这个问题。
我们定义一个TargetDataSource注解,只要标注这个注解,并指定数据源key便可以进行数据源切换,这个注解一定是作用在方法上的,如下:
1 | ({ElementType.TYPE, ElementType.METHOD}) |
切面的话,只要检测到这个方法上有这个注解,那就拿到注解里的数据源key进行切换,如下:
1 |
|
@Order注解设置为-1保证此过程在事务之前执行,然后在执行事务。
@Before注解表示在在动作之前执行,我们设置为在有此注解的方法之前执行changeDataSource方法,这个方法对数据源进行切换,@After注解表示方法执行完之后要处理的事情,这儿我们用clearDataSource方法来清除使用过的数据源信息。
讲到这儿,SpringBoot的多数据源配置也基本完成了,我们进行必要的测试。
测试
创建一个Service类,代码如下:
1 |
|
注:这儿略掉了Score表和Mapper文件的创建。
上面的TestServiceImpl,我们应该期望的结果是 doSomething1执行成功入库test1,doSomething2执行失败不能入库test2(因为抛出异常事务要回滚),我们看下执行结果:
新建Test,执行我们的方法。
1 | (SpringRunner.class) |
可以看到如下输出:
查看数据库。
符合我们期望值。我们把数据库数据清除后,把TestServiceImpl里的flag设置为false。
执行刚才的测试方法,结果如下:
可以看到也符合我们的期望结果,成功完成了数据源切换及事务支持。
总结
通过对SpringBoot多数据源的配置和理解,了解了多数据源的配置和使用,及多数据源实现的一些简单原理,也是蛮不错的一次学习过程。
本节源代码详见: framework-datasource