场景与方案
当一个服务,连接两个数据库,一次接口请求需要向两个数据库同时写入数据,我们使用 @Transactional 注解方式加入事务,此时A数据库写入成功,B数据库写入失败,这时可能数据库A写入的数据不能回滚,原因是 @Transactional 注解中 transactionManager 的参数决定的,只能指定一个事务管理。
A数据源配置代码
java
@Configuration
@MapperScan(basePackages = "com.p.test.testadb.mapper",
annotationClass = Repository.class,
sqlSessionFactoryRef = DataSourceConfig.SQL_SESSION_FACTORY_NAME)
public class DataSourceAConfig {
static final String SQL_SESSION_FACTORY_NAME = "aSessionFactory";
public static final String A_TX_MANAGER = "aTxManager";
@Bean(name = "aDataSource")
@ConfigurationProperties(prefix = "spring.datasource")
@Primary
public DataSource dataSource() {
return DataSourceBuilder.create().build();
}
@Bean(name = A_TX_MANAGER)
@Primary
public PlatformTransactionManager txManager() {
return new DataSourceTransactionManager(dataSource());
}
@Bean(name = SQL_SESSION_FACTORY_NAME)
@Primary
public SqlSessionFactory sqlSessionFactoryBean() throws Exception {
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver()
.getResources("classpath*:mapper-testadb/*.xml"));
sqlSessionFactoryBean.setDataSource(dataSource());
org.apache.ibatis.session.Configuration configuration = new org.apache.ibatis.session.Configuration();
configuration.setCacheEnabled(false);
sqlSessionFactoryBean.setConfiguration(configuration);
return sqlSessionFactoryBean.getObject();
}
}
B数据源配置代码
java
@Configuration
@MapperScan(basePackages = "com.p.test.testbdb.mapper",
annotationClass = Repository.class,
sqlSessionFactoryRef = DataSourceConfig.SQL_SESSION_FACTORY_NAME)
public class DataSourceBConfig {
static final String SQL_SESSION_FACTORY_NAME = "bSessionFactory";
public static final String B_TX_MANAGER = "bTxManager";
@Bean(name = "bDataSource")
@ConfigurationProperties(prefix = "spring.datasource")
@Primary
public DataSource dataSource() {
return DataSourceBuilder.create().build();
}
@Bean(name = B_TX_MANAGER)
@Primary
public PlatformTransactionManager txManager() {
return new DataSourceTransactionManager(dataSource());
}
@Bean(name = SQL_SESSION_FACTORY_NAME)
@Primary
public SqlSessionFactory sqlSessionFactoryBean() throws Exception {
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver()
.getResources("classpath*:mapper-testbdb/*.xml"));
sqlSessionFactoryBean.setDataSource(dataSource());
org.apache.ibatis.session.Configuration configuration = new org.apache.ibatis.session.Configuration();
configuration.setCacheEnabled(false);
sqlSessionFactoryBean.setConfiguration(configuration);
return sqlSessionFactoryBean.getObject();
}
}
最原始的办法
csharp
public interface TestService {
int insertAAndB();
}
less
@Service
public class TestServiceImpl implements TestService {
@Autowired
private Test1Service test1Service;
@Override
@Transactional(DataSourceAConfig.A_TX_MANAGER)
public int insertAAndBUser() {
return test1Service.insert();
}
}
csharp
public interface Test1Service {
int insert();
}
less
@Service
public class Test1ServiceImpl implements Test1Service {
@Override
@Transactional(transactionManager = DataSourceBConfig.B_TX_MANAGER)
public int insert() {
//a写入库
a.insert();
//b写入库
b.insert();
return 1;
}
}
这种方式如果我们一次操作10个数据库,我们就要手写嵌套10层方法,简直是让人头疼
最终解决方案
AOP+自定义注解,下面直接开始撸码
java
/**
* @Author: pp
* @DateTime: 2022/3/29 14:33
* @Description: 定义多事务注解
*/
import java.lang.annotation.*;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MultiTransactional {
String[] value() default {};
}
下面是AOP相关代码,通过AOP技术拦截所有使用了 @MultiTransactional 注解的方法
java
/**
* @Author: pp
* @DateTime: 2022/3/29 14:36
* @Description: AOP拦截MultiTransactional注解方法
*/
@Aspect
@Component
public class MultiTransactionAop {
private final ComboTransaction comboTransaction;
@Autowired
public MultiTransactionAop(ComboTransaction comboTransaction) {
this.comboTransaction = comboTransaction;
}
@Pointcut("@annotation(com.modian.chain.config.datasource.annotation.MultiTransactional)")
public void pointCut() {
}
@Around("pointCut() && @annotation(multiTransactional)")
public Object inMultiTransaction(ProceedingJoinPoint pjp, MultiTransactional multiTransactional) {
return comboTransaction.inCombinedTx(() -> {
try {
return pjp.proceed();
} catch (Throwable throwable) {
if (throwable instanceof RuntimeException) {
throw (RuntimeException) throwable;
}
throw new RuntimeException(throwable);
}
}, multiTransactional.value());
}
}
下面实现一个公共事务方法,这代码很简单,将注解中的参数带过来,循环参数中的事务,判断是哪个事务就执行哪个事务对应的的方法
php
/**
* @Author: pp
* @DateTime: 2022/3/29 14:38
* @Description: 公共事务实现
*/
@Component
public class ComboTransaction {
@Autowired
private ATxBroker aTxBroker;
@Autowired
private BTxBroker bTxBroker;
public <V> V inCombinedTx(Callable<V> callable, String[] transactions) {
if (callable == null) {
return null;
}
Callable<V> combined = Stream
.of(transactions)
.filter(ele -> !StringUtils.isEmpty(ele))
.distinct()
.reduce(callable, (r, tx) -> {
switch (tx) {
case DataSourceBusinessConfig.A_TX_MANAGER:
return () -> aTxBroker.inTransaction(r);
case DataSourceChainConfig.B_TX_MANAGER:
return () -> aTxBroker.inTransaction(r);
default:
return null;
}
}, (r1, r2) -> r2);
try {
return combined.call();
} catch (RuntimeException e) {
throw e;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
ATxBroker和BTxBroker的实现,下面这个代码应该很眼熟吧,和上面我说的很low的实现思想是一样的,只不过这里我们统一封装了而已
php
/**
* @Author: pp
* @DateTime: 2022/3/29 14:39
* @Description: 单个事务实现
*/
@Component
public class ATxBroker {
@Transactional(DataSourceAConfig.A_TX_MANAGER)
public <V> V inTransaction(Callable<V> callable){
try {
return callable.call();
}catch (RuntimeException e){
throw e;
}catch (Exception e){
throw new RuntimeException(e);
}
}
}
/**
* @Author: pp
* @DateTime: 2022/3/29 14:39
* @Description: 单个事务实现
*/
@Component
public class BTxBroker {
@Transactional(DataSourceBConfig.B_TX_MANAGER)
public <V> V inTransaction(Callable<V> callable){
try {
return callable.call();
}catch (RuntimeException e){
throw e;
}catch (Exception e){
throw new RuntimeException(e);
}
}
}
使用方式如下
csharp
@Component
public class ABService{
@MultiTransactional(value = {DataSourceBusinessConfig.A_TX_MANAGER, DataSourceChainConfig.B_TX_MANAGER})
public void add(){
//A库写入数据
a.add();
//B库写入数据
b.add();
}
}
就此优雅的解决了这个问题,从技术上来说没有任何实现难度,核心思想就是我们需要在一个方法上添加两个或者多个 @Transactional(transactionManager = "你需要加的transactionManager")注解,但是原有Spring事务不支持一个方法上添加多个事务注解,我们可以通过AOP方式拦截我们自己定义的注解,然后植入多个方法,将方法上添加对应的@Transactional(transactionManager = "你需要加的transactionManager")注解,实际底层 我们只需要知道 @Transactional 注解的使用方式,知道AOP原理 ,知道Callable 可以接收返回值,并且可以抛异常,知道Stream的reduce方法即可,主要还是设计思想。