该文章讲的不是仅mysql的多数据源。
环境:
mysql: 5.1.27
mongodb: 4.2.2
springboot: 2.2.2
在前n天遇到一个业务是用到mysql和mongo。都有写操作,想到急需一个事务管理的东东,上网搜了一遍,发现mysql和mongo是各自独立事务管理的。mongo用的是 MongoTransactionManager(要依赖的等会给),mysql用的是 DataSourceTransactionManager 。
刚开始我选择的一种思路是给两个 TransactionManager @Bean方式配置上。发现 @Transactional 注解方式仅支持一种事务管理(我看了一下代码方式的事务好像也是有冲突,具体冲突等会讲)@Transactional(transactionManager = "transactionManager")。这个仅支持一种事务管理,要从一个叫 AbstractPlatformTransactionManager 的类说起。AbstractPlatformTransactionManager 是所有事务管理的一个抽象类,其作用是给spring可调用组件式的事务管理继承类(如MongoTransactionManager、DataSourceTransactionManager)。
查看 AbstractPlatformTransactionManager 源代码,该类的开始事务流程需要自行去查看。
下面我要说一下与整个事务改造的相关类。
AbstractPlatformTransactionManager 有一个顺序特别要注意,就是子事务管理类操作完doBegin后才会进行 TransactionSynchronizationManager 相关的初始化,该类与事务上下文有着紧密的联系,事务该不该做都会先咨询该类。该类由一堆 ThreadLocal 管理事务的独立,可自行查看源码。
查看 TransactionSynchronizationManager 后得知,一个线程紧允许一个事务管理进行开启事务。由于 MongoTransactionManager 与 DataSourceTransactionManager 都会操作 TransactionSynchronizationManager 导致在各自开启事务时均到该类操作 synchronizations (一个线程对应一个事务“链”,该变量存放的是resourceList)
显然两个事务管理都配置的方式不能满足我的需求
上网查资料重新整理思路,发现了一个新东西,jta+atomikos 分布式事务管理,但网上的案例全是mysql多数据源。
当时抱着不管三七二十一都加一下依赖来看看这东西玩法的心态就试了一下,通过不断阅读源码与理解,最后就搭建出来了。
先添加以下依赖:
implementation 'org.springframework.boot:spring-boot-starter-data-mongodb'
implementation 'org.springframework.boot:spring-boot-starter-jta-atomikos'
implementation 'org.springframework.boot:spring-jdbc'
implementation 'mysql:mysql-connector-java:8.0.11'
看了一下 jta-atomikos 有一堆*Imp的类,一开始就想着估计是给用户自定义的吧。配置的时候也是创建一个叫UserTransactionImp() 的东西,注入给 Jta 的事务管理 JtaTransactionManager 。看了没啥特殊的依赖,直接复制他的源码,创建自己的 UserTransactionImp。
这里要特别注意的是:使用了 jta-atomikos 会直接管理 mysql 的事务,使用的是 atomikos 的 xa 事务。只需要单独处理 mongo 的事务
package com.shero.sport.web.conf;
import com.atomikos.icatch.jta.UserTransactionManager;
import com.shero.comm.constant.TransactionManagerConstant;
import com.shero.sport.service.utils.MongoUtils;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.transaction.TransactionManagerCustomizers;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jta.atomikos.AtomikosDataSourceBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.env.Environment;
import org.springframework.data.mongodb.MongoDbFactory;
import org.springframework.data.mongodb.MongoTransactionManager;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.jta.JtaTransactionManager;
import javax.sql.DataSource;
import javax.transaction.UserTransaction;
import java.util.Properties;
@Configuration
public class TransactionConfiguration {
@Autowired Environment env;
// @Bean(name = TransactionManagerConstant.MONGO_TRANSACTION_MANAGER)
public MongoTransactionManager mongoTransactionManager(MongoDbFactory factory) {
return new MongoTransactionManager(factory);
}
// @Bean
public DataSourceTransactionManager transactionManager(DataSource dataSource,
ObjectProvider transactionManagerCustomizers) {
DataSourceTransactionManager transactionManager = new DataSourceTransactionManager(dataSource);
transactionManagerCustomizers.ifAvailable((customizers) -> customizers.customize(transactionManager));
return transactionManager;
}
@Bean(name = "primaryMysql")
@Primary
@ConfigurationProperties(prefix = "spring.datasource")
public DataSource primaryDataSource() throws Exception {
AtomikosDataSourceBean ds = new AtomikosDataSourceBean();
ds.setXaDataSourceClassName("com.alibaba.druid.pool.xa.DruidXADataSource");
ds.setUniqueResourceName("primaryMysql");
ds.setPoolSize(5);
ds.setXaProperties(build("spring.datasource."));
return ds;
}
@Bean(name = TransactionManagerConstant.TRANSACTION_MANAGER)
@Primary
@Autowired
public JtaTransactionManager transactionManager(MongoDbFactory factory, MongoUtils mongoUtils) {
UserTransactionManager userTransactionManager = new UserTransactionManager();
UserTransaction userTransaction = new JtaTransactionImp(mongoTransactionManager(factory), mongoUtils);
return new JtaTransactionManager(userTransaction, userTransactionManager);
}
private Properties build(String prefix) {
Properties prop = new Properties();
prop.put("url", env.getProperty(prefix + "url"));
prop.put("username", env.getProperty(prefix + "username"));
prop.put("password", env.getProperty(prefix + "password"));
prop.put(&