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多数据源及事务解决思路的文章就介绍到这了,更多相关MybatisPlus多数据源事务内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!