本篇是通過aop實現多數據源事務的處理,多數據源事務網上頁有很多例子,但大多數是運用分佈式事務實現的,對於一站式應用總感覺怪怪的,於是總結了一下通過aop實現多數據源事務的例子。
一、引入aop依賴
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
二、添加兩個註解,事務和事務管理
1、事務註解 TransactionAnno,用於標註使用事務
import java.lang.annotation.*;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface TransactionAnno {
}
2、事務管理註解 TransactionManagerAnno,用於對多個事務管理對象進行管理
import org.springframework.context.annotation.Bean;
import java.lang.annotation.*;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Bean
public @interface TransactionManagerAnno {
}
三、核心:定義切點和切面
import com.boot.framework.common.config.ApplicationContextHolder;
import com.boot.framework.common.utils.FileUtil;
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.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Set;
import java.util.Stack;
/**
* Created by wxp on 2019/8/28.
*/
@Aspect
@Component
public class TransactionOperateAspect {
private Set<String> multiTransactional=new HashSet<>();
@Pointcut("@annotation(com.boot.framework.common.config.aop.TransactionAnno)")
public void transactionAnno(){}
@Pointcut("@annotation(com.boot.framework.common.config.aop.TransactionManagerAnno)")
public void TransactionManagerAnno(){}
@Around(value = "transactionAnno()")
public Object manageTransaction(ProceedingJoinPoint pjp) throws Throwable{
Object result = null;
Stack<DataSourceTransactionManager> dataSourceTransactionManagers = new Stack<>();
Stack<TransactionStatus> transactionStatuses = new Stack<>();
try {
if (!openTransaction(dataSourceTransactionManagers, transactionStatuses))
return result;
result = pjp.proceed();
commit(dataSourceTransactionManagers, transactionStatuses);
}catch (Throwable e){
rollback(dataSourceTransactionManagers, transactionStatuses);
throw e;
}
return result;
}
/**
* 開啓事務
* @param dataSourceTransactionManagers
* @param transactionStatuses
* @return
*/
private boolean openTransaction(Stack<DataSourceTransactionManager> dataSourceTransactionManagers,
Stack<TransactionStatus> transactionStatuses){
if (multiTransactional.size() == 0){
//獲取所有數據源文件
ArrayList<Class<?>> classList = FileUtil.getClasses("com.boot.framework.common.config.datasource");
for (Class<?> object: classList){
//獲取類中所有方法
Method[] methods = object.getDeclaredMethods();
for (Method method : methods){
//方法上吐過存在TransactionManagerAnno註解則將方法添加進multiTransactional對象中
TransactionManagerAnno managerAnno = method.getAnnotation(TransactionManagerAnno.class);
if (managerAnno != null)//
multiTransactional.add(method.getName());
}
}
//無數據源事物
if (multiTransactional.size() == 0)
return false;
}
//將列表中的事務入棧
for (String transactionManagerName : multiTransactional){
DataSourceTransactionManager dataSourceTransactionManager = ApplicationContextHolder.getBean(transactionManagerName);
TransactionStatus status = dataSourceTransactionManager.getTransaction(new DefaultTransactionDefinition());
transactionStatuses.push(status);
dataSourceTransactionManagers.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());
}
}
}
四、bean操作方法,在切面上使用到
import org.apache.commons.lang3.Validate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Service;
@Service
public class ApplicationContextHolder implements ApplicationContextAware, DisposableBean {
private static ApplicationContext applicationContext = null;
private static Logger logger = LoggerFactory.getLogger(ApplicationContextHolder.class);
// private static final Logger logger = LoggerFactory.getLogger(SpringContextHolder.class);
/**
* 取得存儲在靜態變量中的ApplicationContext.
*/
public static ApplicationContext getApplicationContext() {
assertContextInjected();
return applicationContext;
}
/**
* 從靜態變量applicationContext中取得Bean, 自動轉型爲所賦值對象的類型.
*/
@SuppressWarnings("unchecked")
public static <T> T getBean(String name) {
assertContextInjected();
return (T) applicationContext.getBean(name);
}
/**
* 從靜態變量applicationContext中取得Bean, 自動轉型爲所賦值對象的類型.
*/
public static <T> T getBean(Class<T> requiredType) {
assertContextInjected();
return applicationContext.getBean(requiredType);
}
/**
* 清除SpringContextHolder中的ApplicationContext爲Null.
*/
public static void clearHolder() {
logger.debug("清除SpringContextHolder中的ApplicationContext:"
+ applicationContext);
applicationContext = null;
}
/**
* 實現ApplicationContextAware接口, 注入Context到靜態變量中.
*/
public void setApplicationContext(ApplicationContext applicationContext) {
// logger.debug("注入ApplicationContext到SpringContextHolder:{}", applicationContext);
if (ApplicationContextHolder.applicationContext != null) {
logger.warn("SpringContextHolder中的ApplicationContext被覆蓋, 原有ApplicationContext爲:" + ApplicationContextHolder.applicationContext);
}
ApplicationContextHolder.applicationContext = applicationContext; // NOSONAR
}
/**
* 實現DisposableBean接口, 在Context關閉時清理靜態變量.
*/
public void destroy() throws Exception {
ApplicationContextHolder.clearHolder();
}
/**
* 檢查ApplicationContext不爲空.
*/
private static void assertContextInjected() {
Validate.validState(applicationContext != null, "applicaitonContext屬性未注入, 請在applicationContext.xml中定義SpringContextHolder.");
}
}
五、使用
1、將數據源上的事務管理由配置bean改爲加上@TransactionManagerAnno,如:
2、在需要事務的方法上加上事務註解@TransactionAnno即可
注意:不支持嵌套事務
六、原理
當執行標註@TransactionAnno註解的方法時,會將所有的數據源事務管理器名字度存入局部變量集類型的multiTransactional,然後在訪問這個方法前通過multiTransactional裏面的所有數據源事務管理器名字取到對應的bean,手動開啓事務後將的TransactionStatus對象全部放入一個棧中,(使用棧是因爲要保障事務管理器出現的順序,而不會因爲順序問題而報錯),然後在方法正常執行完之後提交事務,在捕獲到異常後回滾事務,並拋出事務。