一、简要概述

在做项目的时候遇到需要从两个数据源获取数据,项目使用的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. 错误日志收集sentry的安装与简单使用

    通过官方文档https://docs.sentry.io/可以得知,安装服务有两种方式,一种是使用Python,这种方式个人感觉比较麻烦.于是选择了第二种方式:使用docker. 我是在Windows ...

  2. [收藏]:[算法]LRU和LFU的区别

    LRU和LFU是不同的! LRU是最近最少使用页面置换算法(Least Recently Used),也就是首先淘汰最长时间未被使用的页面! LFU是最近最不常用页面置换算法(Least Freque ...

  3. Python 基础数据类型之tuplu

    Python的元组与列表类似,不同之处在于元组的元素不能修改. 元组使用小括号,列表使用方括号. 1.元组的定义 tuple1 = ("hello", "world&qu ...

  4. python---django中模板渲染(csrf令牌使用,自定义模板函数)

    使用终端,可以更方便的去实验,但是没有提示信息: 在项目目录下: D:\MyPython\day23\HelloWorld>python manage.py shell 开始实验: >&g ...

  5. element-ui合并行:span-method

    objectSpanMethod({ row, column, rowIndex, columnIndex }) { if (columnIndex === 0) { if (rowIndex % 2 ...

  6. nginx配置自动跳转

    阅读更多 希望实现的效果是,用户只要访问域名,自动跳转到index.html页面 原本配置为: location / { root   /users/apple/git_local/YAE/YAE/f ...

  7. bzoj千题计划227:bzoj1486: [HNOI2009]最小圈

    http://www.lydsy.com/JudgeOnline/problem.php?id=1486 二分答案 dfs版spfa判负环 #include<queue> #include ...

  8. CS53 C 单调栈

    给出一个目标序列,初始序列为0,你有一种操作方式可以将某段值相同的区间全部加上一定的值,问得到目标序列的最小次数. 开始没注意要求值相同,想都不想就暴力了,后来发现对于每个峰,只要找每个相对峰顶的阶数 ...

  9. [C]语法, 知识点总结(一. 进制, 格式化输入/出, 指针)

    进制 概念: n进制, 最大的数是n-1, 逢n进1位. 数据类型 概念: 其实就是占的位数不同, 转换到计算机当中都是0和1. 常用: 类型名 占字节数 描述 char 1字节=8个二进制位 字符类 ...

  10. Sublime Text 2 绿化与汉化 [Windows篇]

    其实 ST3 已经出了很久了,可是我这个人恋旧,一直钟爱 ST2,所以就选择她了.最近我的 ST2 越来越卡,甚至有时候输入都会延迟1秒,所以打算自己搞个绿化版. 打开 Sublime Text 官网 ...