多数据源系统接入mybatis-plus, 实现动态数据源、动态事务。
目录:
- 实现思想
- 导入依赖、配置说明
- 代码实现
- 问题总结
一.实现思想
接手一个旧系统,SpringBoot 使用的是纯粹的 mybatis ,既没有使用规范的代码生成器,也没有使用 JPA 或者 mybatis-plus。
想着接入 mybatis-plus,为以后敲代码省点力气。普通的接入 mybatis-plus 可以直接参考官方文档 https://mp.baomidou.com/ 。
但我接手的系统是个多数据源系统,本来最优的方法是使用官方的 动态数据源 支持 https://mp.baomidou.com/guide/dynamic-datasource.html 。
但我因为乱七八糟的依赖冲突,决定自己实现 动态数据源 的支持。
实现的核心逻辑:使用一个 代理数据源,来管理 其他数据源 的分发请求。(通过AOP分发)
二.导入依赖、配置说明
因为依赖的冲突,我没有直接使用
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.3.1.tmp</version>
</dependency>
而是引入的
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus</artifactId>
<version>3.3.1.tmp</version>
</dependency>
数据库配置文件大致如下
spring:
datasource: #数据库配置
primary: #数据库1
driver-class-name: com.mysql.cj.jdbc.Driver
jdbc-url: jdbc:mysql://
username: root
password: 3
type: com.alibaba.druid.pool.DruidDataSource
second: #数据库2
driver-class-name: com.mysql.cj.jdbc.Driver
jdbc-url: jdbc:mysql://
username: root
password: 3
type: com.alibaba.druid.pool.DruidDataSource
third: #数据库3
driver-class-name: com.mysql.cj.jdbc.Driver
jdbc-url: jdbc:mysql://
username: root
password: 3
type: com.alibaba.druid.pool.DruidDataSource
MybatisPlus 配置大致如下(mybatis 的配置可以删除)
mybatis-plus:
# 扫描 mapper.xml
mapper-locations: classpath:mapper/*.xml #也可以不配置,在代码中设置
# configuration:
# map-underscore-to-camel-case: false
三.代码实现
1.我们先新建 数据源的枚举
public enum DataSourceEnums {
PRIMARY("primaryDataSource"),
SECOND("secondDataSource"),
THIRD("thirdDataSource");
private String value;
DataSourceEnums(String value){this.value=value;}
public String getValue() {
return value;
}
}
2.用来标记数据源的 注解(在哪里使用哪个数据源)。
/**
* @author zhaww
* @date 2020/4/14
* @Description .自定义 - 区分数据源的注解
*/
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyDataSource {
DataSourceEnums value() default DataSourceEnums.PRIMARY;
}
3.动态数据源管理器,继承 AbstractRoutingDataSource
/**
* @author zhaww
* @date 2020/4/10
* @Description .动态数据源管理器
*/public class DataSourceContextHolder extends AbstractRoutingDataSource { private static final ThreadLocal<String> contextHolder = new InheritableThreadLocal<>(); /**
* 重写这个方法,这里返回使用的数据源 key 值
* @return
*/
@Override
protected Object determineCurrentLookupKey() {
// log.info("动态切换数据源:" + DataSourceContextHolder.getDataSource());
return contextHolder.get();
} /**
* 设置数据源
* @param db
*/
public static void setDataSource(String db){
contextHolder.set(db);
} /**
* 取得当前数据源
* @return
*/
public static String getDataSource(){
return contextHolder.get();
} /**
* 清除上下文数据
*/
public static void clear(){
contextHolder.remove();
} }
4. mybatis-plus 的配置类
/**
* @author zhaww
* @date 2020/4/10
* @Description .
*/
//@EnableTransactionManagement //开启事务
@Configuration
@MapperScan(value = {"com.zydd.admin.dao"}) //扫描Mapper 层的类
public class MybatisPlusConfig { @Bean(name = "primaryDataSource")
@ConfigurationProperties(prefix = "spring.datasource.primary")
public DataSource primaryDataSource() {
return DataSourceBuilder.create().build();
} @Bean(name = "secondDataSource")
@ConfigurationProperties(prefix = "spring.datasource.second")
public DataSource secondDataSource() {
return DataSourceBuilder.create().build();
} @Bean(name = "thirdDataSource")
@ConfigurationProperties(prefix = "spring.datasource.third")
public DataSource thirdDataSource() {
return DataSourceBuilder.create().build();
} @Bean(name = "multipleTransactionManager")
@Primary
public DataSourceTransactionManager multipleTransactionManager(@Qualifier("multipleDataSource") DataSource dataSource) {
// return new MyDataSourceTransactionManager(dataSource);
return new DataSourceTransactionManager(dataSource);
} /**
* 动态数据源配置
*
* @return
*/
@Bean(name = "multipleDataSource")
@Primary
public DataSource multipleDataSource(@Qualifier("primaryDataSource") DataSource primaryDataSource,
@Qualifier("secondDataSource") DataSource secondDataSource,
@Qualifier("thirdDataSource") DataSource thirdDataSource) {
DataSourceContextHolder dynamicDataSource = new DataSourceContextHolder();
Map<Object, Object> targetDataSources = new HashMap<>();
targetDataSources.put(DataSourceEnums.PRIMARY.getValue(), primaryDataSource);
targetDataSources.put(DataSourceEnums.SECOND.getValue(), secondDataSource);
targetDataSources.put(DataSourceEnums.THIRD.getValue(), thirdDataSource);
dynamicDataSource.setTargetDataSources(targetDataSources);
dynamicDataSource.setDefaultTargetDataSource(thirdDataSource); // 默认使用的数据源
return dynamicDataSource;
} @Bean("sqlSessionFactory")
public SqlSessionFactory sqlSessionFactory() throws Exception {
MybatisSqlSessionFactoryBean sqlSessionFactory = new MybatisSqlSessionFactoryBean();
sqlSessionFactory.setDataSource(multipleDataSource(primaryDataSource(), secondDataSource(), thirdDataSource()));
//mybatis-plus yml 配置不生效,要在这里代码里配置
MybatisConfiguration configuration = new MybatisConfiguration();
configuration.setJdbcTypeForNull(JdbcType.NULL);
//是否使用转驼峰
configuration.setMapUnderscoreToCamelCase(false);
configuration.setCacheEnabled(false);
sqlSessionFactory.setConfiguration(configuration); //添加分页功能
Interceptor[] plugins = {paginationInterceptor()};
sqlSessionFactory.setPlugins(plugins); //扫描 mapper 路径
ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
Resource[] resource = resolver.getResources("classpath:mapper/**/*.xml");
sqlSessionFactory.setMapperLocations(resource);
return sqlSessionFactory.getObject();
} /**
* @Description : mybatis-plus分页插件
*/
@Bean
public PaginationInterceptor paginationInterceptor() {
PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
// 设置请求的页面大于最大页后操作, true调回到首页,false 继续请求 默认false
paginationInterceptor.setOverflow(true);
// 设置最大单页限制数量,默认 500 条,-1 不受限制
// paginationInterceptor.setLimit(30);
return paginationInterceptor;
} }
5.使用AOP来实现数据源的动态设置。
/**
* @author zhaww
* @date 2020/4/9
* @Description .AOP通用日志记录、动态数据源分发
*/
@Aspect
@Component
@Slf4j
@Order(-100)
public class AOP { /**
* Controller层路径
*/
@Pointcut("within(com.zydd.admin.controller..*)")
public void controllerPointcut() {
} /**
* Service层路径
*/
@Pointcut("within(com.zydd.admin.service..*)")
public void servicePointcut() {
} @Around("servicePointcut()")
public Object doServiceLogging(ProceedingJoinPoint joinPoint) throws Throwable {
changeDataSource(joinPoint); //检查数据源
} /**
* Mapper层拦截,动态切换 mybatisPlus 数据源
*/
@Before("execution(* com.zydd.admin.dao.primary..*(..))")
public void doAdmin(){
log.info("选择数据源---" + DataSourceEnums.PRIMARY.getValue());
DataSourceContextHolder.setDataSource(DataSourceEnums.PRIMARY.getValue());
} /**
* Mapper层拦截,动态切换 mybatisPlus 数据源
*/
@Before("execution(* com.zydd.admin.dao.second..*(..))")
public void doZYDD(){
log.info("选择数据源---" + DataSourceEnums.SECOND.getValue());
DataSourceContextHolder.setDataSource(DataSourceEnums.SECOND.getValue());
} /**
* Mapper层拦截,动态切换 mybatisPlus 数据源
*/
@Before("execution(* com.zydd.admin.dao.third..*(..))")
public void doDW(){
log.info("选择数据源---" + DataSourceEnums.THIRD.getValue());
DataSourceContextHolder.setDataSource(DataSourceEnums.THIRD.getValue());
} /**
* 通过注解 变更数据源
* @param joinPoint
*/
private void changeDataSource(ProceedingJoinPoint joinPoint) {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
MyDataSource myDataSource = null;
//优先判断方法上的注解
if (method.isAnnotationPresent(MyDataSource.class)) {
myDataSource = method.getAnnotation(MyDataSource.class);
DataSourceContextHolder.setDataSource(myDataSource.value().getValue());
} else if (method.getDeclaringClass().isAnnotationPresent(MyDataSource.class)) { //其次判断类上的注解
myDataSource = method.getDeclaringClass().getAnnotation(MyDataSource.class);
DataSourceContextHolder.setDataSource(myDataSource.value().getValue());
}
if (myDataSource != null) {
log.info("注解方式选择数据源---" + myDataSource.value().getValue());
}
}
}
注意:我们不但默认通过 Mapper 的路径来切换数据源,还通过 Service 方法层来切换数据源。
因为如果 service 有事务的话,进入service方法的时候,DataSourceTransactionManager 就设置好了默认数据源,就算通过Mapper层重新设置数据源,
DataSourceTransactionManager 的默认数据源还是没有变。
所以在 事务管理器 设置默认数据源之前,就切换数据源,实现动态事务+动态数据源。
6.实际使用,只要 MyDataSource 注解就ok了。也可以在 ServiceImpl 类上加注解。
@Override
@MyDataSource(DataSourceEnums.THIRD)
@Transactional
public void test() {
DwUserMPEntity test2 = new DwUserMPEntity();
test2.setUuid("test");
test2.setNickname("test");
test2.setPhone("test");
dwUserDao.insert(test2);
// throw new RuntimeException("lala");
} @Override
@MyDataSource(DataSourceEnums.PRIMARY)
@Transactional
public void test1() {
SysRoleMPEntity test = new SysRoleMPEntity();
test.setRole("test");
test.setDescription("test");
sysRoleDao.insert(test);
// throw new RuntimeException("lala");
}
五.问题总结
1.配置文件里 mybatis-plus的配置不生效:因为我们在 SqlSessionFactory 里重新写了 MybatisConfiguration 。
2.启用事务的话,动态数据源不生效:因为 service 有事务的话,在进入service方法时,DataSourceTransactionManager 就设置好了默认数据源。
多数据源系统接入mybatis-plus, 实现动态数据源、动态事务。的更多相关文章
- myBatis源码解析-数据源篇(3)
前言:我们使用mybatis时,关于数据源的配置多使用如c3p0,druid等第三方的数据源.其实mybatis内置了数据源的实现,提供了连接数据库,池的功能.在分析了缓存和日志包的源码后,接下来分析 ...
- Spring Boot + Mybatis 实现动态数据源
动态数据源 在很多具体应用场景的时候,我们需要用到动态数据源的情况,比如多租户的场景,系统登录时需要根据用户信息切换到用户对应的数据库.又比如业务A要访问A数据库,业务B要访问B数据库等,都可以使用动 ...
- Spring Boot:实现MyBatis动态数据源
综合概述 在很多具体应用场景中,我们需要用到动态数据源的情况,比如多租户的场景,系统登录时需要根据用户信息切换到用户对应的数据库.又比如业务A要访问A数据库,业务B要访问B数据库等,都可以使用动态数据 ...
- SpringBoot+Mybatis 实现动态数据源切换方案
背景 最近让我做一个大数据的系统,分析了一下,麻烦的地方就是多数据源切换抽取数据.考虑到可以跨服务器跨数据库抽数,再整理数据,就配置了这个动态数据源的解决方案.在此分享给大家. 实现方案 数据库配置文 ...
- SaaS 系统架构,Spring Boot 动态数据源实现!
这段时候在准备从零开始做一套SaaS系统,之前的经验都是开发单数据库系统并没有接触过SaaS系统,所以接到这个任务的时候也有也些头疼,不过办法部比困难多,难得的机会. 在网上找了很多关于SaaS的资料 ...
- Spring Boot + Mybatis多数据源和动态数据源配置
文章转自 https://blog.csdn.net/neosmith/article/details/61202084 网上的文章基本上都是只有多数据源或只有动态数据源,而最近的项目需要同时使用两种 ...
- SpringBoot集成Mybatis配置动态数据源
很多人在项目里边都会用到多个数据源,下面记录一次SpringBoot集成Mybatis配置多数据源的过程. pom.xml <?xml version="1.0" encod ...
- 使用 mybatis plus 动态数据源
1.pom.xml 增加 <dependency> <groupId>com.baomidou</groupId> <artifactId>dynami ...
- mybatis多数据库切换,(动态数据源)。
项目中将一个库的某些标的某些数据保存到另一个库. 使用spring的aop编程动态切换数据源,代码如下,以备下次用到! 1.先将两个数据库连接,创建两个数据源,交于spring管理! <bean ...
随机推荐
- 2016 Multi-University Training Contest 4 T9
http://acm.hdu.edu.cn/showproblem.php?pid=5772 最大权闭合子图. 得到价值w[i][j]的条件是选了i,j这两个位置的字符.选择位置的i字符花费为 第一次 ...
- 你不一定知道的UrlPrefix路由规则
引言 接上文,容器内web程序一般会绑定到http://0.0.0.0:{某监听端口}或http://+:{某监听端口},以确保使用容器IP可以访问到web应用. 正如我们在ASP.NET Core官 ...
- Java并发编程之set集合的线程安全类你知道吗
Java并发编程之-set集合的线程安全类 Java中set集合怎么保证线程安全,这种方式你知道吗? 在Java中set集合是 本篇是<凯哥(凯哥Java:kagejava)并发编程学习> ...
- [ICRA 2019]Multi-Task Template Matching for Object Detection, Segmentation and Pose Estimation Using Depth Images
简介 本文作者提出新的框架(MTTM),使用模板匹配来完成多个任务,从深度图的模板上找到目标物体,通过比较模板特征图与场景特征图来预测分割mask和模板与检测物体之间的位姿变换.作者提 ...
- 洛谷 P5176 公约数 题解
原题链接 我天哪 大大的庆祝一下: 数论黑题 \(T1\) 达成! 激动地不行 记住套路:乱推 \(\gcd\),欧拉筛模板,然后乱换元,乱换式子,完了整除分块,欧拉筛和前缀和就解决了! \[\sum ...
- 从数据结构分析mysql为何使用B+tree
理解mysql为何选择升级版的二叉树,就需要对各种常用的二叉树进行对比.B+Tree是一种特殊的二叉树,本质上也算二叉树.自然会满足二叉树的一般特性. 比如,比节点数据大的在右边,节点数据小的在左边. ...
- java基于Hash表和双向链表简单实现LRU Cache
package lru; import java.util.HashMap; public class LRUCache2<K,V> { public final int capacity ...
- 决战Leetcode: easy part(51-96)
本博客是个人原创的针对leetcode上的problem的解法,所有solution都基本通过了leetcode的官方Judging,个别未通过的例外情况会在相应部分作特别说明. 欢迎互相交流! em ...
- 构建一个简单的 Google Dialogflow 聊天机器人【上】
概述 本教程将向您展示如何构建一个简单的Dialogflow聊天机器人,引导您完成Dialogflow的最重要功能.您将学习如何: 创建Dialogflow帐户和第一个Dialogflow聊天机器人, ...
- TensorFlow系列专题(九):常用RNN网络结构及依赖优化问题
欢迎大家关注我们的网站和系列教程:http://panchuang.net/ ,学习更多的机器学习.深度学习的知识! 目录: 常用的循环神经网络结构 多层循环神经网络 双向循环神经网络 递归神经网络 ...