<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
使用Spring事務的前提是:物件要被Spring管理,事務方法所在的類要被載入為bean物件
如果事務方法所在的類沒有被載入為一個bean,那麼事務自然就失效了,範例:
//@Service public class UserServiceImpl { @Transactional public void doTest() { // 業務程式碼 } }
以MySQL為例,InnoDB
引擎是支援事務的,而像MyISAM
、MEMORY
等是不支援事務的。
從MySQL5.5.5開始預設的儲存引擎是InnoDB
,之前預設都是MyISAM
。所以在開發過程中發現事務失效,不一定是Spring的鍋,最好確認一下資料庫表是否支援事務。
眾所周知,java的存取許可權修飾符有:private
、default
、protected
、public
四種,
但是@Transactional
註解只能作用於public
修飾的方法上,
在AbstractFallbackTransactionAttributeSource
類(Spring通過這個類獲取@Transactional註解的設定屬性資訊)的computeTransactionAttribute
方法中有個判斷,如果目標方法不是public
,則TransactionAttribute
返回null
,即不支援事務。
@Nullable protected TransactionAttribute computeTransactionAttribute(Method method, @Nullable Class<?> targetClass) { // Don't allow no-public methods as required. if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) { return null; } //……………… }
其實想想動態代理的原理就很好理解了,動態代理是通過實現介面或者繼承來實現的,所以目標方法必須是public修飾,並且不能是final修飾。
如果一個方法不想被子類重寫,那麼我們就可以把他寫成final
修飾的方法
如果事務方法使用final
修飾,那麼aop就無法在代理類中重寫該方法,事務就不會生效
同樣的,static
修飾的方法也無法通過代理變成事務方法
假如在某個Service的方法中,呼叫了另外一個事務方法:
@Service public class UserServiceImpl { @Autowired UserMapper userMapper; public void del(){ doTest(); } @Transactional public void doTest() { userMapper.deleteById(200108); int i = 10/0; //模擬發生異常 } }
像上面的程式碼,doTest
方法使用@Transactional
註解標註,在del()
方法中呼叫了doTest()
方法,在外部呼叫del()
方法時,事務也不會生效,因為這裡del()
方法中呼叫的是類本身的方法,而不是代理物件的方法。
那麼如果確實有這樣的需求怎麼辦呢?
引入自身bean
@Service public class UserServiceImpl { @Autowired UserMapper userMapper; @Autowired UserServiceImpl userServiceImpl; public void del(){ userServiceImpl.doTest(); } @Transactional public void doTest() { userMapper.deleteById(200112); int i = 10/0; //模擬發生異常 } }
通過ApplicationContext
引入bean
@Service public class UserServiceImpl { @Autowired UserMapper userMapper; @Autowired ApplicationContext applicationContext; public void del(){ ((UserServiceImpl)applicationContext.getBean("userServiceImpl")).doTest(); } @Transactional public void doTest() { userMapper.deleteById(200112); int i = 10/0; //模擬發生異常 } }
通過AopContext獲取當前代理類
在啟動類上新增註解@EnableAspectJAutoProxy(exposeProxy = true)
,表示是否對外暴露代理物件,即是否可以獲取AopContext
@Service public class UserServiceImpl { @Autowired UserMapper userMapper; @Autowired ApplicationContext applicationContext; public void del(){ ((UserServiceImpl)AopContext.currentProxy()).doTest(); } @Transactional public void doTest() { userMapper.deleteById(200112); int i = 10/0; //模擬發生異常 } }
如果是SpringBoot
專案,那麼SpringBoot
通過DataSourceTransactionManagerAutoConfiguration
自動設定類幫我們開啟了事務。
如果是傳統的Spring
專案,則需要我們自己設定
<!-- 設定事務管理器--> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"/> </bean> <!-- 設定事務通知--> <tx:advice id="Advice" transaction-manager="transactionManager"> <!-- 設定事務屬性,即哪些方法要執行事務--> <tx:attributes> <tx:method name="insert*" propagation="REQUIRED"/> <!-- 所有insert開頭的方法,以下同理 --> <tx:method name="update*" propagation="REQUIRED"/> <tx:method name="delete*" propagation="REQUIRED"/> </tx:attributes> </tx:advice> <!-- 設定事務切面--> <aop:config> <aop:pointcut id="AdviceAop" expression="execution(* com.yy.service..*(..))"/> <!--要執行的方法在哪個包,意思為:com.yy.service下的所有包裡面的包含任意引數的所有方法--> <aop:advisor advice-ref="Advice" pointcut-ref="AdviceAop"/> <!-- 設定為AdviceAop執行哪個事務通知 --> </aop:config>
這樣在執行service包下的增刪改操作的方法時,就開啟事務了,或者使用註解的方式
<!-- 設定事務管理器--> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"/> </bean> <!-- 註解式事務宣告設定--> <tx:annotation-driven transaction-manager="transactionManager" />
@Service public class UserServiceImpl { @Autowired UserMapper userMapper; @Transactional public void doTest() throws InterruptedException { userMapper.deleteById(200110); new Thread(()->{ userMapper.deleteById(200112); int i = 10/0; //模擬發生異常 }).start(); } }
在事務方法doTest
中,啟動了一個新的執行緒,並在新的執行緒中發生了異常,這樣doTest
是不會回滾的。
因為兩個操作不在一個執行緒中,獲取到的資料庫連線不一樣,從而是兩個不同的事務,所以也不會回滾。
Spring定義了7種傳播行為,我們可以通propagation
屬性來指定傳播行為引數,目前只有REQUIRED
、REQUIRES_NEW
、NESTED
會建立新的事務,其他的則會以非事務的方式執行或者丟擲異常
@Service public class UserServiceImpl { @Autowired UserMapper userMapper; @Transactional(propagation = Propagation.NEVER) public void doTest() throws InterruptedException { userMapper.deleteById(200114); int i = 10/0; //模擬發生異常 } }
如果沒有異常丟擲,則Spring認為程式是正常的,就不會回滾
@Service public class UserServiceImpl { @Autowired UserMapper userMapper; @Transactional public void doTest() { try{ userMapper.deleteById(200115); int i = 10/0; //模擬發生異常 }catch (Exception e){ // 異常操作 } } }
Spring預設只會回滾RuntimeException
和Error
對於普通的Exception
,不會回滾
如果你想觸發其他異常的回滾,需要在註解上設定一下,如:@Transactional(rollbackFor = Exception.class)
@Service public class UserServiceImpl { @Autowired UserMapper userMapper; @Transactional public void doTest() throws Exception { try{ userMapper.deleteById(200116); int i = 10/0; //模擬發生異常 }catch (Exception e){ // 異常操作 throw new Exception(); } } }
rollbackFor 用於指定能夠觸發事務回滾的異常型別,可以指定多個異常型別。
預設是在RuntimeException和Error上回滾。
若異常非設定指定的異常類,則事務失效
@Service public class UserServiceImpl { @Autowired UserMapper userMapper; @Transactional(rollbackFor = NullPointerException.class) public void doTest() throws MyException { userMapper.deleteById(200118); throw new MyException(); } }
即使rollbackFor有預設值,但阿里巴巴開發者規範中,還是要求開發者重新指定該引數。
因為如果使用預設值,一旦程式丟擲了Exception,事務不會回滾,這會出現很大的bug。所以,建議一般情況下,將該引數設定成:Exception或Throwable。
@Service public class UserServiceImpl { @Autowired UserMapper userMapper; @Transactional public void doTest() { userMapper.deleteById(200118); ((UserServiceImpl)AopContext.currentProxy()).test02(); } @Transactional(propagation = Propagation.NESTED) public void test02(){ userMapper.deleteById(200119); int i = 10 / 0; //模擬發生異常 } }
test02()
方法出現了異常,沒有手動捕獲,會繼續往上拋,到外層doTest()
方法的代理方法中捕獲了異常。所以,這種情況是直接回滾了整個事務,不只回滾單個儲存點。
如果只回滾單個儲存點,可以將內部巢狀事務放在try/catch中,類似於上面的自己try…catch…掉異常,並且不繼續往上拋異常。這樣就能保證,如果內部巢狀事務中出現異常,只回滾內部事務,而不影響外部事務。
@Service public class UserServiceImpl { @Autowired UserMapper userMapper; @Transactional public void doTest() { userMapper.deleteById(200118); try{ ((UserServiceImpl)AopContext.currentProxy()).test02(); }catch (Exception e){ } } @Transactional(propagation = Propagation.NESTED) public void test02(){ userMapper.deleteById(200119); int i = 10 / 0; //模擬發生異常 } }
到此這篇關於Spring詳細講解事務失效的場景的文章就介紹到這了,更多相關Spring事務失效內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45