对于一个方法内,同时操作多个、或者多种类型的数据源时,比如一个MySQL、一个MongoDB,或者两个不同的MySQL,事务管理器只能唯一的场景使用声明式的注解@Transactional 无法保证多个数据源回滚,只能保证单一数据源@Primary回滚,使用编程式的方式去回滚事务,代码侵入太多,使用复杂度高。所以需要自定义注解,实现对多个数据源的管理 二、实现过程 1、自定义注解@MultiDataSourceTransactional
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@documented
public @interface MultiDataSourceTransactional {
@AliasFor("transactionManagers")
String[] values() default {"transactionManager", "mongoTransactionManager"};
@AliasFor("values")
String[] transactionManagers() default {"transactionManager", "mongoTransactionManager"};
}
2、定义切面
@Slf4j
@Aspect
@Configuration
public class MultiDataSourceTransactionalAspect {
private static final ThreadLocal>> THREAD_LOCAL = new ThreadLocal<>();
@Autowired
private ApplicationContext applicationContext;
private DefaultTransactionDefinition def = new DefaultTransactionDefinition();
{
log.info("代码块执行 start");
// 非只读模式
def.setReadOnly(false);
// 事务隔离级别:采用数据库的
def.setIsolationLevel(TransactionDefinition.ISOLATION_DEFAULT);
// 事务传播行为
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
log.info("代码块执行 end");
}
@Pointcut("@annotation(你的注解包名.MultiDataSourceTransactional)")
public void pointcut() {
log.info("切面");
}
@Before("pointcut() && @annotation(transactional)")
public void before(MultiDataSourceTransactional transactional) {
long s = System.currentTimeMillis();
log.info("before start");
// 根据设置的事务名称按顺序声明,并放到ThreadLocal里
String[] transactionManagerNames = transactional.transactionManagers();
Stack> pairStack = new Stack<>();
for (String transactionManagerName : transactionManagerNames) {
PlatformTransactionManager transactionManager;
// TODO 名字定义为枚举
if ("mongoTransactionManager".equals(transactionManagerName)) {
transactionManager = applicationContext.getBean(transactionManagerName, MongoTransactionManager.class);
} else {
transactionManager = applicationContext.getBean(transactionManagerName, DataSourceTransactionManager.class);
}
TransactionStatus transactionStatus = transactionManager.getTransaction(def);
pairStack.push(new Pair(transactionManager, transactionStatus));
}
THREAD_LOCAL.set(pairStack);
long e = System.currentTimeMillis();
log.info("before end 消耗时间:{} ms", (e - s));
}
@AfterReturning("pointcut()")
public void afterReturning() {
long s = System.currentTimeMillis();
// ※栈顶弹出(后进先出)
Stack> pairStack = THREAD_LOCAL.get();
while (!pairStack.empty()) {
Pair pair = pairStack.pop();
pair.getKey().commit(pair.getValue());
}
THREAD_LOCAL.remove();
long e = System.currentTimeMillis();
log.info("afterReturning 消耗时间:{} ms", (e - s));
}
@AfterThrowing(value = "pointcut()")
public void afterThrowing() {
long s = System.currentTimeMillis();
// ※栈顶弹出(后进先出)
Stack> pairStack = THREAD_LOCAL.get();
while (!pairStack.empty()) {
Pair pair = pairStack.pop();
pair.getKey().rollback(pair.getValue());
}
THREAD_LOCAL.remove();
long e = System.currentTimeMillis();
log.info("afterThrowing 消耗时间:{} ms", (e - s));
}
}
三、使用教程
1、纯MySQL
@Transactional(rollbackFor = Exception.class)
2、纯MongoDB
@Transactional(value = "mongoTransactionManager", rollbackFor = Exception.class)
3、MySQL MongoDB混合
@MultiDataSourceTransactional



