https://lanjingling.github.io/2016/02/15/spring-aop-dynamicdatasource/
一、多数据源动态切换原理
项目中我们经常会遇到多数据源的问题,尤其是数据同步或定时任务等项目更是如此;又例如:读写分离数据库配置的系统。
1、多数据源设置:
1)静态数据源切换:
一般情况下,我们可以配置多个数据源,然后为每个数据源写一套对应的sessionFactory和dao层代码(以hibernate为例,mybatis同理),——我们称之为静态数据源配置。
2)动态数据源切换:
可看出在Dao层代码中写死了两个SessionFactory,这样日后如果再多一个数据源,还要改代码添加一个SessionFactory,显然这并不符合开闭原则。比较好的做法是,配置多个数据源,只对应一套sessionFactory,数据源之间可以动态切换。
2、动态数据源切换时,如何保证数据库的事务:
目前事务最灵活的方式,是使用spring的声明式事务,本质是利用了spring的aop,在执行数据库操作前后,加上事务处理。
spring的事务管理,是基于数据源的,所以如果要实现动态数据源切换,而且在同一个数据源中保证事务是起作用的话,就需要注意二者的顺序问题,即:在事物起作用之前就要把数据源切换回来。
举一个例子:web开发常见是三层结构:controller、service、dao。一般事务都会在service层添加,如果使用spring的声明式事物管理,在调用service层代码之前,spring会通过aop的方式动态添加事务控制代码,所以如果要想保证事物是有效的,那么就必须在spring添加事务之前把数据源动态切换过来,也就是动态切换数据源的aop要至少在service上添加,而且要在spring声明式事物aop之前添加.根据上面分析:
- 最简单的方式是把动态切换数据源的aop加到controller层,这样在controller层里面就可以确定下来数据源了。不过,这样有一个缺点就是,每一个controller绑定了一个数据源,不灵活。对于这种:一个请求,需要使用两个以上数据源中的数据完成的业务时,就无法实现了。
- 针对上面的这种问题,可以考虑把动态切换数据源的aop放到service层,但要注意一定要在事务aop之前来完成。这样,对于一个需要多个数据源数据的请求,我们只需要在controller里面注入多个service实现即可。但这种做法的问题在于,controller层里面会涉及到一些不必要的业务代码,例如:合并两个数据源中的list…
- 此外,针对上面的问题,还可以再考虑一种方案,就是把事务控制到dao层,然后在service层里面动态切换数据源。
二、实例1:
本例子中,对不同数据源分包(package)管理,同一包下的代码使用了同一数据源。
1、写一个DynamicDataSource类继承AbstractRoutingDataSource,并实现determineCurrentLookupKey方法:
1
|
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
|
2、利用ThreadLocal解决线程安全问题:
1
|
public class CustomerContextHolder {
|
3、定义一个数据源切面类,通过aop来控制数据源的切换:
1
|
import org.aspectj.lang.JoinPoint;
|
4、在spring的application.xml中配置多个dataSource:
1
|
<!-- 数据源1 -->
|
三、实例2:
该例子,实现了在业务逻辑层控制了mysql的读写分离。同样,使用了spring的aop来动态切换读和写的数据源。和上个例子不同之处在于:不同数据源没有按照包分类管理,而是使用了自定义注解。
1、首先配置mysql 的主从复制:
详情见,这里。
2、自定义注解:
1
|
import java.lang.annotation.ElementType;
|
3、基于spring的aop实现多数据源(读和写两个数据源):
1)写一个ChooseDataSource: 类继承AbstractRoutingDataSource,并实现determineCurrentLookupKey方法:
1
|
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
|
2)利用ThreadLocal解决线程安全问题:
1
|
public class HandleDataSource {
|
3)定义一个数据源切面类,通过aop访问,获取方法上的自定义注解,然后根据注解内容尽情判断,动态设置数据源:
1
|
import java.lang.reflect.Method;
|
4)配置applicationContext.xml:
1
|
<!-- 主库数据源 -->
|
4、测试:
1
|
@DataSource("write")
|
- 测试写操作:可以通过应用修改数据,修改主库数据,发现从库的数据被同步更新了,所以定义的write操作都是走的写库
- 测试读操作: 后台修改从库数据,查看主库的数据没有被修改,在应用页面中刷新,发现读的是从库的数据,说明读写分离ok。