采用SpringBoot进行后端开发,项目里面配置了多台Mysql数据库,需要涉及场景为新增或修改数据时需要同时写入多台数据库,并保证事务一致,即要么所有数据库都写入成功入库,要么都写入失败回滚;
我们知道,Spring提供了事务管理,有声明式事务和编程式事务,声明式事务我们可以直接用@transactional注解作用于需要事务支持的方法上即可,该注解属性有:
当项目中存在多个数据源时,我们可以通过@Transactional(name="xxxTransactionManager")来指定使用的事务管理器,其实就是使用哪个数据源嘛,但是如果被注解的方法需要同时支持两个事务管理器呢,这个时候如果用@Transactional注解就不合适了,属性name只支持字符串类型,所以只能填一个,如果不传name属性,而且项目没有配置默认的事务管理器,在调用方法时则会抛出未指定默认事务管理器的异常。
这个时候,我们可以用编程式事务来解决这个问题。
首先,事务管理器还是需要配置的。比如我下面就配置了两个Mysql的数据源和事务管理器
import javax.sql.DataSource;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.jdbc.DataSourceBuilder;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
@Configuration
public class DataSourceConfig {
// A库datasource
@Bean
@Primary
@ConfigurationProperties(prefix = "spring.datasource.primary")
public DataSource primaryDataSource() {
return DataSourceBuilder.create().build();
}
// B库datasource
@Bean(name = "secondaryDataSource")
@Qualifier("secondaryDataSource")
@ConfigurationProperties(prefix = "spring.datasource.secondary")
public DataSource secondaryDataSource() {
return DataSourceBuilder.create().build();
}
// A库JdbcTemplate
@Bean(name = "primaryJdbcTemplate")
@Primary
public JdbcTemplate primaryJdbcTemplate(DataSource dataSource) {
return new JdbcTemplate(dataSource);
}
// B库JdbcTemplate
@Bean(name="secondaryJdbcTemplate")
public JdbcTemplate secondaryJdbcTemplate(@Qualifier("secondaryJdbcTemplate") DataSource dataSource){
return new JdbcTemplate(dataSource);
}
// A库事务管理器
@Bean
@Qualifier("primaryTransaction")
public PlatformTransactionManager primaryTransactionManager(@Qualifier("primaryDataSource") DataSource primaryDataSource) {
return new DataSourceTransactionManager(primaryDataSource);
}
// B库事务管理器
@Bean
@Qualifier("secondaryTransaction")
public PlatformTransactionManager secondaryManager(@Qualifier("secondaryDataSource") DataSource secondaryDataSource) {
return new DataSourceTransactionManager(secondaryDataSource);
}
}
配置了事务管理器,那怎么使用呢?
我们可以写一个注解,作用其实和Spring提供的@Transactional一样,只是这个注解接收多个不同是事务管理,然后将这个注解作用到对应的Service方法上。
自定义的注解
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 自定义事务注解
* value为String类型数组,传入为定义的事务管理器
* @author 一iTa
*
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD,ElementType.PARAMETER})
public @interface CustomTransaction {
String[] value() default {};
}
作用到方法上
@CustomTransaction(value = {"primaryTransaction","secondaryTransaction"})
public void insertLine(List<Line> lines){
log.info("xxx pre");
// 各种操作....
xxxDao.insertLine(lines);
log.info("xxx post");
return result;
}
ok,注解现在作用到具体的方法上了,但是现在还是不能生效的,我们需要在Aop里面去通过编程式事务使之生效。
用的是SpringBoot,所以采用注解的方式实现Aop还是比较容易的。
import java.util.Stack;
import org.apache.commons.lang.ArrayUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
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.context.annotation.Configuration;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.DefaultTransactionDefinition;
@Aspect
@Configuration
public class TransactionAop {
final Logger logger = LogManager.getLogger(TransactionAop.class);
@Pointcut("@annotation(com.only.annotation.CustomTransaction)")
public void CustomTransaction() {
}
@Pointcut("execution(* com.only.custom.controller.*.**(..))")
public void excudeController() {
}
@Around(value = "CustomTransaction()&&excudeController()&&@annotation(annotation)")
public Object twiceAsOld(ProceedingJoinPoint thisJoinPoint, CustomTransaction annotation) throws Throwable {
Stack<DataSourceTransactionManager> dataSourceTransactionManagerStack = new Stack<DataSourceTransactionManager>();
Stack<TransactionStatus> transactionStatuStack = new Stack<TransactionStatus>();
try {
if (!openTransaction(dataSourceTransactionManagerStack, transactionStatuStack, annotation)) {
return null;
}
Object ret = thisJoinPoint.proceed();
commit(dataSourceTransactionManagerStack, transactionStatuStack);
return ret;
} catch (Throwable e) {
rollback(dataSourceTransactionManagerStack, transactionStatuStack);
logger.error(String.format("MultiTransactionalAspect, method:%s-%s occors error:",
thisJoinPoint.getTarget().getClass().getSimpleName(), thisJoinPoint.getSignature().getName()), e);
throw e;
}
}
/**
* 开启事务处理方法
*
* @param dataSourceTransactionManagerStack
* @param transactionStatuStack
* @param multiTransactional
* @return
*/
private boolean openTransaction(Stack<DataSourceTransactionManager> dataSourceTransactionManagerStack,
Stack<TransactionStatus> transactionStatuStack, CustomTransaction multiTransactional) {
String[] transactionMangerNames = multiTransactional.value();
if (ArrayUtils.isEmpty(multiTransactional.value())) {
return false;
}
for (String beanName : transactionMangerNames) {
DataSourceTransactionManager dataSourceTransactionManager = (DataSourceTransactionManager) SpringUtil
.getBean(beanName);
TransactionStatus transactionStatus = dataSourceTransactionManager
.getTransaction(new DefaultTransactionDefinition());
transactionStatuStack.push(transactionStatus);
dataSourceTransactionManagerStack.push(dataSourceTransactionManager);
}
return true;
}
/**
* 提交处理方法
*
* @param dataSourceTransactionManagerStack
* @param transactionStatuStack
*/
private void commit(Stack<DataSourceTransactionManager> dataSourceTransactionManagerStack,
Stack<TransactionStatus> transactionStatuStack) {
while (!dataSourceTransactionManagerStack.isEmpty()) {
dataSourceTransactionManagerStack.pop().commit(transactionStatuStack.pop());
}
}
/**
* 回滚处理方法
*
* @param dataSourceTransactionManagerStack
* @param transactionStatuStack
*/
private void rollback(Stack<DataSourceTransactionManager> dataSourceTransactionManagerStack,
Stack<TransactionStatus> transactionStatuStack) {
while (!dataSourceTransactionManagerStack.isEmpty()) {
dataSourceTransactionManagerStack.pop().rollback(transactionStatuStack.pop());
}
}
}
1. 首先根据指定的多个TransactionManager依次开启事务,这个次序不影响,因为其实大家都是平等的
2. 其次就是调用目标方法执行具体的业务逻辑
3. 若是成功返回则提交每个事务,若中途报错,那么就回滚每个事务
这里用了Stack来保存TransactionManager和TransactionStatus主要是为了保证顺序一致,当然也可以使用其他容器类。
到此,整个Aop就配置好了,启动工程,当调用insertLine方法时,事务将由aop的openTransaction方法开启并往下执行。