javascript
Spring事务的处理流程、传播属性、及部分释疑
目錄
文章目錄
- 一、spring事務利用AOP的攔截和處理流程
- 必須記住的兩個要點
- 二、事務傳播屬性
- 三、為什么很多Exception異常必須配置在rollback-for中才有用
- 四、事務的傳播性在同一個類中方法互調時為什么會失效?
spring或是AOP參考: spring概要
一、spring事務利用AOP的攔截和處理流程
如果知道了事務的處理流程,去理解事務傳播屬性導致的回滾就是分分鐘的事兒了。
想要知道事務的攔截處理流程,只需要分析TransactionAspectSupport類的部分源碼即可
// 事務攔截處理方法 protected Object invokeWithinTransaction(Method method, Class<?> targetClass, final TransactionAspectSupport.InvocationCallback invocation) throws Throwable {final TransactionAttribute txAttr = this.getTransactionAttributeSource().getTransactionAttribute(method, targetClass);final PlatformTransactionManager tm = this.determineTransactionManager(txAttr);final String joinpointIdentification = this.methodIdentification(method, targetClass);// 以下代碼只是部分代碼,為了方便看只截取了能說明問題的部分// 1.獲取事務TransactionAspectSupport.TransactionInfo txInfo = this.createTransactionIfNecessary(tm, txAttr, joinpointIdentification);Object retVal = null;try {// 2.處理真正調用的方法retVal = invocation.proceedWithInvocation();} catch (Throwable var15) {// 3.有異常回滾:this.completeTransactionAfterThrowing(txInfo, var15);throw var15;} finally {this.cleanupTransactionInfo(txInfo);}// 4.處理方法完成,提交事務this.commitTransactionAfterReturning(txInfo);return retVal;}從上面注釋可以看到,任何一個被事務攔截的方法,都是先在真正調用該方法之前獲取了事務,執行完該方法后再決定是事務回滾或提交。
我們還可以看到,調用處理真正要執行的方法是被try catch的,而catch塊里才會有事務回滾的代碼。所以,如果某個事務方法A內部有異常沒有拋出來(被自己的try cathch塊捕獲了),而不能被這里的try catch塊捕獲到,那么這個方法A就不會被回滾。
不會回滾的例子:
將會回滾的例子:
@Transactional(propagation = Propagation.REQUIRED,rollbackFor = Exception.class)@Overridepublic void saveTx() { Log log = new Log();log.setTitle("saveTx");log.setBeginDate(new Date());logService.save(log);int i=0;int j=2/i; }在我們寫代碼的時候,有時候會考慮某個方法是否應該被回滾,那么我們就得注意方法里的異常是否該被try catch是非常重要的。
還有一點,如果一個事務方法調用了一個非事務方法,那么非事務方法就要看作是事務方法的一部分,它們共用的一個數據庫連接。
必須記住的兩個要點
二、事務傳播屬性
表格中只要說到加入父事務后,就為同一個事務,他們會共用一個Connection
| 傳播屬性 | 說明 |
| required | 如果當前存在事務則加入該事務,如果沒有則新建一個事務 |
| supports | 如果當前存在事務則加入該事務,如果沒有則以非事務的方式運行。 |
| mandatory | 強制加入當前的事務。如果當前沒有事務,就拋出異常。 |
| requires_new | 新建事務。如果當前存在事務,把當前事務掛起。 |
| not_supported | 不支持事務。如果當前存在事務,就把當前事務掛起,然后以非事務方式執行。 |
| never | 以非事務方式執行,如果當前存在事務,則拋出異常。 |
| nested | 如果當前存在事務,則嵌套執行(相當于指定了回滾點SavePoint)。如果當前沒有事務,則新建事務。 |
三、為什么很多Exception異常必須配置在rollback-for中才有用
經歷過的都知道,Exception異常如果不在rollback-for屬性當中指定,即使出現了Exception異常也不會發生事務回滾。
這是因為spring事務處理時,只對RuntimeException和Error異常進行了處理,而Exception沒有在其中。
我們看看TransactionAspectSupport類回滾方法里的代碼:下面代碼只看注釋就行了
protected void completeTransactionAfterThrowing(TransactionAspectSupport.TransactionInfo txInfo, Throwable ex) {if (txInfo != null && txInfo.hasTransaction()) {if (this.logger.isTraceEnabled()) {this.logger.trace("Completing transaction for [" + txInfo.getJoinpointIdentification() + "] after exception: " + ex);}// 判斷是否是要回滾的異常:rollbackOn方法代碼在后面if (txInfo.transactionAttribute.rollbackOn(ex)) {try {// 回滾txInfo.getTransactionManager().rollback(txInfo.getTransactionStatus());} catch (TransactionSystemException var7) {this.logger.error("Application exception overridden by rollback exception", ex);var7.initApplicationException(ex);throw var7;} catch (RuntimeException var8) {this.logger.error("Application exception overridden by rollback exception", ex);throw var8;} catch (Error var9) {this.logger.error("Application exception overridden by rollback error", ex);throw var9;}// 不在指定異常范圍內} else {try {// 事務提交 txInfo.getTransactionManager().commit(txInfo.getTransactionStatus());} catch (TransactionSystemException var4) {this.logger.error("Application exception overridden by commit exception", ex);var4.initApplicationException(ex);throw var4;} catch (RuntimeException var5) {this.logger.error("Application exception overridden by commit exception", ex);throw var5;} catch (Error var6) {this.logger.error("Application exception overridden by commit error", ex);throw var6;}}}}異常判斷方法:
// RuleBasedTransactionAttribute類的方法 public boolean rollbackOn(Throwable ex) {if (logger.isTraceEnabled()) {logger.trace("Applying rules to determine whether transaction should rollback on " + ex);}RollbackRuleAttribute winner = null;int deepest = 2147483647;if (this.rollbackRules != null) {Iterator var4 = this.rollbackRules.iterator();// 遍歷rollback規則while(var4.hasNext()) {RollbackRuleAttribute rule = (RollbackRuleAttribute)var4.next();// 查找當前異常是否在規則里面int depth = rule.getDepth(ex);if (depth >= 0 && depth < deepest) {deepest = depth;winner = rule;}}}if (logger.isTraceEnabled()) {logger.trace("Winning rollback rule is: " + winner);}// 該異常沒在rollback規則里,則再去判斷是否是RuntimeException或Errorif (winner == null) {logger.trace("No relevant rollback rule found: applying default rules");// 這里調用的方法參考return super.rollbackOn(ex);} else {// rollback規則里有此異常就返true,即要回滾return !(winner instanceof NoRollbackRuleAttribute);}}代碼片段3:
// 如果是RuntimeException或Error異常的子類就返回truepublic boolean rollbackOn(Throwable ex) {// 只指定了這兩個異常,沒有Exception異常return ex instanceof RuntimeException || ex instanceof Error;}rollback-for屬性只針對Error和RuntimeException
我在網上找了一個特別合適的類圖來說明:
四、事務的傳播性在同一個類中方法互調時為什么會失效?
我們知道,spring的事務都是通過AOP動態代理實現的。如果想要任何一個方法實現事務代理,就必須通過事務代理類去調用,而內部方法調用與事務代理類一點我關系都木有,所以不會生效。下面我舉個例子
要代理的接口類:
public interface Subject {void methodOne();void methodTwo(); }要代理的真實類:
public class RealSubject implements Subject {@Overridepublic void methodOne() {System.out.println("one");this.menthoTwo();}@Overridepublic void methodTwo() {System.out.println("two");} }代理類要調用的事務處理器(代理類就是通過它對方法實現的額外處理):
public class MyInvocationHandler implements InvocationHandler {private Object obj;public MyInvocationHandler(){}public MyInvocationHandler(Object obj){this.obj = obj;}// 調用方法前的額外處理:記錄方法調用開始時間public void startRecordRequestTime(){System.out.println("方法調用開始時間:"+System.currentTimeMillis());}// 調用方法前的額外處理:模擬處理事務的傳播屬性public void processTransaction(){System.out.println("事務處理..........");}// 調用方法后的額外處理:記錄方法調用結束時間public void endRecordRequestTime(){System.out.println("方法調用結束時間:"+System.currentTimeMillis());}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {this.startRecordRequestTime();// 調用處理事務傳播屬性方法this.processTransaction();// 通過反射執行真實的方法method.invoke(obj,args);this.endRecordRequestTime();return null;} }使用代理訪問:
public class Test { public static void main(String[] args) {Subject sub = new RealSubject();MyInvocationHandler handler = new MyInvocationHandler(sub);// 利用反射動態生成的代理類Subject proxy=(Subject) Proxy.newProxyInstance(Subject.class.getClassLoader(),new Class[]{Subject.class},handler);// 通過代理類訪問方法proxy.methodOne();} }//運行結果為: 方法調用開始時間:1531564567403 事務處理.......... one two 方法調用結束時間:1531564567403 .好了,從執行了proxy.methodOne();這句代碼后的結果能看到,當代理類調用了methodOne()后,methodTwo()是在內部調用的,與代理類一點兒關系也沒有,所以methodOne調用內部的methodTwo時,是不會走事務處理代碼的,所以事務傳播屬性也就會失效。
上面動態生成的代理類大概如下,有興趣的可以了解下,重點是下面的重寫方法調用的MyInvocationHandler 類的invoke方法:
總結
以上是生活随笔為你收集整理的Spring事务的处理流程、传播属性、及部分释疑的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Java单例模式双重检查锁定中volat
- 下一篇: 什么样的编程姿势才没有bug