[教程] Spring+Mybatis环境配置多数据源
一、简要概述
在做项目的时候遇到需要从两个数据源获取数据,项目使用的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配置多数据源涉及事务无法切换解决方案(绝对有效)
如果您觉得这篇博文对你有帮助,请点赞或者喜欢,让更多的人看到,谢谢!
如果帅气(美丽)、睿智(聪颖),和我一样简单善良的你看到本篇博文中存在问题,请指出,我虚心接受你让我成长的批评,谢谢阅读!
祝你今天开心愉快!
欢迎访问我的csdn博客和关注的个人微信公众号!
愿你我在人生的路上能都变成最好的自己,能够成为一个独挡一面的人。
不管做什么,只要坚持下去就会看到不一样!在路上,不卑不亢!
博客首页 : http://blog.csdn.net/u010648555
© 每天都在变得更好的阿飞
[教程] Spring+Mybatis环境配置多数据源的更多相关文章
- 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. ...
- SpringBoot系列七:SpringBoot 整合 MyBatis(配置 druid 数据源、配置 MyBatis、事务控制、druid 监控)
1.概念:SpringBoot 整合 MyBatis 2.背景 SpringBoot 得到最终效果是一个简化到极致的 WEB 开发,但是只要牵扯到 WEB 开发,就绝对不可能缺少数据层操作,所有的开发 ...
- spring boot 环境配置(profile)切换
Spring Boot 集成教程 Spring Boot 介绍 Spring Boot 开发环境搭建(Eclipse) Spring Boot Hello World (restful接口)例子 sp ...
- spring+mybatis管理多个数据源(非分布式事务)
本文通过一个demo,介绍如何使用spring+mybatis管理多个数据源,注意,本文的事务管理并非之前博文介绍的分布式事务. 这个demo将使用两个事务管理器分别管理两个数据源.对于每一个独立的事 ...
- 【Mybatis】MyBatis之配置自定义数据源(十一)
本例是在[Mybatis]MyBatis之配置多数据源(十)的基础上进行拓展,查看本例请先学习第十章 实现原理 1.扩展Spring的AbstractRoutingDataSource抽象类(该类充当 ...
- Xamarin Anroid开发教程之验证环境配置是否正确
Xamarin Anroid开发教程之验证环境配置是否正确 经过前面几节的内容已经把所有的编程环境设置完成了,但是如何才能确定所有的一切都处理争取并且没有任何错误呢?这就需要使用相应的实例来验证,本节 ...
- Spring Boot + MyBatis + Pagehelper 配置多数据源
前言: 本文为springboot结合mybatis配置多数据源,在项目当中很多情况是使用主从数据源来读写分离,还有就是操作多库,本文介绍如何一个项目同时使用2个数据源. 也希望大家带着思考去学习!博 ...
- 【Mybatis】MyBatis之配置多数据源(十)
在做项目的过程中,有时候一个数据源是不够,那么就需要配置多个数据源.本例介绍mybatis多数据源配置 前言 一般项目单数据源,使用流程如下: 单个数据源绑定给sessionFactory,再在Dao ...
- springmvc+spring+mybatis 项目配置
前提 工作环境:JDK 1.8.Mysql 5.7.18.Intellij IDEA 2018.1.Tomcat 8.5.Maven 框架版本:Spring 4.2.0.RELEASE.SpringM ...
随机推荐
- (转)Maven学习总结(三)——使用Maven构建项目
孤傲苍狼 只为成功找方法,不为失败找借口! Maven学习总结(三)——使用Maven构建项目 maven作为一个高度自动化构建工具,本身提供了构建项目的功能,下面就来体验一下使用maven构建项目的 ...
- ES6学习(二)基础命令
一.Let 和 const 作用域的概念:在es5之前是有两个作用域,一个是全局作用域,另外一个是函数作用域,在es6中就多了这样一个块作用域.在这里let 和 const 就是传说中的块作用域,它 ...
- MySQL删除所有表的外键约束、禁用外键约束
转: MySQL删除所有表的外键约束.禁用外键约束 2017年10月27日 00:11:34 李阿飞 阅读数:4512 版权声明:本文为博主原创文章,未经博主允许不得转载. https://blo ...
- mysql、mybatis遇到问题集合
1.错误描述 使用JDBC连接MySql时出现:The server time zone value '�й���ʱ��' is unrecognized or represents more th ...
- ThinkPHP框架学习(一)
这几天呢,断断续续地在看孙叔华老师的ThinkPHP教程,期间还做了一些其他事情,出去办了点事,总结总结下一学期规划等等,不知不觉间又过去了大半个星期.现在呢,看完了一天的教程,在这里,还是希望稍微总 ...
- 流媒体技术学习笔记之(十四)FFmpeg进行笔记本摄像头+麦克风实现流媒体直播服务
FFmpeg推送视频流,Nginx RTMP模块转发,VLC播放器播放,实现整个RTMP直播 查看本机电脑的设备 ffmpeg -list_devices true -f dshow -i dummy ...
- CSS-3 新弹性盒模型属性
flex 语法:flex:none | [ flex-grow ] || [ flex-shrink ] || [ flex-basis ] 取值: none:none关键字的计算值为: 0 0 au ...
- expect 交互
expect expect 是一种自动交互语言,能实现在shell脚本中为scp和ssh等自动输入密码自动登录. exp_continue # 多个spawn命令时并行 interact # 执行完成 ...
- 设置linux的console为串口【转】
转自:http://blog.chinaunix.net/uid-27717694-id-4074219.html 以Grub2为例:1. 修改文件/etc/default/grub #显示启动菜 ...
- C# 将某个方法去异步执行
C# 将某个方法去异步执行 Task.Run(() => { string msgerror = SendPhoneCode.NewSendByTemplate(apply.PhoneNum, ...