1.问题说明
2.思路
有两个数据源,因此就有两个事物管理器,我们可以在需要开启事物的方法内手动控制事物的提交。
使用AOP环绕通知+自定义注解,在执行目标方法之前传入需要用到的两个或者多个事务管理器的名称,在切面内通过名称获取对应管理器的Bean。然后执行目标方法,若发生异常,手动回滚。因此需要重新编写回滚和提交的方法。
3.实现步骤
1.准备工具类:根据名称获取对应的Bean
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
@Component
@Lazy(false)
public class SpringContextUtil implements ApplicationContextAware {
private static final Logger logger = LoggerFactory.getLogger(SpringContextUtil.class);
private static ApplicationContext applicationContext;
public SpringContextUtil() {
}
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
if (SpringContextUtil.applicationContext == null) {
SpringContextUtil.applicationContext = applicationContext;
}
}
public static ApplicationContext getApplicationContext() {
return applicationContext;
}
public static Object getBean(String name) {
return getApplicationContext().getBean(name);
}
public static <T> T getBean(Class<T> clazz) {
return getApplicationContext().getBean(clazz);
}
public static <T> T getBean(String name, Class<T> clazz) {
return getApplicationContext().getBean(name, clazz);
}
}
2.创建自定义注解
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.PARAMETER})
public @interface MoreTransaction {
String[] value() default {};
}
3.配置事物管理器和数据源
import com.alibaba.druid.pool.DruidDataSource;
import lombok.extern.slf4j.Slf4j;
import org.flowable.app.spring.SpringAppEngineConfiguration;
import org.flowable.spring.boot.EngineConfigurationConfigurer;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
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.context.annotation.PropertySource;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import javax.sql.DataSource;
import java.beans.PropertyVetoException;
/**
* 一共有两个配置类,一个是ProcessEngineConfiguration,一个是SpringAppEngineConfiguration
* 他们里面都需要重写configure方法来进行配置
* 配置数据源应该在SpringAppEngineConfiguration中设定
* 前者是配置ProcessEngine的,如自动生成表,设置中文,在yml文件中配置的属性便是在此类中读取
*/
@Configuration
@PropertySource("classpath:application-dev.yml")
@Slf4j
@EnableTransactionManagement
public class FlowableConfig implements EngineConfigurationConfigurer<SpringAppEngineConfiguration> {
//读取配置
@Value("${flow.username}")
private String user;
@Value("${flow.password}")
String password;
@Value("${flow.url}")
String jdbcUrl;
@Value("${flow.driver-class-name}")
String driverClass;
@Value("${flow.initialPoolSize}")
Integer initialPoolSize;
@Value("${flow.maxPoolSize}")
int maxPoolSize;
//配置数据源
@Bean
public DruidDataSource flowDataSource() throws PropertyVetoException {
DruidDataSource dataSource = new DruidDataSource();
dataSource.setUsername(user);
dataSource.setPassword(password);
dataSource.setUrl(jdbcUrl);
dataSource.setDriverClassName(driverClass);
dataSource.setInitialSize(initialPoolSize);
dataSource.setMaxPoolPreparedStatementPerConnectionSize(maxPoolSize);
return dataSource;
}
/**
* @return
* @throws PropertyVetoException
*/
@Bean
@Primary
@ConfigurationProperties(prefix = "spring.datasource.druid")
public DruidDataSource dataSource() throws PropertyVetoException {
return new DruidDataSource();
}
//配置第一个数据源的事务管理器,定义bean名称为 transactionManagerOne
@Primary
@Bean(name = "transactionManagerOne")
public PlatformTransactionManager transactionManagerOne(@Qualifier("dataSource") DataSource dataSourceOne) {
return new DataSourceTransactionManager(dataSourceOne);
}
//flowable 事物管理器
@Bean(name = "transactionManagerTwo")
public PlatformTransactionManager transactionManagerTwo() throws PropertyVetoException {
return new DataSourceTransactionManager(flowDataSource());
}
@Override
public void configure(SpringAppEngineConfiguration engineConfiguration) {
try {
//把数据源设置进来
engineConfiguration.setDataSource(flowDataSource());
//配置对应的事务管理器
engineConfiguration.setTransactionManager(transactionManagerTwo());
log.info("配置flowable数据源成功");
} catch (PropertyVetoException e) {
throw new RuntimeException(e);
}
}
}
我们可以看到,定义了两个数据源,默认数据源需要使用@Primary注解表示默认数据源,flowbale使用的数据源是flowDataSource()方法提供。然后需要对这两个数据源分别指定事务管理器,一个transactionManagerOne,一个transactionManagerTwo,在管理器里面分别指定数据源。
4.定义切面TransactionAspect
import com.igeek.server.annotation.MoreTransaction;
import com.igeek.utils.SpringContextUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.ArrayUtils;
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.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.stereotype.Component;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.DefaultTransactionDefinition;
import java.util.Stack;
@Aspect
@Component
@Slf4j
public class TransactionAspect {
//切入点 返回值 包.类.方法(参数) ..表示参数
//指定加了注解的才拦截
@Pointcut("execution(* com.igeek.server.service.serviceImpl.*.*(..)) && @annotation(com.igeek.server.annotation.MoreTransaction)")
public void moreTransaction() {
}
@Around("moreTransaction()&&@annotation(annotation)")
public Object twoTransaction(ProceedingJoinPoint thisJoinPoint, MoreTransaction annotation) throws Throwable {
//存放事务管理器的栈
Stack<DataSourceTransactionManager> dataSourceTransactionManagerStack = new Stack<>();
//存放事务状态
Stack<TransactionStatus> transactionStatusStack = new Stack<>();
try{
// 检查是否有需要开启事务的事务管理器名字
if(!openTransaction(dataSourceTransactionManagerStack,transactionStatusStack,annotation))
return null;
Object proceed= thisJoinPoint.proceed();
//如果没有异常,说明两个sql都执行成功,两个数据源的sql全部提交事务
commit(dataSourceTransactionManagerStack, transactionStatusStack);
return proceed;
}catch (Throwable e){
//业务代码发生异常,回滚两个数据源的事务
rollback(dataSourceTransactionManagerStack, transactionStatusStack);
log.error(String.format("MultiTransactionalAspect, method:%s-%s occors error:",
thisJoinPoint.getTarget().getClass().getSimpleName(), thisJoinPoint.getSignature().getName()), e);
throw e;
}
}
private boolean openTransaction(Stack<DataSourceTransactionManager> dataSourceTransactionManagerStack, Stack<TransactionStatus> transactionStatusStack, MoreTransaction annotation) {
// 获取需要开启事务的事务管理器名字
String[] transactionMangerNames = annotation.value();
// 检查是否有需要开启事务的事务管理器名字
if (ArrayUtils.isEmpty(annotation.value())) {
return false;
}
// 遍历事务管理器名字数组,逐个开启事务并将事务状态和管理器存入栈中
for (String beanName : transactionMangerNames) {
// 从Spring上下文中获取事务管理器
DataSourceTransactionManager dataSourceTransactionManager =(DataSourceTransactionManager) SpringContextUtil.getBean(beanName);
// 创建新的事务状态
TransactionStatus transactionStatus = dataSourceTransactionManager
.getTransaction(new DefaultTransactionDefinition());
// 将事务状态和事务管理器存入对应的栈中
transactionStatusStack.push(transactionStatus);
dataSourceTransactionManagerStack.push(dataSourceTransactionManager);
}
return true;
}
private void commit(Stack<DataSourceTransactionManager> dataSourceTransactionManagerStack,
Stack<TransactionStatus> transactionStatuStack) {
// 循环,直到事务管理器栈为空
while (!dataSourceTransactionManagerStack.isEmpty()) {
// 从事务管理器栈和事务状态栈中分别弹出当前的事务管理器和事务状态
// 提交当前事务状态
dataSourceTransactionManagerStack.pop()
.commit(transactionStatuStack.pop());
}
}
private void rollback(Stack<DataSourceTransactionManager> dataSourceTransactionManagerStack,
Stack<TransactionStatus> transactionStatuStack) {
// 循环,直到事务管理器栈为空
while (!dataSourceTransactionManagerStack.isEmpty()) {
// 从事务管理器栈和事务状态栈中分别弹出当前的事务管理器和事务状态
// 回滚当前事务状态
dataSourceTransactionManagerStack.pop().rollback(transactionStatuStack.pop());
}
}
}
指定切点后,分别写了四个方法:
-
twoTransaction()为环绕通知。
-
openTransaction()为开启事物,将读取注解的内容,也就是事物管理器的名字,在方法内获取两个或多个事务管理器放到栈中。
-
commit()事物提交方法。
-
rollback()事物回滚方法。
5.使用
在方法上指定事物管理器名称即可。