天天看點

@Transactional 自調用失效問題解析一、背景二、Spring注解方式的事務實作機制三、分析四、解決方法

一、背景

”髒髒包“在技術群裡問了一個問題:”大家有在項目中遇到這樣的場景嗎 在一個service層重寫的方法中調用一個私有方法。 service重寫的方法不加事務 私有方法想加入事務 他去調用私有方法時 私有方法需要被事務控制“ 。

這個問題比較典型,面試時也經常被問到,在此簡單整理一下。

二、Spring注解方式的事務實作機制

在應用調用聲明@Transactional 的目标方法時,Spring Framework 預設使用 AOP 代理,在代碼運作時生成一個代理對象,根據@Transactional 的屬性配置資訊,這個代理對象決定該聲明@Transactional 的目标方法是否由攔截器 TransactionInterceptor 來使用攔截,在 TransactionInterceptor 攔截時,會在在目标方法開始執行之前建立并加入事務,并執行目标方法的邏輯, 最後根據執行情況是否出現異常,利用抽象事務管理器(圖 2 有相關介紹)AbstractPlatformTransactionManager 操作資料源 DataSource 送出或復原事務, 如圖 1 所示。
@Transactional 自調用失效問題解析一、背景二、Spring注解方式的事務實作機制三、分析四、解決方法

圖 1. Spring 事務實作機制

Spring AOP 代理有 CglibAopProxy 和 JdkDynamicAopProxy 兩種,圖 1 是以 CglibAopProxy 為例,對于 CglibAopProxy,需要調用其内部類的 DynamicAdvisedInterceptor 的 intercept 方法。

對于 JdkDynamicAopProxy,需要調用其 invoke 方法。

三、分析

3.1 為什麼@Transactional 隻能應用到 public 方法才有效?

3.1.1 從理論角度

理論上@Transactional 注解是為了進行事務增強。

JDK 動态代理,比如需事先接口才行,是以必然是 public的。

AspectJ 動态代理,是基于類的代理,如果該類的方法為私有,那麼子類中就無法重寫和調用。

3.1.2 從源碼角度

這是因為在使用 Spring AOP 代理時,Spring 在調用在的 TransactionInterceptor 在目标方法執行前後進行攔截之前,DynamicAdvisedInterceptor(CglibAopProxy 的内部類)的的 intercept 方法或 JdkDynamicAopProxy 的 invoke 方法會間接調用 AbstractFallbackTransactionAttributeSource(Spring 通過這個類擷取 @Transactional 注解的事務屬性配置屬性資訊)的 computeTransactionAttribute 方法。

其源碼如下:

private TransactionAttribute computeTransactionAttribute(Method method, Class<?> targetClass) {

// 關鍵在這裡

if (this.allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
            return null;
        } else {
            Class<?> userClass = ProxyUtils.getUserClass(targetClass);
            Method specificMethod = ClassUtils.getMostSpecificMethod(method, userClass);
            specificMethod = BridgeMethodResolver.findBridgedMethod(specificMethod);
            TransactionAttribute txAtt = null;
            if (specificMethod != method) {
                txAtt = this.findTransactionAttribute(method);
                if (txAtt != null) {
                    return txAtt;
                }

                txAtt = this.findTransactionAttribute(method.getDeclaringClass());
                if (txAtt != null || !this.enableDefaultTransactions) {
                    return txAtt;
                }
            }

            txAtt = this.findTransactionAttribute(specificMethod);
            if (txAtt != null) {
                return txAtt;
            } else {
                txAtt = this.findTransactionAttribute(specificMethod.getDeclaringClass());
                if (txAtt != null) {
                    return txAtt;
                } else if (!this.enableDefaultTransactions) {
                    return null;
                } else {
                    Method targetClassMethod = this.repositoryInformation.getTargetClassMethod(method);
                    if (targetClassMethod.equals(method)) {
                        return null;
                    } else {
                        txAtt = this.findTransactionAttribute(targetClassMethod);
                        if (txAtt != null) {
                            return txAtt;
                        } else {
                            txAtt = this.findTransactionAttribute(targetClassMethod.getDeclaringClass());
                            return txAtt != null ? txAtt : null;
                        }
                    }
                }
            }
        }
    }           

非公有函數事務屬性資訊傳回null

3.2 為什麼自調用無效?

在 Spring 的 AOP 代理下,隻有目标方法由外部調用,目标方法才由 Spring 生成的代理對象來管理,這會造成自調用問題。

若同一類中的其他沒有@Transactional 注解的方法内部調用有@Transactional 注解的方法,有@Transactional 注解的方法的事務被忽略,不會發生復原。

@Service

public class OrderService {

private void insert() {           

insertOrder();

}

@Transactional

public void insertOrder() {
    //SQL操作
   }           

insertOrder 盡管有@Transactional 注解,但它被内部方法 insert 調用,事務被忽略,出現異常事務不會發生復原。

四、解決方法

4.1 可以使用ApplicatonContextHolder 工具類,從上下文中擷取目前bean,再調用。

4.2 可以使用上下文工具類擷取目前對象的代理類  @EnableAspectJAutoProxy (exposeProxy = true) 然後通過下面方法擷取代理對象,然後再調用

public void insert() {

OrderService proxy = (OrderService) AopContext.currentProxy();
   proxy.insertOrder();
}

@Transactional
public void insertOrder() {
    //SQL操作
   }           

4.3  maven中加入spring-aspects 和 aspectjrt 的依賴以及 aspectj-maven-plugin插件

注:

第二節和第三節部分内容轉載自:

 透徹的掌握 Spring 中@transactional 的使用

創作不易,如果覺得本文對你有幫助,歡迎點贊,歡迎關注我,如果有補充歡迎評論交流,我将努力創作更多更好的文章。