Spring詳細(xì)講解事務(wù)失效的場(chǎng)景
1)未被Spring管理
使用Spring事務(wù)的前提是:對(duì)象要被Spring管理,事務(wù)方法所在的類(lèi)要被加載為bean對(duì)象
如果事務(wù)方法所在的類(lèi)沒(méi)有被加載為一個(gè)bean,那么事務(wù)自然就失效了,示例:
//@Service public class UserServiceImpl { @Transactional public void doTest() { // 業(yè)務(wù)代碼 } }
2)數(shù)據(jù)庫(kù)引擎不支持事務(wù)
以MySQL為例,InnoDB
引擎是支持事務(wù)的,而像MyISAM
、MEMORY
等是不支持事務(wù)的。
從MySQL5.5.5開(kāi)始默認(rèn)的存儲(chǔ)引擎是InnoDB
,之前默認(rèn)都是MyISAM
。所以在開(kāi)發(fā)過(guò)程中發(fā)現(xiàn)事務(wù)失效,不一定是Spring的鍋,最好確認(rèn)一下數(shù)據(jù)庫(kù)表是否支持事務(wù)。
3)事務(wù)方法沒(méi)有被public修飾
眾所周知,java的訪問(wèn)權(quán)限修飾符有:private
、default
、protected
、public
四種,
但是@Transactional
注解只能作用于public
修飾的方法上,
在AbstractFallbackTransactionAttributeSource
類(lèi)(Spring通過(guò)這個(gè)類(lèi)獲取@Transactional注解的配置屬性信息)的computeTransactionAttribute
方法中有個(gè)判斷,如果目標(biāo)方法不是public
,則TransactionAttribute
返回null
,即不支持事務(wù)。
@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; } //……………… }
其實(shí)想想動(dòng)態(tài)代理的原理就很好理解了,動(dòng)態(tài)代理是通過(guò)實(shí)現(xiàn)接口或者繼承來(lái)實(shí)現(xiàn)的,所以目標(biāo)方法必須是public修飾,并且不能是final修飾。
4)方法使用final修飾
如果一個(gè)方法不想被子類(lèi)重寫(xiě),那么我們就可以把他寫(xiě)成final
修飾的方法
如果事務(wù)方法使用final
修飾,那么aop就無(wú)法在代理類(lèi)中重寫(xiě)該方法,事務(wù)就不會(huì)生效
同樣的,static
修飾的方法也無(wú)法通過(guò)代理變成事務(wù)方法
5)同一類(lèi)中方法調(diào)用
假如在某個(gè)Service的方法中,調(diào)用了另外一個(gè)事務(wù)方法:
@Service public class UserServiceImpl { @Autowired UserMapper userMapper; public void del(){ doTest(); } @Transactional public void doTest() { userMapper.deleteById(200108); int i = 10/0; //模擬發(fā)生異常 } }
像上面的代碼,doTest
方法使用@Transactional
注解標(biāo)注,在del()
方法中調(diào)用了doTest()
方法,在外部調(diào)用del()
方法時(shí),事務(wù)也不會(huì)生效,因?yàn)檫@里del()
方法中調(diào)用的是類(lèi)本身的方法,而不是代理對(duì)象的方法。
那么如果確實(shí)有這樣的需求怎么辦呢?
引入自身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; //模擬發(fā)生異常 } }
通過(guò)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; //模擬發(fā)生異常 } }
通過(guò)AopContext獲取當(dāng)前代理類(lèi)
在啟動(dòng)類(lèi)上添加注解@EnableAspectJAutoProxy(exposeProxy = true)
,表示是否對(duì)外暴露代理對(duì)象,即是否可以獲取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; //模擬發(fā)生異常 } }
6)未開(kāi)啟事務(wù)
如果是SpringBoot
項(xiàng)目,那么SpringBoot
通過(guò)DataSourceTransactionManagerAutoConfiguration
自動(dòng)配置類(lèi)幫我們開(kāi)啟了事務(wù)。
如果是傳統(tǒng)的Spring
項(xiàng)目,則需要我們自己配置
<!-- 配置事務(wù)管理器--> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"/> </bean> <!-- 配置事務(wù)通知--> <tx:advice id="Advice" transaction-manager="transactionManager"> <!-- 配置事務(wù)屬性,即哪些方法要執(zhí)行事務(wù)--> <tx:attributes> <tx:method name="insert*" propagation="REQUIRED"/> <!-- 所有insert開(kāi)頭的方法,以下同理 --> <tx:method name="update*" propagation="REQUIRED"/> <tx:method name="delete*" propagation="REQUIRED"/> </tx:attributes> </tx:advice> <!-- 配置事務(wù)切面--> <aop:config> <aop:pointcut id="AdviceAop" expression="execution(* com.yy.service..*(..))"/> <!--要執(zhí)行的方法在哪個(gè)包,意思為:com.yy.service下的所有包里面的包含任意參數(shù)的所有方法--> <aop:advisor advice-ref="Advice" pointcut-ref="AdviceAop"/> <!-- 配置為AdviceAop執(zhí)行哪個(gè)事務(wù)通知 --> </aop:config>
這樣在執(zhí)行service包下的增刪改操作的方法時(shí),就開(kāi)啟事務(wù)了,或者使用注解的方式
<!-- 配置事務(wù)管理器--> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"/> </bean> <!-- 注解式事務(wù)聲明配置--> <tx:annotation-driven transaction-manager="transactionManager" />
7)多線(xiàn)程調(diào)用
@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; //模擬發(fā)生異常 }).start(); } }
在事務(wù)方法doTest
中,啟動(dòng)了一個(gè)新的線(xiàn)程,并在新的線(xiàn)程中發(fā)生了異常,這樣doTest
是不會(huì)回滾的。
因?yàn)閮蓚€(gè)操作不在一個(gè)線(xiàn)程中,獲取到的數(shù)據(jù)庫(kù)連接不一樣,從而是兩個(gè)不同的事務(wù),所以也不會(huì)回滾。
8)錯(cuò)誤的傳播行為
Spring定義了7種傳播行為,我們可以通propagation
屬性來(lái)指定傳播行為參數(shù),目前只有REQUIRED
、REQUIRES_NEW
、NESTED
會(huì)創(chuàng)建新的事務(wù),其他的則會(huì)以非事務(wù)的方式運(yùn)行或者拋出異常
@Service public class UserServiceImpl { @Autowired UserMapper userMapper; @Transactional(propagation = Propagation.NEVER) public void doTest() throws InterruptedException { userMapper.deleteById(200114); int i = 10/0; //模擬發(fā)生異常 } }
9)自己try…catch…掉了異常
如果沒(méi)有異常拋出,則Spring認(rèn)為程序是正常的,就不會(huì)回滾
@Service public class UserServiceImpl { @Autowired UserMapper userMapper; @Transactional public void doTest() { try{ userMapper.deleteById(200115); int i = 10/0; //模擬發(fā)生異常 }catch (Exception e){ // 異常操作 } } }
10)手動(dòng)拋出了錯(cuò)誤的異常
Spring默認(rèn)只會(huì)回滾RuntimeException
和Error
對(duì)于普通的Exception
,不會(huì)回滾
如果你想觸發(fā)其他異常的回滾,需要在注解上配置一下,如:@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; //模擬發(fā)生異常 }catch (Exception e){ // 異常操作 throw new Exception(); } } }
11)自定義回滾異常
rollbackFor 用于指定能夠觸發(fā)事務(wù)回滾的異常類(lèi)型,可以指定多個(gè)異常類(lèi)型。
默認(rèn)是在RuntimeException和Error上回滾。
若異常非配置指定的異常類(lèi),則事務(wù)失效
@Service public class UserServiceImpl { @Autowired UserMapper userMapper; @Transactional(rollbackFor = NullPointerException.class) public void doTest() throws MyException { userMapper.deleteById(200118); throw new MyException(); } }
即使rollbackFor有默認(rèn)值,但阿里巴巴開(kāi)發(fā)者規(guī)范中,還是要求開(kāi)發(fā)者重新指定該參數(shù)。
因?yàn)槿绻褂媚J(rèn)值,一旦程序拋出了Exception,事務(wù)不會(huì)回滾,這會(huì)出現(xiàn)很大的bug。所以,建議一般情況下,將該參數(shù)設(shè)置成:Exception或Throwable。
12)嵌套事務(wù)回滾多了
@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; //模擬發(fā)生異常 } }
test02()
方法出現(xiàn)了異常,沒(méi)有手動(dòng)捕獲,會(huì)繼續(xù)往上拋,到外層doTest()
方法的代理方法中捕獲了異常。所以,這種情況是直接回滾了整個(gè)事務(wù),不只回滾單個(gè)保存點(diǎn)。
如果只回滾單個(gè)保存點(diǎn),可以將內(nèi)部嵌套事務(wù)放在try/catch中,類(lèi)似于上面的自己try…catch…掉異常,并且不繼續(xù)往上拋異常。這樣就能保證,如果內(nèi)部嵌套事務(wù)中出現(xiàn)異常,只回滾內(nèi)部事務(wù),而不影響外部事務(wù)。
@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; //模擬發(fā)生異常 } }
到此這篇關(guān)于Spring詳細(xì)講解事務(wù)失效的場(chǎng)景的文章就介紹到這了,更多相關(guān)Spring事務(wù)失效內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
java使用zookeeper實(shí)現(xiàn)的分布式鎖示例
這篇文章主要介紹了java使用zookeeper實(shí)現(xiàn)的分布式鎖示例,需要的朋友可以參考下2014-05-05解決java.util.HashMap$Values?cannot?be?cast?to?java.ut的問(wèn)題
這篇文章主要介紹了解決java.util.HashMap$Values?cannot?be?cast?to?java.ut的問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-03-03Java開(kāi)發(fā)JUC交換器Exchanger使用詳解
這篇文章主要為大家介紹了Java開(kāi)發(fā)JUC交換器Exchanger使用詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-12-12關(guān)于Spring?Cache?緩存攔截器(?CacheInterceptor)
這篇文章主要介紹了關(guān)于Spring?Cache緩存攔截器(?CacheInterceptor),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-12-12struts2中simple主題下<s:fieldError>標(biāo)簽?zāi)J(rèn)樣式的移除方法
這篇文章主要給大家介紹了關(guān)于struts2中simple主題下<s:fieldError>標(biāo)簽?zāi)J(rèn)樣式的移除方法,文中通過(guò)示例代碼介紹的非常詳細(xì),需要的朋友可以參考借鑒,下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2018-10-10