Spring+Mybatis动态切换数据源
功能需求是公司要做一个大的运营平台:
1、运营平台有自身的数据库,维护用户、角色、菜单、部分以及权限等基本功能。
2、运营平台还需要提供其他不同服务(服务A,服务B)的后台运营,服务A、服务B的数据库是独立的。
所以,运营平台至少要连三个库:运营库,A库,B库,并且希望达到针对每个功能请求能够自动切换到对应的数据源(我最终实现是针对Service的方法级别进行切换的,也可以实现针对每个DAO层的方法进行切换。我们系统的功能是相互之间比较独立的)。
第一步:配置多数据源
1、定义数据源:
我采用的数据源是阿里的DruidDataSource(用DBCP也行,这个随便)。配置如下:
<!-- op dataSource -->
<bean id="opDataSource" class="com.alibaba.druid.pool.DruidDataSource"
init-method="init" destroy-method="close">
<property name="url" value="${db.master.url}" />
<property name="username" value="${db.master.user}" />
<property name="password" value="${db.master.password}" />
<property name="driverClassName" value="${db.master.driver}" />
<property name="initialSize" value="5" />
<property name="maxActive" value="100" />
<property name="minIdle" value="10" />
<property name="maxWait" value="60000" />
<property name="validationQuery" value="SELECT 'x'" />
<property name="testOnBorrow" value="false" />
<property name="testOnReturn" value="false" />
<property name="testWhileIdle" value="true" />
<property name="timeBetweenEvictionRunsMillis" value="600000" />
<property name="minEvictableIdleTimeMillis" value="300000" />
<property name="removeAbandoned" value="true" />
<property name="removeAbandonedTimeout" value="1800" />
<property name="logAbandoned" value="true" />
<!-- 配置监控统计拦截的filters -->
<property name="filters" value="config,mergeStat,wall,log4j2" />
<property name="connectionProperties" value="config.decrypt=true" />
</bean> <!-- serverA dataSource -->
<bean id="serverADataSource" class="com.alibaba.druid.pool.DruidDataSource"
init-method="init" destroy-method="close">
<property name="url" value="${db.serverA.master.url}" />
<property name="username" value="${db.serverA.master.user}" />
<property name="password" value="${db.serverA.master.password}" />
<property name="driverClassName" value="${db.serverA.master.driver}" />
<property name="initialSize" value="5" />
<property name="maxActive" value="100" />
<property name="minIdle" value="10" />
<property name="maxWait" value="60000" />
<property name="validationQuery" value="SELECT 'x'" />
<property name="testOnBorrow" value="false" />
<property name="testOnReturn" value="false" />
<property name="testWhileIdle" value="true" />
<property name="timeBetweenEvictionRunsMillis" value="600000" />
<property name="minEvictableIdleTimeMillis" value="300000" />
<property name="removeAbandoned" value="true" />
<property name="removeAbandonedTimeout" value="1800" />
<property name="logAbandoned" value="true" />
<!-- 配置监控统计拦截的filters -->
<property name="filters" value="config,mergeStat,wall,log4j2" />
<property name="connectionProperties" value="config.decrypt=true" />
</bean> <!-- serverB dataSource -->
<bean id="serverBDataSource" class="com.alibaba.druid.pool.DruidDataSource"
init-method="init" destroy-method="close">
<property name="url" value="${db.serverB.master.url}" />
<property name="username" value="${db.serverB.master.user}" />
<property name="password" value="${db.serverB.master.password}" />
<property name="driverClassName" value="${db.serverB.master.driver}" />
<property name="initialSize" value="5" />
<property name="maxActive" value="100" />
<property name="minIdle" value="10" />
<property name="maxWait" value="60000" />
<property name="validationQuery" value="SELECT 'x'" />
<property name="testOnBorrow" value="false" />
<property name="testOnReturn" value="false" />
<property name="testWhileIdle" value="true" />
<property name="timeBetweenEvictionRunsMillis" value="600000" />
<property name="minEvictableIdleTimeMillis" value="300000" />
<property name="removeAbandoned" value="true" />
<property name="removeAbandonedTimeout" value="1800" />
<property name="logAbandoned" value="true" />
<!-- 配置监控统计拦截的filters -->
<property name="filters" value="config,mergeStat,wall,log4j2" />
<property name="connectionProperties" value="config.decrypt=true" />
</bean>
我配置了三个数据源:oPDataSource(运营平台本身的数据源),serverADataSource,serverBDataSource。
2、配置multipleDataSource
multipleDataSource相当于以上三个数据源的一个代理,真正与Spring/Mybatis相结合的时multipleDataSource,和单独配置的DataSource使用没有分别:
<!-- Spring整合Mybatis:配置multipleDatasource -->
<bean id="sqlSessionFactory"
class="com.baomidou.mybatisplus.spring.MybatisSqlSessionFactoryBean">
<property name="dataSource" ref="multipleDataSource" />
<!-- 自动扫描Mapping.xml文件 -->
<property name="mapperLocations">
<list>
<value>classpath*:/sqlMapperXml/*.xml</value>
<value>classpath*:/sqlMapperXml/*/*.xml</value>
</list>
</property>
<property name="configLocation" value="classpath:xml/mybatis-config.xml"></property>
<property name="typeAliasesPackage" value="com.XXX.platform.model" />
<property name="globalConfig" ref="globalConfig" />
<property name="plugins">
<array>
<!-- 分页插件配置 -->
<bean id="paginationInterceptor"
class="com.baomidou.mybatisplus.plugins.PaginationInterceptor">
<property name="dialectType" value="mysql" />
<property name="optimizeType" value="aliDruid" />
</bean>
</array>
</property>
</bean> <!-- MyBatis 动态实现 -->
<bean id="mapperScannerConfigurer" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<!-- 对Dao 接口动态实现,需要知道接口在哪 -->
<property name="basePackage" value="com.XXX.platform.mapper" />
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"></property>
</bean>
<!-- MP 全局配置 -->
<bean id="globalConfig" class="com.baomidou.mybatisplus.entity.GlobalConfiguration">
<property name="idType" value="0" />
<property name="dbColumnUnderline" value="true" />
</bean> <!-- 事务管理配置multipleDataSource -->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="multipleDataSource"></property>
</bean>
了解了multipleDataSource所处的位置之后,接下来重点看下multipleDataSource怎么实现,配置文件如下:
<bean id="multipleDataSource" class="com.xxxx.platform.commons.db.MultipleDataSource">
<property name="defaultTargetDataSource" ref="opDataSource" />
<property name="targetDataSources">
<map>
<entry key="opDataSource" value-ref="opDataSource" />
<entry key="serverADataSource" value-ref="serverADataSource" />
<entry key="serverBDataSource" value-ref="serverBDataSource" />
</map>
</property>
</bean>
实现的Java代码如下,不需要过多的解释,很一目了然:
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource; /**
*
* @ClassName: MultipleDataSource
* @Description: 配置多个数据源<br>
* @author: yuzhu.peng
* @date: 2018年1月12日 下午4:37:25
*/
public class MultipleDataSource extends AbstractRoutingDataSource { private static final ThreadLocal<String> dataSourceKey = new InheritableThreadLocal<String>(); public static void setDataSourceKey(String dataSource) {
dataSourceKey.set(dataSource);
} @Override
protected Object determineCurrentLookupKey() {
return dataSourceKey.get();
} public static void removeDataSourceKey() {
dataSourceKey.remove();
}
}
继承自spring的AbstractRoutingDataSource,实现抽象方法determineCurrentLookupKey,这个方法会在每次获得数据库连接Connection的时候之前,决定本次连接的数据源Datasource,可以看下Spring的代码就很清晰了:
/*获取连接*/
public Connection getConnection()
throws SQLException {
return determineTargetDataSource().getConnection();
} protected DataSource determineTargetDataSource() {
Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");
/*此处的determineCurrentLookupKey为抽象接口,获取具体的数据源名称*/
Object lookupKey = determineCurrentLookupKey();
DataSource dataSource = (DataSource)this.resolvedDataSources.get(lookupKey);
if ((dataSource == null) && (((this.lenientFallback) || (lookupKey == null)))) {
dataSource = this.resolvedDefaultDataSource;
}
if (dataSource == null) {
throw new IllegalStateException("Cannot determine target DataSource for lookup key [" + lookupKey + "]");
}
return dataSource;
}
/*抽象接口:也即我们的multipleDataSource实现的接口*/
protected abstract Object determineCurrentLookupKey();
第二步:每次请求(Service方法级别)动态切换数据源
实现思路是利用Spring的AOP思想,拦截每次的Service方法调用,然后根据方法的整体路径名,动态切换multipleDataSource中的数据的key。我们的项目,针对不同服务也即不同数据库的操作,是彼此之间互相独立的,不太建议在同一个service方法中调用不同的数据源,这样的话需要将动态判断是否需要切换的频次(AOP拦截的频次)放在DAO级别,也就是SQL级别。另外,还不方便进行事务管理。
我们来看动态切换数据源的AOP实现:
import java.lang.reflect.Proxy; import org.apache.commons.lang.ClassUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.core.annotation.Order; /**
* 数据源切换AOP
*
* @author yuzhu.peng
* @since 2018-01-15
*/
@Aspect
@Order(1)
public class MultipleDataSourceInterceptor {
/**
* 拦截器对所有的业务实现类请求之前进行数据源切换 特别注意,由于用到了多数据源,Mapper的调用最好只在*ServiceImpl,不然调用到非默认数据源的表时,会报表不存在的异常
*
* @param joinPoint
* @throws Throwable
*/
@Before("execution(* com.xxxx.platform.service..*.*ServiceImpl.*(..))")
public void setDataSoruce(JoinPoint joinPoint)
throws Throwable {
Class<?> clazz = joinPoint.getTarget().getClass();
String className = clazz.getName();
if (ClassUtils.isAssignable(clazz, Proxy.class)) {
className = joinPoint.getSignature().getDeclaringTypeName();
}
// 对类名含有serverA的设置为serverA数据源,否则默认为后台的数据源
if (className.contains(".serverA.")) {
MultipleDataSource.setDataSourceKey(DBConstant.DATA_SOURCE_serverA);
}
else if (className.contains(".serverB.")) {
MultipleDataSource.setDataSourceKey(DBConstant.DATA_SOURCE_serverB);
}
else {
MultipleDataSource.setDataSourceKey(DBConstant.DATA_SOURCE_OP);
}
} /**
* 当操作完成时,释放当前的数据源 如果不释放,频繁点击时会发生数据源冲突,本是另一个数据源的表,结果跑到另外一个数据源去,报表不存在
*
* @param joinPoint
* @throws Throwable
*/
@After("execution(* com.xxxx.service..*.*ServiceImpl.*(..))")
public void removeDataSoruce(JoinPoint joinPoint)
throws Throwable {
MultipleDataSource.removeDataSourceKey();
}
}
拦截所有的ServiceImpl方法,根据方法的全限定名去判断属于那个数据源的功能,然后选择相应的数据源,发放执行完后,释放当前的数据源。注意我用到了Spring的 @Order,注解,接下来会讲到,当定义多个AOP的时候,order是很有用的。
其他:
一开始项目中并没有引入事务,所以一切都OK,每次都能访问到正确的数据源,当加入SPring的事务管理后,不能动态切换数据源了(也好像是事务没有生效,反正是二者没有同时有效),后来发现原因是AOP的执行顺序问题,所以用到了上边提到的SPring的Order:
order越小,先被执行。至此,既可以动态切换数据源,又可以成功用事务(在同一个数据源)。
Spring+Mybatis动态切换数据源的更多相关文章
- 在使用 Spring Boot 和 MyBatis 动态切换数据源时遇到的问题以及解决方法
相关项目地址:https://github.com/helloworlde/SpringBoot-DynamicDataSource 1. org.apache.ibatis.binding.Bind ...
- Spring MVC动态切换数据源(多数据库类型)
最近由于项目需求,需要将Sql Server 和 Mysql 两种数据库整合到一个项目,项目的用到的框架是SSM. 因此尝试了利用AOP切面来切每次执行的Servcie方法,根据Service所在的包 ...
- Spring AOP动态切换数据源
现在稍微复杂一点的项目,一个数据库也可能搞不定,可能还涉及分布式事务什么的,不过由于现在我只是做一个接口集成的项目,所以分布式就先不用了,用Spring AOP来达到切换数据源,查询不同的数据库就可以 ...
- mybatis动态切换数据源
(#)背景:由于业务的需求,导致需要随时切换15个数据源,此时不能low逼的去写十几个mapper,所以想到了实现一个数据源的动态切换 首先要想重写多数据源,那么你应该理解数据源的一个概念是什么,Da ...
- Spring + Mybatis 项目实现动态切换数据源
项目背景:项目开发中数据库使用了读写分离,所有查询语句走从库,除此之外走主库. 最简单的办法其实就是建两个包,把之前数据源那一套配置copy一份,指向另外的包,但是这样扩展很有限,所有采用下面的办法. ...
- springmvc+mybatis多数据源配置,AOP注解动态切换数据源
springmvc与springboot没多大区别,springboot一个jar包配置几乎包含了所有springmvc,也不需要繁琐的xml配置,springmvc需要配置多种jar包,需要繁琐的x ...
- Spring动态切换数据源及事务
前段时间花了几天来解决公司框架ssm上事务问题.如果不动态切换数据源话,直接使用spring的事务配置,是完全没有问题的.由于框架用于各个项目的快速搭建,少去配置各个数据源配置xml文件等.采用了动态 ...
- Spring Boot 如何动态切换数据源
本章是一个完整的 Spring Boot 动态数据源切换示例,例如主数据库使用 lionsea 从数据库 lionsea_slave1.lionsea_slave2.只需要在对应的代码上使用 Data ...
- spring+myBatis 配置多数据源,切换数据源
注:本文来源于 tianzhiwuqis <spring+myBatis 配置多数据源,切换数据源> 一个项目里一般情况下只会使用到一个数据库,但有的需求是要显示其他数据库的内容,像这样 ...
随机推荐
- Object_C 集成环信时,中文环境下不显示中文
因为本app只有中文,没有做中英文判断,但是环信是默认英文环境的,所以,此时,需要在info.plist 文件中修改它的语言环境.如下
- Fundebug前端异常监控插件更新至 1.9.0,支持监控 HTTP 慢请求
摘要: 1.9.0新增 httpTimeout 配置选项,支持监控 HTTP 慢请求,同时修复了记录的 HTTP 响应时间偏小的 BUG. Fundebug提供专业的前端异常监控服务,可以第一时间捕获 ...
- 13.生产环境中的 redis 是怎么部署的?
作者:中华石杉 面试题 生产环境中的 redis 是怎么部署的? 面试官心理分析 看看你了解不了解你们公司的 redis 生产集群的部署架构,如果你不了解,那么确实你就很失职了,你的 redis 是主 ...
- Linux rpm包管理工具
1.什么是rpm包 是一种编译好的二进制软件包,安装速度快. 2.rpm包命名 el6:软件包用于在Red Hat 6.x, CentOS 6.x, and CloudLinux 6.x进行安装 el ...
- URL处理----拼接和编码
ps:浪了好几周,我的锅... 前几天想爬取一个用户网站自动创建每个用户的资料方便注册一些账号,想写一个通用点的爬虫程序爬取只要配置一些爬取规则.爬取深度就ok,避免代码改动,由于时间关系只完成的个半 ...
- 201871010101-陈来弟《面向对象程序设计(Java)》第十一周学习总结
201871010101-陈来弟<面向对象程序设计(Java)>第十一周学习总结 实验九 泛型程序设计技术 实验时间 2019-11-8 第一部分:理论基础知识 1.什么是泛型类 泛型是 ...
- 关于setImageURI out of memory的一些解决办法
http://stackoverflow.com/questions/477572/strange-out-of-memory-issue-while-loading-an-image-to-a-bi ...
- Oracle告Google输了
Oracle告Google输了 boxi • 2016-05-27 • 大公司 Google表示,陪审团的认定代表了Android生态体系.Java开发社区以及依靠开放免费编程语言开发创新消费者产品的 ...
- .Net反射-两种方式获取Enum中的值
public enum EJobType { 客服 = , 业务员 = , 财务 = , 经理 = } Type jobType = typeof(EJobType); 方式1: Array enum ...
- [LeetCode] 145. Binary Tree Postorder Traversal 二叉树的后序遍历
Given a binary tree, return the postorder traversal of its nodes' values. For example: Given binary ...