一、简要概述

在做项目的时候遇到需要从两个数据源获取数据,项目使用的Spring + Mybatis环境,看到网上有一些关于多数据源的配置,自己也整理学习一下,然后自动切换实现从不同的数据源获取数据功能。

二、代码详解

2.1 DataSourceConstants 数据源常量类

/**
* 数据源名称常量类
* 对应 application.xml 中 bean multipleDataSource
* @author:dufy
* @version:1.0.0
* @date 2018/12/17
*/
public class DataSourceConstants {
/**
* 数据源1,默认数据源配置
*/
public static final String DATASOURCE_1 = "dataSource1";
/**
* 数据源2
*/
public static final String DATASOURCE_2 = "dataSource2"; }

2.2 DataSourceType 自定义数据源注解

/**
* 自定义数据源类型注解
*
* @author:dufy
* @version:1.0.0
* @date 2018/12/17
*/ @Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataSourceType { String value() default DataSourceConstants.DATASOURCE_1; }

2.3 MultipleDataSource 多数据源配置类

MultipleDataSource 继承 AbstractRoutingDataSource 类,为什么继承这个类就可以了?请看 第五章 :实现原理。

import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

/**
* 自定义多数据源配置类
*
* @author:dufy
* @version:1.0.0
* @date 2018/12/17
*/
public class MultipleDataSource extends AbstractRoutingDataSource { private static final ThreadLocal<String> dataSourceHolder = new ThreadLocal<String>(); /**
* 设置数据源
* @param dataSource 数据源名称
*/
public static void setDataSource(String dataSource){
dataSourceHolder.set(dataSource);
} /**
* 获取数据源
* @return
*/
public static String getDatasource() {
return dataSourceHolder.get();
} /**
* 清除数据源
*/
public static void clearDataSource(){
dataSourceHolder.remove();
} @Override
protected Object determineCurrentLookupKey() {
return dataSourceHolder.get();
}
}

2.4 MultipleDataSourceAop 多数据源自动切换通知类

注意:请设置 @Order(0)。否则可能出现 数据源切换失败问题! 因为要在事务开启之前就进行判断,并进行切换数据源!

/**
* 多数据源自动切换通知类<br>
* <p>
* 首先判断当前类是否被该DataSourceType注解进行注释,如果没有指定注解,则采用默认的数据源配置; <br>
* 如果有,则读取注解中的value值,将数据源切到value指定的数据源
*
* @author:dufy
* @version:1.0.0
* @date 2018/12/17
*/ @Aspect // for aop
@Component // for auto scan
@Order(0) // execute before @Transactional
public class MultipleDataSourceAop { private final Logger logger = Logger.getLogger(MultipleDataSourceAop.class); /**
* 拦截 com.**.servicee中所有的方法,根据配置情况进行数据源切换
* com.jiuling.tz.service
* com.jiuling.web.service
* @param joinPoint
* @throws Throwable
*/
@Before("execution(* com.dufy.*.service.*.*(..))")
public void changeDataSource(JoinPoint joinPoint) throws Throwable { try {
// 拦截的实体类,就是当前正在执行的service
Class<?> clazz = joinPoint.getTarget().getClass();
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
// 提取目标对象方法注解和类型注解中的数据源标识
Class<?>[] types = method.getParameterTypes();
if (clazz.isAnnotationPresent(DataSourceType.class)) {
DataSourceType source = clazz.getAnnotation(DataSourceType.class);
MultipleDataSource.setDataSource(source.value());
logger.info("Service Class 数据源切换至--->" + source.value());
} Method m = clazz.getMethod(method.getName(), types);
if (m != null && m.isAnnotationPresent(DataSourceType.class)) {
DataSourceType source = m.getAnnotation(DataSourceType.class);
MultipleDataSource.setDataSource(source.value());
logger.info("Service Method 数据源切换至--->" + source.value());
}
} catch (Exception e) {
e.printStackTrace();
}
} /**
* 方法结束后
*/
@After("execution(* com.dufy.*.service.*.*(..))")
public void afterReturning() throws Throwable {
try {
MultipleDataSource.clearDataSource();
logger.debug("数据源已移除!");
} catch (Exception e) {
e.printStackTrace();
logger.debug("数据源移除报错!");
} }
}

三、配置详情

applicationContext.xml 中配置详情

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx" xmlns:p="http://www.springframework.org/schema/p"
xmlns:util="http://www.springframework.org/schema/util" xmlns:jdbc="http://www.springframework.org/schema/jdbc"
xmlns:cache="http://www.springframework.org/schema/cache"
xsi:schemaLocation="
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/jdbc
http://www.springframework.org/schema/jdbc/spring-jdbc.xsd
http://www.springframework.org/schema/cache
http://www.springframework.org/schema/cache/spring-cache.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/util
http://www.springframework.org/schema/util/spring-util.xsd"> <!-- 自动扫描包 ,将带有注解的类 纳入spring容器管理 -->
<context:component-scan base-package="com.dufy"></context:component-scan> <!-- 引入配置文件 -->
<bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="locations">
<list>
<value>classpath*:jdbc.properties</value>
</list>
</property>
</bean> <!-- dataSource1 配置 -->
<bean id="dataSource1" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
<!-- 基本属性 url、user、password -->
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/> <!-- 配置初始化大小、最小、最大 -->
<property name="initialSize" value="${ds.initialSize}"/>
<property name="minIdle" value="${ds.minIdle}"/>
<property name="maxActive" value="${ds.maxActive}"/> <!-- 配置获取连接等待超时的时间 -->
<property name="maxWait" value="${ds.maxWait}"/> <!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 -->
<property name="timeBetweenEvictionRunsMillis" value="${ds.timeBetweenEvictionRunsMillis}"/> <!-- 配置一个连接在池中最小生存的时间,单位是毫秒 -->
<property name="minEvictableIdleTimeMillis" value="${ds.minEvictableIdleTimeMillis}"/> <property name="validationQuery" value="SELECT 'x'"/>
<property name="testWhileIdle" value="true"/>
<property name="testOnBorrow" value="false"/>
<property name="testOnReturn" value="false"/> <!-- 打开PSCache,并且指定每个连接上PSCache的大小 -->
<property name="poolPreparedStatements" value="false"/>
<property name="maxPoolPreparedStatementPerConnectionSize" value="20"/> <!-- 配置监控统计拦截的filters -->
<property name="filters" value="stat"/>
</bean> <!-- dataSource2 配置-->
<bean id="dataSource2" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
<!-- 基本属性 url、user、password -->
<property name="url" value="${jd.jdbc.url}"/>
<property name="username" value="${jd.jdbc.username}"/>
<property name="password" value="${jd.jdbc.password}"/>
<!-- 其他配置省略 -->
</bean> <!--多数据源配置-->
<bean id="multipleDataSource" class="com.jiuling.core.ds.MultipleDataSource">
<property name="defaultTargetDataSource" ref="dataSource1" />
<property name="targetDataSources">
<map key-type = "java.lang.String">
<entry key="dataSource1" value-ref="dataSource1"/>
<entry key="dataSource2" value-ref="dataSource2"/>
<!-- 这里还可以加多个dataSource -->
</map>
</property>
</bean> <!-- mybatis文件配置,扫描所有mapper文件 -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean" p:dataSource-ref="multipleDataSource"
p:configLocation="classpath:mybatis-config.xml"
p:mapperLocations="classpath:com/dufy/*/dao/*.xml"/> <!-- spring与mybatis整合配置,扫描所有dao -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer" p:basePackage="com.dufy.*.dao"
p:sqlSessionFactoryBeanName="sqlSessionFactory"/> <!-- 对dataSource 数据源进行事务管理 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"
p:dataSource-ref="multipleDataSource"/> <!-- 事务管理 通知 -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<!-- 对insert,update,delete 开头的方法进行事务管理,只要有异常就回滚 -->
<tx:method name="insert*" propagation="REQUIRED" rollback-for="java.lang.Throwable"/>
<tx:method name="update*" propagation="REQUIRED" rollback-for="java.lang.Throwable"/>
<tx:method name="delete*" propagation="REQUIRED" rollback-for="java.lang.Throwable"/>
<!-- select,count开头的方法,开启只读,提高数据库访问性能 -->
<tx:method name="select*" read-only="true"/>
<tx:method name="count*" read-only="true"/>
<!-- 对其他方法 使用默认的事务管理 -->
<tx:method name="*"/>
</tx:attributes>
</tx:advice> <!-- 事务 aop 配置 -->
<aop:config>
<aop:pointcut id="serviceMethods" expression="execution(* com.dufy.*.service..*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="serviceMethods"/>
</aop:config> <!-- 配置使Spring采用CGLIB代理 -->
<aop:aspectj-autoproxy proxy-target-class="true"/> <!-- 启用对事务注解的支持 -->
<tx:annotation-driven transaction-manager="transactionManager"/> </beans>

jdbc.properties 配置内容

##-------------mysql数据库连接配置 ---------------------###
# dataSource1
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://192.168.1.110:3306/jdsc?useUnicode=true&characterEncoding=utf-8
jdbc.username=test
jdbc.password=123456 #配置初始化大小、最小、最大
ds.initialSize=1
ds.minIdle=1
ds.maxActive=20
#配置获取连接等待超时的时间
ds.maxWait=60000 #配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
ds.timeBetweenEvictionRunsMillis=60000 #配置一个连接在池中最小生存的时间,单位是毫秒
ds.minEvictableIdleTimeMillis=300000 # dataSource2
jd.jdbc.url=jdbc:mysql://192.168.1.120:3306/jdsc?useUnicode=true&characterEncoding=utf-8
jd.jdbc.username=root
jd.jdbc.password=123456

四、测试切换

注意:测试服务类的包路径,因为只有被AOP拦截的到的指定的Service才会进行数据源的切换。

package com.dufy.web.service.impl.Data1ServiceImpl;

/**
* 使用 dataSourc1 ,配置dataSourc1的数据源
* @author:duf
* @version:1.0.0
* @date 2018/12/17
*/
@Service
@DataSourceType(value = DataSourceConstants.DATASOURCE_1)
public class Data1ServiceImpl implements Data1Service { @Resource
private Data1Mapper data1Mapper; @Override
public List<String> selectCaseByUpdateTime(String name) {
List<String> data1 = data1Mapper.selectData1(name);
return data1;
} } package com.dufy.web.service.impl.Data2ServiceImpl;
/**
* 使用 dataSourc2 ,配置dataSourc2的数据源
* @author:duf
* @version:1.0.0
* @date 2018/12/17
*/
@Service
@DataSourceType(value = DataSourceConstants.DATASOURCE_2)
public class Data2ServiceImpl implements Data2Service { @Resource
private Data2Mapper data2Mapper; @Override
public List<String> selectCaseByUpdateTime(String name) {
List<String> data2 = data2Mapper.selectData2(name);
return data2;
} }

通过测试后发现,两个Service服务器分别调用自己的配置的数据源进行数据的获取!

五、实现原理

基于AbstractRoutingDataSource 实现 多数据源配置,通过AOP来进行数据源的灵活切换。AOP的相关原理这里不做说明,就简单说一下 AbstractRoutingDataSource ,它是如何切换数据源的!

首先我们继承 AbstractRoutingDataSource 它是一个抽象类,然后要实现它里面的一个抽象方法。

public abstract class AbstractRoutingDataSource extends AbstractDataSource implements InitializingBean

实现了InitializingBean,InitializingBean接口为bean提供了初始化方法的方式,它只包括afterPropertiesSet方法,凡是继承该接口的类,在初始化bean的时候都会执行该方法。

AbstractRoutingDataSource afterPropertiesSet方法的实现。

public void afterPropertiesSet() {
if (this.targetDataSources == null) {
throw new IllegalArgumentException("Property 'targetDataSources' is required");
} else {
this.resolvedDataSources = new HashMap(this.targetDataSources.size());
Iterator var1 = this.targetDataSources.entrySet().iterator(); while(var1.hasNext()) {
Entry<Object, Object> entry = (Entry)var1.next();
Object lookupKey = this.resolveSpecifiedLookupKey(entry.getKey());
DataSource dataSource = this.resolveSpecifiedDataSource(entry.getValue());
this.resolvedDataSources.put(lookupKey, dataSource);
} if (this.defaultTargetDataSource != null) {
this.resolvedDefaultDataSource = this.resolveSpecifiedDataSource(this.defaultTargetDataSource);
} }
}

afterPropertiesSet方法将配置在 applicationContext.xml中的 targetDataSources 解析并构造出一个HashMap。

然后在实际过程中当需要访问数据库的时候,会首先获取一个Connection,下面看一下获取 Connection 的方法。

public Connection getConnection() throws SQLException {
return this.determineTargetDataSource().getConnection();
} public Connection getConnection(String username, String password) throws SQLException {
return this.determineTargetDataSource().getConnection(username, password);
} protected DataSource determineTargetDataSource() {
Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");
// 这里需要注意,通过子类的 determineCurrentLookupKey 方法 获取 lookupKey
Object lookupKey = this.determineCurrentLookupKey();
// 转换为对应的 DataSource 数据源
DataSource dataSource = (DataSource)this.resolvedDataSources.get(lookupKey);
if (dataSource == null && (this.lenientFallback || lookupKey == null)) {
// 如果符合上述条件,则获取默认的数据源,也就是在 ApplicationContext.xml 配置的默认数据源
dataSource = this.resolvedDefaultDataSource;
} if (dataSource == null) {
throw new IllegalStateException("Cannot determine target DataSource for lookup key [" + lookupKey + "]");
} else {
return dataSource;
}
}
// 抽象方法,用于子类实现
protected abstract Object determineCurrentLookupKey();

知道了 AbstractRoutingDataSource 的抽象方法后,通过AOP拦截,将Service上面配置不同的数据源进行装配到当前请求的ThreadLocal中, 最后 在获取Connection的时候,就能通过determineCurrentLookupKey 方法获取到 设置的数据源。

@Override
protected Object determineCurrentLookupKey() {
return dataSourceHolder.get();
}

再次强调必须保证切换数据源的Aspect必须在@Transactional这个Aspect之前执行,使用@Order(0)来保证切换数据源先于@Transactional执行)

六、参考文章

Spring, MyBatis 多数据源的配置和管理

spring配置多数据源涉及事务无法切换解决方案(绝对有效)


如果您觉得这篇博文对你有帮助,请点赞或者喜欢,让更多的人看到,谢谢!

如果帅气(美丽)、睿智(聪颖),和我一样简单善良的你看到本篇博文中存在问题,请指出,我虚心接受你让我成长的批评,谢谢阅读!
祝你今天开心愉快!


欢迎访问我的csdn博客和关注的个人微信公众号!

愿你我在人生的路上能都变成最好的自己,能够成为一个独挡一面的人。

不管做什么,只要坚持下去就会看到不一样!在路上,不卑不亢!

博客首页 : http://blog.csdn.net/u010648555

© 每天都在变得更好的阿飞

[教程] Spring+Mybatis环境配置多数据源的更多相关文章

  1. idea spring+springmvc+mybatis环境配置整合详解

    idea spring+springmvc+mybatis环境配置整合详解 1.配置整合前所需准备的环境: 1.1:jdk1.8 1.2:idea2017.1.5 1.3:Maven 3.5.2 2. ...

  2. SpringBoot系列七:SpringBoot 整合 MyBatis(配置 druid 数据源、配置 MyBatis、事务控制、druid 监控)

    1.概念:SpringBoot 整合 MyBatis 2.背景 SpringBoot 得到最终效果是一个简化到极致的 WEB 开发,但是只要牵扯到 WEB 开发,就绝对不可能缺少数据层操作,所有的开发 ...

  3. spring boot 环境配置(profile)切换

    Spring Boot 集成教程 Spring Boot 介绍 Spring Boot 开发环境搭建(Eclipse) Spring Boot Hello World (restful接口)例子 sp ...

  4. spring+mybatis管理多个数据源(非分布式事务)

    本文通过一个demo,介绍如何使用spring+mybatis管理多个数据源,注意,本文的事务管理并非之前博文介绍的分布式事务. 这个demo将使用两个事务管理器分别管理两个数据源.对于每一个独立的事 ...

  5. 【Mybatis】MyBatis之配置自定义数据源(十一)

    本例是在[Mybatis]MyBatis之配置多数据源(十)的基础上进行拓展,查看本例请先学习第十章 实现原理 1.扩展Spring的AbstractRoutingDataSource抽象类(该类充当 ...

  6. Xamarin Anroid开发教程之验证环境配置是否正确

    Xamarin Anroid开发教程之验证环境配置是否正确 经过前面几节的内容已经把所有的编程环境设置完成了,但是如何才能确定所有的一切都处理争取并且没有任何错误呢?这就需要使用相应的实例来验证,本节 ...

  7. Spring Boot + MyBatis + Pagehelper 配置多数据源

    前言: 本文为springboot结合mybatis配置多数据源,在项目当中很多情况是使用主从数据源来读写分离,还有就是操作多库,本文介绍如何一个项目同时使用2个数据源. 也希望大家带着思考去学习!博 ...

  8. 【Mybatis】MyBatis之配置多数据源(十)

    在做项目的过程中,有时候一个数据源是不够,那么就需要配置多个数据源.本例介绍mybatis多数据源配置 前言 一般项目单数据源,使用流程如下: 单个数据源绑定给sessionFactory,再在Dao ...

  9. springmvc+spring+mybatis 项目配置

    前提 工作环境:JDK 1.8.Mysql 5.7.18.Intellij IDEA 2018.1.Tomcat 8.5.Maven 框架版本:Spring 4.2.0.RELEASE.SpringM ...

随机推荐

  1. c++并发编程之thread::join()和thread::detach()

    thread::join(): 阻塞当前线程,直至 *this 所标识的线程完成其执行.*this 所标识的线程的完成同步于从 join() 的成功返回. 该方法简单暴力,主线程等待子进程期间什么都不 ...

  2. 《剑指offer》— JavaScript(30)连续子数组的最大和

    连续子数组的最大和 题目描述 HZ偶尔会拿些专业问题来忽悠那些非计算机专业的同学.今天测试组开完会后,他又发话了:在古老的一维模式识别中,常常需要计算连续子向量的最大和,当向量全为正数的时候,问题很好 ...

  3. ubuntu 指定用户执行脚本

    方法 创建可执行脚本 以下以脚本名称为superset.sh为例,具体的脚本内容大家可以自己发挥. cd ~ vi superset.sh # 脚本内容自己写好后保存 修改脚本权限 sudo chmo ...

  4. 样本标准差分母为何是n-1

    sklearn实战-乳腺癌细胞数据挖掘 https://study.163.com/course/introduction.htm?courseId=1005269003&utm_campai ...

  5. telegraf、influxDB、Grafana的安装与基本使用

    目的理解influxDB的数据收集原理和方法为使用grafana分析数据及展示结作好准备介绍[收集数据] Telegraf 是一个用 Go 编写的代理程序,可收集系统和服务的统计数据,并写入到 Inf ...

  6. bzoj千题计划193:bzoj2460: [BeiJing2011]元素

    http://www.lydsy.com/JudgeOnline/problem.php?id=2460 按魔力值从小到大排序构造线性基 #include<cstdio> #include ...

  7. c#的as,is 运算符

  8. sh脚本学习之:变量

    变量的创建 环境配置 /etc/profile =>~/.bash_profile(~/.bash_login,~/.profile) => ~/.bashrc sh声明 name=&qu ...

  9. js调试系列: 断点与动态调试[基础篇]

    js调试系列目录: - 额,我说的不是张敬轩的 断点 这首歌,是调试用到的断点,进入正题吧. 昨天留的课后练习 1. 分析 votePost 函数是如何实现 推荐 的.其实我们已经看到了源码,只要读下 ...

  10. spfa求图的最大流

    题目链接: https://vjudge.net/contest/255738#problem/B AC代码: #include <iostream> #include<vector ...