接口操作MySQL跟MongoDB事务回滚问题,MySQL可正常回滚,MongoDB无法正常回滚
问题描述
通常使用的事务注解:@Transactional 是不会对MongoDB生效的,但是在一些生产接口中无法避免同时使用MySQL跟MongoDB的操作。
问题处理方式
1、创建 MultiTransactional.java
import java.lang.annotation.*;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MultiTransactional {
String[] value() default {};
}
2、创建 TransactionConfig.java
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.mongodb.MongoDatabaseFactory;
import org.springframework.data.mongodb.MongoTransactionManager;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import javax.sql.DataSource;
@Configuration
public class TransactionConfig {
@Bean(name = "mybatisTransactionManager")
@Primary //事务默认使用mysql数据库
public DataSourceTransactionManager testTransactionManager(@Qualifier("dataSource") DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
@Bean(name = "mongoTransactionManager")
MongoTransactionManager transactionManager(MongoDatabaseFactory factory){
return new MongoTransactionManager(factory);
}
}
3、创建 DbTxBroker.java
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import java.util.concurrent.Callable;
@Component
public class DbTxBroker {
@Transactional(value = "mybatisTransactionManager")
public <V> V inTransactionMybatisTransactionManager(Callable<V> callable) {
try {
return callable.call();
} catch (RuntimeException e) {
throw e;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@Transactional(value = "mongoTransactionManager")
public <V> V inTransactionMongoTransactionManager(Callable<V> callable) {
try {
return callable.call();
} catch (RuntimeException e) {
throw e;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
4、创建 ComboTransaction.java
import com.alibaba.druid.util.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.concurrent.Callable;
import java.util.stream.Stream;
@Component
public class ComboTransaction {
@Autowired
private com.skeqi.plims.config.DbTxBroker dbTxBroker;
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 "mybatisTransactionManager":
return () -> dbTxBroker.inTransactionMybatisTransactionManager(r);
case "mongoTransactionManager":
return () -> dbTxBroker.inTransactionMongoTransactionManager(r);
default:
return null;
}
}, (r1, r2) -> r2);
try {
return combined.call();
} catch (RuntimeException e) {
throw e;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
5、创建 MultiTransactionAop.java
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class MultiTransactionAop {
private final com.skeqi.plims.config.ComboTransaction comboTransaction;
@Autowired
public MultiTransactionAop(com.skeqi.plims.config.ComboTransaction comboTransaction) {
this.comboTransaction = comboTransaction;
}
@Pointcut("@annotation(com.skeqi.plims.config.MultiTransactional)")
public void pointCut() {
}
@Around("pointCut() && @annotation(multiTransactional)")
public Object inMultiTransactions(ProceedingJoinPoint pjp, com.skeqi.plims.config.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());
}
}
6、最后在使用接口上使用 @MultiTransactional 的注解
mybatisTransactionManager跟mongoTransactionManager表示同时启用数据mybatis操作的数据库跟MongoDB同时开启事务同时提交事务
@MultiTransactional(value = {"mybatisTransactionManager", "mongoTransactionManager"})