前一段时间研究了一下spring多数据源的配置和使用,为了后期从多个数据源拉取数据定时进行数据分析和报表统计做准备。由于之前做过的项目都是单数据源的,没有遇到这种场景,所以也一直没有去了解过如何配置多数据源。
后来发现其实基于spring来配置和使用多数据源还是比较简单的,因为spring框架已经预留了这样的接口可以方便数据源的切换。
先看一下spring获取数据源的源码:
可以看到AbstractRoutingDataSource获取数据源之前会先调用determineCurrentLookupKey方法查找当前的lookupKey,这个lookupKey就是数据源标识。
因此通过重写这个查找数据源标识的方法就可以让spring切换到指定的数据源了。
第一步:创建一个DynamicDataSource的类,继承AbstractRoutingDataSource并重写determineCurrentLookupKey方法,
代码如下:
public class DynamicDataSource extends AbstractRoutingDataSource { @Override protected Object determineCurrentLookupKey() { // 从自定义的位置获取数据源标识,对应线程工具类 return DynamicDataSourceHolder.getDataSource(); } } |
第二步:创建DynamicDataSourceHolder用于持有当前线程中使用的数据源标识,
代码如下:
public class DynamicDataSourceHolder { /** * 注意:数据源标识(spring框架原生提供的抽象接口)保存在线程变量中,避免多线程操作数据源时互相干扰 */ private static final ThreadLocal<String> THREAD_DATA_SOURCE = new ThreadLocal<String>(); public static String getDataSource() { //返回当前线程所对应的线程局部变量 return THREAD_DATA_SOURCE.get(); } //ThreadLocal类为Java原生线程类 public static void setDataSource(String dataSource) { //设置当前线程的线程局部变量的值 THREAD_DATA_SOURCE.set(dataSource); //和spring的配置文件的ban id相同 } public static void clearDataSource() { THREAD_DATA_SOURCE.remove(); /** 将当前线程局部变量的值删除,目的是为了减少内存的占用,该方法是JDK 5.0新增的方法。需要指出的是,当线程结束后,对应该线程的局部变量将自动被垃圾回收,所以显式调用该方法清除线程的局部变量并不是必须的操作,但它可以加快内存回收的速度 */ } } |
ThreadLocal类未用到的方法:
- protected Object initialValue()返回该线程局部变量的初始值,该方法是一个protected的方法,显然是为了让子类覆盖而设计的。这个方法是一个延迟调用方法,在线程第1次调用get()或set(Object)时才执行,并且仅执行1次。ThreadLocal中的缺省实现直接返回一个null。
第三步:配置多个数据源和第一步里创建的DynamicDataSource(spring总配置文件中添加)的bean,
简化的配置如下:
<!--创建数据源1,连接数据库db1 --> <bean id="dataSource1"class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"> <property name="driverClassName" value="${db1.driver}" /> <property name="url" value="${db1.url}" /> <property name="username" value="${db1.username}" /> <property name="password" value="${db1.password}" /> </bean> <!--创建数据源2,连接数据库db2 --> <bean id="dataSource2" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"> <property name="driverClassName" value="${db2.driver}" /> <property name="url" value="${db2.url}" /> <property name="username" value="${db2.username}" /> <property name="password" value="${db2.password}" /> </bean> <!--创建数据源3,连接数据库db3 --> <bean id="dataSource3" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"> <property name="driverClassName" value="${db3.driver}" /> <property name="url" value="${db3.url}" /> <property name="username" value="${db3.username}" /> <property name="password" value="${db3.password}" /> </bean> <bean id="dynamicDataSource" <!—ID 与第一个类名相对应,class为第一个类的地址--> class="com.test.context.datasource.DynamicDataSource"> <property name="targetDataSources"> <map key-type="java.lang.String"> <!-- 指定lookupKey和与之对应的数据源 --> <entry key="dataSource1" value-ref="dataSource1"></entry> <entry key="dataSource2" value-ref="dataSource2"></entry> <entry key="dataSource3 " value-ref="dataSource3"></entry> </map> </property> <!-- 这里可以指定默认的数据源 --> <property name="defaultTargetDataSource" ref="dataSource1" /> </bean> |
到这里已经可以使用多数据源了,在操作数据库之前只要DynamicDataSourceHolder.setDataSource("dataSource2")即可切换到数据源2并对数据库db2进行操作了。
@Service public class DataServiceImpl implements DataService { @Autowired private DataMapper dataMapper; @Override public List<Map<String, Object>> getList1() { // 没有指定,则默认使用数据源1 return dataMapper.getList1(); } @Override public List<Map<String, Object>> getList2() { // 调用 第二步 创建的set方法,指定切换到数据源2 DynamicDataSourceHolder.setDataSource("dataSource2"); return dataMapper.getList2(); } @Override public List<Map<String, Object>> getList3() { // 指定切换到数据源3 DynamicDataSourceHolder.setDataSource("dataSource3"); return dataMapper.getList3(); } } |