6

MybatisPlus多数据源及事务解决思路

 3 years ago
source link: https://www.zuoyu.top/archives/mybatisplus%E5%A4%9A%E6%95%B0%E6%8D%AE%E6%BA%90%E5%8F%8A%E4%BA%8B%E5%8A%A1%E8%A7%A3%E5%86%B3%E6%80%9D%E8%B7%AF
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

MybatisPlus多数据源及事务解决思路

左羽 2020年06月19日 913次浏览

关于多数据源解决方案

目前在SpringBoot框架基础上多数据源的解决方案大多手动创建多个DataSource,后续方案有三:

  1. 继承org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource,使用AOP切面注入相应的数据源 ,但是这种做法仅仅适用单Service方法使用一个数据源可行,如果单Service方法有多个数据源执行会造成误读。
  2. 通过DataSource配置 JdbcTemplateBean,直接使用 JdbcTemplate操控数据源。
  3. 分别通过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步骤(目的就是让JTAUserTransaction接管驱动为分布式的数据源,通常为AtomikosDataSourceBean):

  1. 配置好AtomikosDataSourceBean
  2. AtomikosDataSourceBean交给SqlSessionFactory
  3. 配置UserTransaction事务管理。

但是我们用的是MybatisPlus,我们需要做的是接管MybatisPlus每一个数据源的配置,然后再把数据源依次交给MybatisPlus进行管理。

看看MybatisPlus是怎么进行多数据源配置的,源码里有这几个地方需要重点看一下:

  1. com.baomidou.dynamic.datasource.provider.AbstractDataSourceProvider,这个就是MybatisPlus多数据源配置的方式,利用HashMap来装载。
  2. 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@Primarypublic 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@Componentpublic 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@EnableTransactionManagementpublic 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多数据源的误走,且支持多数据源下的事务问题。

做任何事情,重要的是思路,而不是搬砖。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK