1:事務原理

1.2:aop/動態代理

類路徑:org/springframework/aop/framework/CglibAopProxy.java


ReflectiveMethodInvocation#proceed 後續:


1.2:threadLocal


1.3:事務核心程式碼

org.springframework.transaction.interceptor.TransactionAspectSupport#invokeWithinTransaction

    /**
* 每個被 @Transactional 修飾的方法都會走一遍 transaction interceptor,然後新增一個事務節點。
* 每個事務節點執行前都會判斷是否需要新建事務、開啟事務。
**/
@Nullable
protected Object invokeWithinTransaction(Method method, @Nullable Class<?> targetClass,
final InvocationCallback invocation) throws Throwable { // If the transaction attribute is null, the method is non-transactional.
TransactionAttributeSource tas = getTransactionAttributeSource();
final TransactionAttribute txAttr = (tas != null ? tas.getTransactionAttribute(method, targetClass) : null);
final PlatformTransactionManager tm = determineTransactionManager(txAttr);
final String joinpointIdentification = methodIdentification(method, targetClass, txAttr); if (txAttr == null || !(tm instanceof CallbackPreferringPlatformTransactionManager)) {
// 建立一個事務資訊物件,每一次呼叫 @Transactional 註解修飾的方法,都會重新建立一個 TransactionInfo 物件。
// 若有呼叫鏈有多個 @TransactionInfo 修飾的方法,則會形成一個事務鏈。
// 將最新的事務節點資訊通過 threadLocal 更新到當前執行緒 。
TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification); Object retVal;
try {
// 真正執行crud語句的過程
retVal = invocation.proceedWithInvocation();
}
catch (Throwable ex) {
// 拋異常之後決定是否回滾還是繼續提交
completeTransactionAfterThrowing(txInfo, ex);
throw ex;
}
finally {
// 清除當前節點的事務資訊,將舊事務節點資訊通過 threadLocal 更新到當前執行緒。
cleanupTransactionInfo(txInfo);
}
// 事務鏈執行完畢之後
commitTransactionAfterReturning(txInfo);
return retVal;
}else {
...
}
}

org.springframework.transaction.interceptor.TransactionAspectSupport.TransactionInfo

    protected final class TransactionInfo {

        // 事務管理器
@Nullable
private final PlatformTransactionManager transactionManager; @Nullable
private final TransactionAttribute transactionAttribute; // 切點資訊(類路徑#方法名稱)
private final String joinpointIdentification; // 當前事務節點狀態(是否完成、)
@Nullable
private TransactionStatus transactionStatus; // 舊事務節點/前事務節點
@Nullable
private TransactionInfo oldTransactionInfo;
}

1.4:事務鏈-圖

2:事務回滾場景

用兩個Service進行測試:

/**
* 模擬 Service A
**/
@Service
public class AopiService {
private final Logger log = LoggerFactory.getLogger(this.getClass()); @Resource(name = AopiRepositry.PACKAGE_BEAN_NAME)
private AopiRepositry aopiRepositry;
@Resource
private PmsTestService pmsTestService;
@Resource
private AopiService aopiService; ...
} /**
* 模擬 Service B
**/
@Service
public class PmsTestService { @Transactional(rollbackFor = Exception.class)
public void insertWithTransactional() {
int i = 1 / 0;
} public void insertWithoutTransactional() {
int i = 1 / 0;
}
}

模擬場景:

1:模擬ServiceA呼叫ServiceB(會異常,被try-catch),這種情況會回滾嗎?

2:模擬ServiceA中呼叫自己的本類中的方法(會異常,被try-catch),這種情況會回滾嗎?

3:模擬ServiceA注入自己並通過依賴的ServiceA呼叫另一個方法(會異常,被try-catch),這種情況會回滾嗎?


2.1:場景1-1

    /**
* serviceA 加了 @Transactional
* serviceB 加了 @Transactional
* 最終:回滾
**/
@Transactional(rollbackFor = Exception.class)
public void insertA() {
Aopi aopi = new Aopi();
aopi.setName("1");
aopi.setAge(23);
aopiRepositry.insert(aopi);
try {
pmsTestService.insertWithTransactional();
} catch (Exception e) {
log.error("插入報錯", e);
}
// 判斷事務是否回滾
if (TransactionAspectSupport.currentTransactionStatus().isRollbackOnly()) {
log.info("事務回滾了");
} else {
log.info("事務沒回滾");
}
}

2.2:場景1-2

    /**
* serviceA 加了 @Transactional
* serviceB 沒加 @Transactional,但是手動 throw e;
* 最終:回滾
**/
@Transactional(rollbackFor = Exception.class)
public void insertAA() {
Aopi aopi = new Aopi();
aopi.setName("1");
aopi.setAge(23);
aopiRepositry.insert(aopi);
try {
pmsTestService.insertWithoutTransactional();
} catch (Exception e) {
log.error("插入報錯", e);
throw e;
}
}

2.3:場景1-3

    /**
* serviceA 加了 @Transactional
* serviceB 沒加 @Transactional,但是手動 TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
* 最終:回滾
* <p>
* 若不手動 TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(),那麼不會回滾
**/
@Transactional(rollbackFor = Exception.class)
public void insertAAA() {
Aopi aopi = new Aopi();
aopi.setName("1");
aopi.setAge(23);
aopiRepositry.insert(aopi);
try {
pmsTestService.insertWithoutTransactional();
} catch (Exception e) {
log.error("插入報錯", e);
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}
// 判斷事務是否回滾
if (TransactionAspectSupport.currentTransactionStatus().isRollbackOnly()) {
log.info("事務回滾了");
} else {
log.info("事務沒回滾");
}
}

2.4:場景2-1

    /**
* serviceA 加了 @Transactional
* 呼叫過程中被異常被捕獲,並不丟擲
* 最終:不回滾
**/
@Transactional(rollbackFor = Exception.class)
public void insertAAAA() {
Aopi aopi = new Aopi();
aopi.setName("1");
aopi.setAge(23);
aopiRepositry.insert(aopi);
try {
int i = 1 / 0;
} catch (Exception e) {
log.error("插入報錯", e);
}
// 判斷事務是否回滾
if (TransactionAspectSupport.currentTransactionStatus().isRollbackOnly()) {
log.info("事務回滾了");
} else {
log.info("事務沒回滾");
}
}

2.5:場景2-2

    /**
* 本類方法A 加了 @Transactional
* 本類方法B 加了 @Transactional,異常被捕獲,並不丟擲
* 最終:不回滾
* <p>
* 原因:呼叫 insert 並不會重新走代理呼叫(this 物件不是代理物件)
**/
@Transactional(rollbackFor = Exception.class)
public void insertAAAAA() {
Aopi aopi = new Aopi();
aopi.setName("1");
aopi.setAge(23);
aopiRepositry.insert(aopi);
try {
insert();
} catch (Exception e) {
log.error("插入報錯", e);
}
// 判斷事務是否回滾
if (TransactionAspectSupport.currentTransactionStatus().isRollbackOnly()) {
log.info("事務回滾了");
} else {
log.info("事務沒回滾");
}
}

2.6:場景3-1

    /**
* 本類方法A 加了 @Transactional
* 自己注入自己,再呼叫本類方法B,加了 @Transactional,異常被捕獲,並不丟擲
* 最終:回滾
* <p>
* 原因:aopiService bean 是一個代理bean,每次呼叫都會重新走代理呼叫邏輯。
**/
@Transactional(rollbackFor = Exception.class)
public void insertAAAAAA() {
Aopi aopi = new Aopi();
aopi.setName("1");
aopi.setAge(23);
aopiRepositry.insert(aopi);
try {
aopiService.insert();
} catch (Exception e) {
log.error("插入報錯", e);
}
// 判斷事務是否回滾
if (TransactionAspectSupport.currentTransactionStatus().isRollbackOnly()) {
log.info("事務回滾了");
} else {
log.info("事務沒回滾");
}
} @Transactional(rollbackFor = Exception.class)
public void insert() {
int i = 1 / 0;
}

3:結論

1:程式異常,事務是否回滾取決於 當前執行緒的事務狀態。

2:異常是否丟擲並不是並不是一定會導致回滾失敗的原因。即使異常被捕獲,且不再次throw,事務也可能回滾。

3:丟擲的異常不在rollback 範圍內,也不會進行回滾。


其他:

1:spring 用的cglib代理庫是自己的庫(依賴於spring-aop的包),並沒有引用第三方cglib庫。