前一段时间研究了一下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 {
/**
* 注意:数据源标识保存在线程变量中,避免多线程操作数据源时互相干扰
*/
private static final ThreadLocal<String> THREAD_DATA_SOURCE = new ThreadLocal<String>(); public static String getDataSource() {
return THREAD_DATA_SOURCE.get();
} public static void setDataSource(String dataSource) {
THREAD_DATA_SOURCE.set(dataSource);
} public static void clearDataSource() {
THREAD_DATA_SOURCE.remove();
} }

第三步:配置多个数据源和第一步里创建的DynamicDataSource的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" 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() {
// 指定切换到数据源2
DynamicDataSourceHolder.setDataSource("dataSource2");
return dataMapper.getList2();
} @Override
public List<Map<String, Object>> getList3() {
// 指定切换到数据源3
DynamicDataSourceHolder.setDataSource("dataSource3");
return dataMapper.getList3();
}
}

--------------------------------------------------------------------------------------华丽的分割线--------------------------------------------------------------------------------------------------

但是问题来了,如果每次切换数据源时都调用DynamicDataSourceHolder.setDataSource("xxx")就显得十分繁琐了,而且代码量大了很容易会遗漏,后期维护起来也比较麻烦。能不能直接通过注解的方式指定需要访问的数据源呢,比如在dao层使用@DataSource("xxx")就指定访问数据源xxx?当然可以!前提是,再加一点额外的配置^_^。
首先,我们得定义一个名为DataSource的注解,代码如下:

 @Target({ TYPE, METHOD })
@Retention(RUNTIME)
public @interface DataSource {
String value();
}

然后,定义AOP切面以便拦截所有带有注解@DataSource的方法,取出注解的值作为数据源标识放到DynamicDataSourceHolder的线程变量中:

 public class DataSourceAspect {

     /**
* 拦截目标方法,获取由@DataSource指定的数据源标识,设置到线程存储中以便切换数据源
*
* @param point
* @throws Exception
*/
public void intercept(JoinPoint point) throws Exception {
Class<?> target = point.getTarget().getClass();
MethodSignature signature = (MethodSignature) point.getSignature();
// 默认使用目标类型的注解,如果没有则使用其实现接口的注解
for (Class<?> clazz : target.getInterfaces()) {
resolveDataSource(clazz, signature.getMethod());
}
resolveDataSource(target, signature.getMethod());
} /**
* 提取目标对象方法注解和类型注解中的数据源标识
*
* @param clazz
* @param method
*/
private void resolveDataSource(Class<?> clazz, Method method) {
try {
Class<?>[] types = method.getParameterTypes();
// 默认使用类型注解
if (clazz.isAnnotationPresent(DataSource.class)) {
DataSource source = clazz.getAnnotation(DataSource.class);
DynamicDataSourceHolder.setDataSource(source.value());
}
// 方法注解可以覆盖类型注解
Method m = clazz.getMethod(method.getName(), types);
if (m != null && m.isAnnotationPresent(DataSource.class)) {
DataSource source = m.getAnnotation(DataSource.class);
DynamicDataSourceHolder.setDataSource(source.value());
}
} catch (Exception e) {
System.out.println(clazz + ":" + e.getMessage());
}
} }

最后在spring配置文件中配置拦截规则就可以了,比如拦截service层或者dao层的所有方法:

 <bean id="dataSourceAspect" class="com.test.context.datasource.DataSourceAspect" />
<aop:config>
<aop:aspect ref="dataSourceAspect">
<!-- 拦截所有service方法 -->
<aop:pointcut id="dataSourcePointcut" expression="execution(* com.test.*.dao.*.*(..))"/>
<aop:before pointcut-ref="dataSourcePointcut" method="intercept" />
</aop:aspect>
</aop:config>
</bean>

OK,这样就可以直接在类或者方法上使用注解@DataSource来指定数据源,不需要每次都手动设置了。

示例代码如下:

 @Service
// 默认DataServiceImpl下的所有方法均访问数据源1
@DataSource("dataSource1")
public class DataServiceImpl implements DataService {
@Autowired
private DataMapper dataMapper; @Override
public List<Map<String, Object>> getList1() {
// 不指定,则默认使用数据源1
return dataMapper.getList1();
} @Override
// 覆盖类上指定的,使用数据源2
@DataSource("dataSource2")
public List<Map<String, Object>> getList2() {
return dataMapper.getList2();
} @Override
// 覆盖类上指定的,使用数据源3
@DataSource("dataSource3")
public List<Map<String, Object>> getList3() {
return dataMapper.getList3();
}
}

提示:注解@DataSource既可以加在方法上,也可以加在接口或者接口的实现类上,优先级别:方法>实现类>接口。也就是说如果接口、接口实现类以及方法上分别加了@DataSource注解来指定数据源,则优先以方法上指定的为准。

基于注解的Spring多数据源配置和使用的更多相关文章

  1. 基于注解的Spring多数据源配置和使用(非事务)

    原文:基于注解的Spring多数据源配置和使用 1.创建DynamicDataSource类,继承AbstractRoutingDataSource package com.rps.dataSourc ...

  2. 基于xml的Spring多数据源配置和使用

    上一篇讲了<基于注解的Spring多数据源配置和使用>,通过在类或者方法上添加@DataSource注解就可以指定某个数据源.这种方式的优点是控制粒度细,也更灵活. 但是当有些时候项目分模 ...

  3. 基于注解的Spring AOP的配置和使用

    摘要: 基于注解的Spring AOP的配置和使用 AOP是OOP的延续,是Aspect Oriented Programming的缩写,意思是面向切面编程.可以通过预编译方式和运行期动态代理实现在不 ...

  4. 基于注解实现SpringBoot多数据源配置

    1.功能介绍 在实际的开发中,同一个项目中使用多个数据源是很常见的场景.最近在学习的过程中使用注解的方式实现了一个Springboot项目多数据源的功能.具体实现方式如下. 2.在applicatio ...

  5. 基于注解的Spring AOP的配置和使用--转载

    AOP是OOP的延续,是Aspect Oriented Programming的缩写,意思是面向切面编程.可以通过预编译方式和运行期动态代理实现在不修改源代码的情况下给程序动态统一添加功能的一种技术. ...

  6. spring基于通用Dao的多数据源配置详解【ds1】

    spring基于通用Dao的多数据源配置详解 有时候在一个项目中会连接多个数据库,需要在spring中配置多个数据源,最近就遇到了这个问题,由于我的项目之前是基于通用Dao的,配置的时候问题不断,这种 ...

  7. Spring系列9:基于注解的Spring容器配置

    写在前面 前面几篇中我们说过,Spring容器支持3种方式进行bean定义信息的配置,现在具体说明下: XML:bean的定义和依赖都在xml文件中配置,比较繁杂. Annotation-based ...

  8. Spring7:基于注解的Spring MVC(下篇)

    Model 上一篇文章<Spring6:基于注解的Spring MVC(上篇)>,讲了Spring MVC环境搭建.@RequestMapping以及参数绑定,这是Spring MVC中最 ...

  9. 基于注解的Spring AOP入门、增强Advice实例

    这篇文章简单通过一个例子,介绍几种增强的基本配置,以方便spring框架初学者对aop的代码结构有个清楚的了解认识.首先,spring支持aop编程,支持aspectJ的语法格式来表示切入点,切面,增 ...

随机推荐

  1. 【AR实验室】OpenGL ES绘制相机(OpenGL ES 1.0版本)

    0x00 - 前言 之前做一些移动端的AR应用以及目前看到的一些AR应用,基本上都是这样一个套路:手机背景显示现实场景,然后在该背景上进行图形学绘制.至于图形学绘制时,相机外参的解算使用的是V-SLA ...

  2. socket读写返回值的处理

    在调用socket读写函数read(),write()时,都会有返回值.如果没有正确处理返回值,就可能引入一些问题 总结了以下几点 1当read()或者write()函数返回值大于0时,表示实际从缓冲 ...

  3. 学习AOP之认识一下Spring AOP

    心碎之事 要说知道AOP这个词倒是很久很久以前了,但是直到今天我也不敢说非常的理解它,其中的各种概念即抽象又太拗口. 在几次面试中都被问及AOP,但是真的没有答上来,或者都在面上,这给面试官的感觉就是 ...

  4. Win10 IIS本地部署MVC网站时不能运行?

    异常处理汇总-服 务 器 http://www.cnblogs.com/dunitian/p/4522983.html 部署后出现这个错误: 打开文件目录后发现是可以看见目录的,静态页面也是可以打开的 ...

  5. 手动添加kdump

    背景:     Linux嵌入式设备内核挂死后,无法自动重启,需要手动重启.而且如果当时没有连串口的话,就无法记录内核挂死时的堆栈,所以需要添加一种方式来记录内核挂死信息方便以后调试使用.设备中增加k ...

  6. LeetCode[3] Longest Substring Without Repeating Characters

    题目描述 Given a string, find the length of the longest substring without repeating characters. For exam ...

  7. 高频交易算法研发心得--MACD指标算法及应用

    凤鸾宝帐景非常,尽是泥金巧样妆. 曲曲远山飞翠色:翩翩舞袖映霞裳. 梨花带雨争娇艳:芍药笼烟骋媚妆. 但得妖娆能举动,取回长乐侍君王. [摘自<封神演义>纣王在女娲宫上香时题的诗] 一首定 ...

  8. ASP.NET Core CORS 简单使用

    CORS 全称"跨域资源共享"(Cross-origin resource sharing). 跨域就是不同域之间进行数据访问,比如 a.sample.com 访问 b.sampl ...

  9. Asp.Net WebApi核心对象解析(上篇)

    生活需要自己慢慢去体验和思考,对于知识也是如此.匆匆忙忙的生活,让人不知道自己一天到晚都在干些什么,似乎每天都在忙,但又好似不知道自己到底在忙些什么.不过也无所谓,只要我们知道最后想要什么就行.不管怎 ...

  10. MongoDB系列(二):C#应用

    前言 上一篇文章<MongoDB系列(一):简介及安装>已经介绍了MongoDB以及其在window环境下的安装,这篇文章主要讲讲如何用C#来与MongoDB进行通讯.再次强调一下,我使用 ...