您的位置:首页 > 编程语言 > Java开发

SpringBoot多数据源事务管理

2018-06-28 11:48 295 查看

采用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 {}; } [/code]作用到方法上

@ItsysTransaction(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方法开启并往下执行。




内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: