MybatisPlus多数据源及事务解决思路
关于多数据源解决方案
目前在SpringBoot
框架基础上多数据源的解决方案大多手动创建多个DataSource
,后续方案有三:
- 继承
org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource
,使用AOP
切面注入相应的数据源 ,但是这种做法仅仅适用单Service
方法使用一个数据源可行,如果单Service
方法有多个数据源执行会造成误读。 - 通过
DataSource
配置JdbcTemplate
Bean,直接使用JdbcTemplate
操控数据源。 - 分别通过
DataSource
创建SqlSessionFactory
并扫描相应的Mapper
文件和Mapper
接口。
MybatisPlus
的多数据源
我通过阅读源码,发现MybatisPlus
的多数据源解决方案正是AOP
,继承了org.springframework.jdbc.datasource.AbstractDataSource
,有自己对ThreadLocal
的处理。通过注解切换数据源。也就是说,MybatisPlus
只支持在单Service
方法内操作一个数据源,毕竟官网都指明——“强烈建议只注解在service实现上”。
而后,注意看com.baomidou.dynamic.datasource.toolkit.DynamicDataSourceContextHolder
,也就是MybatisPlus
是如何切换数据源的。
重点看:
/**
* 为什么要用链表存储(准确的是栈)
* <pre>
* 为了支持嵌套切换,如ABC三个service都是不同的数据源
* 其中A的某个业务要调B的方法,B的方法需要调用C的方法。一级一级调用切换,形成了链。
* 传统的只设置当前线程的方式不能满足此业务需求,必须模拟栈,后进先出。
* </pre>
*/
private static final ThreadLocal<Deque<String>> LOOKUP_KEY_HOLDER = new ThreadLocal() {
@Override
protected Object initialValue() {
return new ArrayDeque();
}
};
这段话翻译为大家都能懂得的意思就是“可以同时操控多个数据源”。那么,在MYSQL
中,有语法为schemaName
+.
+tableName
,如此一来就不会误走数据源了。
我继续看MybatisPlus
是如何利用mybatis
本身的ORM
机制将实体类自动映射以及生成SQL
语句的(这里插一句,MybatisPlus
的源码易读懂,写的很不错)。无意看到了注解com.baomidou.mybatisplus.annotation.TableName
中的schema
,如果在类上加schema
,在生成SQL
语句时就会生成schemaName
+.
+tableName
格式。
MybatisPlus
多数据源事务(JTA
)
简单说明一下JTA
JTA
包括事务管理器(Transaction Manager)和一个或多个支持 XA 协议的资源管理器 ( Resource Manager ) 两部分, 可以将资源管理器看做任意类型的持久化数据存储;事务管理器则承担着所有事务参与单元的协调与控制。
JTA
只是提供了一个接口,并没有提供具体的实现。
不过Atomikos
对其进行了实现,而后SpringBoot
将其进行了整合,对其进行了托管,很方便开发者拿来即用。
其中事务管理器的主要部分为UserTransaction
接口,开发人员通过此接口在信息系统中实现分布式事务;而资源管理器则用来规范提供商(如数据库连接提供商)所提供的事务服务,它约定了事务的资源管理功能,使得 JTA
可以在异构事务资源之间执行协同沟通。
通常接入JTA
步骤(目的就是让JTA
的UserTransaction
接管驱动为分布式的数据源,通常为AtomikosDataSourceBean
):
- 配置好
AtomikosDataSourceBean
。 - 把
AtomikosDataSourceBean
交给SqlSessionFactory
。 - 配置
UserTransaction
事务管理。
但是我们用的是
MybatisPlus
,我们需要做的是接管MybatisPlus
每一个数据源的配置,然后再把数据源依次交给MybatisPlus
进行管理。
看看MybatisPlus
是怎么进行多数据源配置的,源码里有这几个地方需要重点看一下:
com.baomidou.dynamic.datasource.provider.AbstractDataSourceProvider
,这个就是MybatisPlus
多数据源配置的方式,利用HashMap
来装载。com.baomidou.dynamic.datasource.DynamicDataSourceCreator
,这个是每个数据源的配置方式。
其中com.baomidou.dynamic.datasource.provider.AbstractDataSourceProvider
实现了接口com.baomidou.dynamic.datasource.provider.DynamicDataSourceProvider
,是该接口的默认的实现。也就是说我们只需要实现该接口,自己配置多数据源以及每个数据源的驱动,成为该接口的默认实现就OK。
实现该接口,配置多数据源:
package xxx.xxx.xxx.config; import com.baomidou.dynamic.datasource.provider.DynamicDataSourceProvider;
import com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DataSourceProperty;
import com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DynamicDataSourceProperties;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Service; import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map; /**
* @author : zuoyu
* @description : 接管MybatisPlus多数据源至Atomikos管理
* @date : 2020-06-01 16:36
**/
@Service
@Primary
public class DynamicDataSourceProviderImpl implements DynamicDataSourceProvider { /**
* 配置文件数据的松散绑定
*/
private final DynamicDataSourceProperties properties; /**
* Atomikos驱动数据源创建
*/
private final AtomikosDataSourceCreator atomikosDataSourceCreator; public DynamicDataSourceProviderImpl(DynamicDataSourceProperties properties, AtomikosDataSourceCreator atomikosDataSourceCreator) {
this.properties = properties;
this.atomikosDataSourceCreator = atomikosDataSourceCreator;
} @Override
public Map<String, DataSource> loadDataSources() {
Map<String, DataSourceProperty> dataSourcePropertiesMap = properties.getDatasource();
Map<String, DataSource> dataSourceMap = new HashMap<>(dataSourcePropertiesMap.size() * 2);
for (Map.Entry<String, DataSourceProperty> item : dataSourcePropertiesMap.entrySet()) {
String pollName = item.getKey();
DataSourceProperty dataSourceProperty = item.getValue();
dataSourceProperty.setPollName(pollName);
dataSourceMap.put(pollName, atomikosDataSourceCreator.createDataSource(dataSourceProperty));
}
return dataSourceMap;
}
}Atomikos
驱动数据源创建:package xxx.xxx.xxx.config; import com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DataSourceProperty;
import com.mysql.jdbc.jdbc2.optional.MysqlXADataSource;
import org.springframework.boot.jta.atomikos.AtomikosDataSourceBean;
import org.springframework.stereotype.Component; import javax.sql.DataSource; /**
* @author : zuoyu
* @description : 事务数据源
* @date : 2020-06-01 17:30
**/
@Component
public class AtomikosDataSourceCreator {
/**
* 创建数据源
*
* @param dataSourceProperty 数据源信息
* @return 数据源
*/
public DataSource createDataSource(DataSourceProperty dataSourceProperty) {
MysqlXADataSource mysqlXaDataSource = new MysqlXADataSource();
mysqlXaDataSource.setUrl(dataSourceProperty.getUrl());
mysqlXaDataSource.setPassword(dataSourceProperty.getPassword());
mysqlXaDataSource.setUser(dataSourceProperty.getUsername());
AtomikosDataSourceBean xaDataSource = new AtomikosDataSourceBean();
xaDataSource.setXaDataSource(mysqlXaDataSource);
xaDataSource.setMinPoolSize(5);
xaDataSource.setBorrowConnectionTimeout(60);
xaDataSource.setMaxPoolSize(20);
xaDataSource.setXaDataSourceClassName(dataSourceProperty.getDriverClassName());
xaDataSource.setTestQuery("SELECT 1 FROM DUAL");
xaDataSource.setUniqueResourceName(dataSourceProperty.getPollName());
return xaDataSource;
}
}配置
JTA
事务管理器:package xxx.xxx.xxx.config; import com.atomikos.icatch.jta.UserTransactionImp;
import com.atomikos.icatch.jta.UserTransactionManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import org.springframework.transaction.jta.JtaTransactionManager; import javax.transaction.TransactionManager;
import javax.transaction.UserTransaction; /**
* @author : zuoyu
* @description : 分布式事务配置
* @date : 2020-06-01 17:55
**/
@Configuration
@EnableTransactionManagement
public class TransactionManagerConfig { @Bean(name = "userTransaction")
public UserTransaction userTransaction() throws Throwable {
UserTransactionImp userTransactionImp = new UserTransactionImp();
userTransactionImp.setTransactionTimeout(10000);
return userTransactionImp;
} @Bean(name = "atomikosTransactionManager")
public TransactionManager atomikosTransactionManager() throws Throwable {
UserTransactionManager userTransactionManager = new UserTransactionManager();
userTransactionManager.setForceShutdown(false);
return userTransactionManager;
} @Bean(name = "transactionManager")
@DependsOn({"userTransaction", "atomikosTransactionManager"})
public PlatformTransactionManager transactionManager() throws Throwable {
return new JtaTransactionManager(userTransaction(), atomikosTransactionManager());
}
}
如此,即可
这样一来便可解决MybatisPlus
多数据源的误走,且支持多数据源下的事务问题。
做任何事情,重要的是思路,而不是搬砖。
本文首发于我的个人博客左羽(一杯茶)
MybatisPlus多数据源及事务解决思路的更多相关文章
- 【spring boot】SpringBoot初学(7)– 多数据源及其事务
前言 github: https://github.com/vergilyn/SpringBootDemo 代码位置: 参考: Spring Boot Reference Guide , §77.2 ...
- MySQL在并发场景下的问题及解决思路
目录 1.背景 2.表锁导致的慢查询的问题 3.线上修改表结构有哪些风险? 4.一个死锁问题的分析 5.锁等待问题的分析 6.小结 1.背景 对于数据库系统来说在多用户并发条件下提高并发性的同时又要保 ...
- C#不用union,而是有更好的方式实现 .net自定义错误页面实现 .net自定义错误页面实现升级篇 .net捕捉全局未处理异常的3种方式 一款很不错的FLASH时种插件 关于c#中委托使用小结 WEB网站常见受攻击方式及解决办法 判断URL是否存在 提升高并发量服务器性能解决思路
C#不用union,而是有更好的方式实现 用过C/C++的人都知道有个union,特别好用,似乎char数组到short,int,float等的转换无所不能,也确实是能,并且用起来十分方便.那C# ...
- chosen.jquery.js 搜索框只能从头匹配的解决思路+方法
chosen.jquery.js 搜索框只能从头匹配的解决思路+方法 心急者请直接看下方 总结 ,由于本问题未能找到直接答案,所以只能通过修改源码解决.故将修改源码思路贴出来供大家参考,在遇到其他改源 ...
- springboot+atomikos+多数据源管理事务(mysql 8.0)
jta:Java Transaction API,即是java中对事务处理的api 即 api即是接口的意思 atomikos:Atomikos TransactionsEssentials 是一个为 ...
- Spring 下,关于动态数据源的事务问题的探讨
开心一刻 毒蛇和蟒蛇在讨论谁的捕猎方式最高效. 毒蛇:我只需要咬对方一口,一段时间内它就会逐渐丧失行动能力,最后死亡. 蟒蛇冷笑:那还得等生效时间,我只需要缠住对方,就能立刻致它于死地. 毒蛇大怒:你 ...
- C# 高并发、抢单解决思路
高并发 高并发(High Concurrency)是互联网分布式系统架构设计中必须考虑的因素之一,它通常是指,通过设计保证系统能够同时并行处理很多请求.高并发相关常用的一些指标有响应时间(Respon ...
- 关于Spring Boot 多数据源的事务管理
自己的一些理解:自从用了Spring Boot 以来,这近乎零配置和"约定大于配置"的设计范式用着确实爽,其实对零配置的理解是:应该说可以是零配置可以跑一个简单的项目,因为Spri ...
- Jquery UI - DatePicker 在Dialog中无法自动隐藏的解决思路
通过Jquery UI Dialog模态展示如下的一个员工编辑页面,但是遇到一个奇怪的问题:点击Start Date的input元素后,其无法失去焦点.从而导致DatePicker控件在选择日期后无法 ...
随机推荐
- django APIview使用
1.APIview使用 ModelVIewSet 是对 APIView 封装 ModelSerializer 是对 Serializer 1.1 在 user/urls.py 中添加路由 urlpat ...
- OGG报错:Cannot load ICU resource bundle 'ggMessage', error code 2 - No such file or directory
[oracle@dgdb1 ~]$ ggsci Oracle GoldenGate Command Interpreter for OracleVersion 11.2.1.0.3 14400833 ...
- Typora+图床详解(小白都能学得会)
Typora+图床详解(小白都能学得会) 1 了解工作 博客中用的笔记软件--Typora(Markdown语法) 博客中用的图床--阿里云对象存储(Object Storage Service,简称 ...
- SpringCloud 源码系列(4)—— 负载均衡 Ribbon
一.负载均衡 1.RestTemplate 在研究 eureka 源码上篇中,我们在 demo-consumer 消费者服务中定义了用 @LoadBalanced 标记的 RestTemplate,然 ...
- 深入解析 C# 的 String.Create 的方法
作者:Casey McQuillan 译者:精致码农 原文:http://dwz.win/YVW 说明:原文比较长,翻译时精简了很多内容,对于不重要的细枝末节只用了一句话概括,但不并影响阅读. 你还记 ...
- 移动端 rem和flexible
一.rem布局 rem是相对于根元素的字体大小单位. 假设html的字体大小为16px,那么1rem = 16px; 一旦根元素html定义的font-size变化,整个页面中运用到的rem都会随之变 ...
- 2020 史上最全IDEA插件总结
最喜欢的一句话: 1.01的365次方=37.78343433289 >>>1 0.99的365次方= 0.02551796445229, 每天进步一点点的目标,贵在坚持 IDEA ...
- 用python写图片格式批量处理工具
一.思路分析 其实,照片处理要求很简单,主要是两个方面:一个是调整图片尺寸(即宽x高),另一个是调整图片的大小(即压缩).为了实现这两个功能,利用python中的PIL库即可,其安装方法如下: pip ...
- Python 设计模式——单例模式
单例模式即确保类有且只有一个特定类型的对象,并提供全局访问点.因此通常用于日志记录.数据库操作.打印机后台处理程序等.这些程序在运行过程中只生成一个实例,避免对同一资源产生相互冲突的请求. 特点: 确 ...
- Swift3.0学习之Button
1.根据字符串的长度确定Button的长度(button的高度给定) let hight:CGFloat = 30.0 let size1 = CGSize(width: getLabWidth(la ...