Springboot+AOP實現多數據源事務處理

本篇是通過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對象全部放入一個棧中,(使用棧是因爲要保障事務管理器出現的順序,而不會因爲順序問題而報錯),然後在方法正常執行完之後提交事務,在捕獲到異常後回滾事務,並拋出事務。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章