Spring詳細講解事務(wù)失效的場景
1)未被Spring管理
使用Spring事務(wù)的前提是:對象要被Spring管理,事務(wù)方法所在的類要被加載為bean對象
如果事務(wù)方法所在的類沒有被加載為一個bean,那么事務(wù)自然就失效了,示例:
//@Service
public class UserServiceImpl {
@Transactional
public void doTest() {
// 業(yè)務(wù)代碼
}
}2)數(shù)據(jù)庫引擎不支持事務(wù)
以MySQL為例,InnoDB引擎是支持事務(wù)的,而像MyISAM、MEMORY等是不支持事務(wù)的。
從MySQL5.5.5開始默認的存儲引擎是InnoDB,之前默認都是MyISAM。所以在開發(fā)過程中發(fā)現(xiàn)事務(wù)失效,不一定是Spring的鍋,最好確認一下數(shù)據(jù)庫表是否支持事務(wù)。
3)事務(wù)方法沒有被public修飾
眾所周知,java的訪問權(quán)限修飾符有:private、default、protected、public四種,
但是@Transactional注解只能作用于public修飾的方法上,
在AbstractFallbackTransactionAttributeSource類(Spring通過這個類獲取@Transactional注解的配置屬性信息)的computeTransactionAttribute方法中有個判斷,如果目標方法不是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;
}
//………………
}其實想想動態(tài)代理的原理就很好理解了,動態(tài)代理是通過實現(xiàn)接口或者繼承來實現(xiàn)的,所以目標方法必須是public修飾,并且不能是final修飾。
4)方法使用final修飾
如果一個方法不想被子類重寫,那么我們就可以把他寫成final修飾的方法
如果事務(wù)方法使用final修飾,那么aop就無法在代理類中重寫該方法,事務(wù)就不會生效
同樣的,static修飾的方法也無法通過代理變成事務(wù)方法
5)同一類中方法調(diào)用
假如在某個Service的方法中,調(diào)用了另外一個事務(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注解標注,在del()方法中調(diào)用了doTest()方法,在外部調(diào)用del()方法時,事務(wù)也不會生效,因為這里del()方法中調(diào)用的是類本身的方法,而不是代理對象的方法。
那么如果確實有這樣的需求怎么辦呢?
引入自身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ā)生異常
}
}通過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ā)生異常
}
}通過AopContext獲取當(dāng)前代理類
在啟動類上添加注解@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; //模擬發(fā)生異常
}
}6)未開啟事務(wù)
如果是SpringBoot項目,那么SpringBoot通過DataSourceTransactionManagerAutoConfiguration自動配置類幫我們開啟了事務(wù)。
如果是傳統(tǒng)的Spring項目,則需要我們自己配置
<!-- 配置事務(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開頭的方法,以下同理 -->
<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í)行的方法在哪個包,意思為:com.yy.service下的所有包里面的包含任意參數(shù)的所有方法-->
<aop:advisor advice-ref="Advice" pointcut-ref="AdviceAop"/> <!-- 配置為AdviceAop執(zhí)行哪個事務(wù)通知 -->
</aop:config>
這樣在執(zhí)行service包下的增刪改操作的方法時,就開啟事務(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)多線程調(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中,啟動了一個新的線程,并在新的線程中發(fā)生了異常,這樣doTest是不會回滾的。
因為兩個操作不在一個線程中,獲取到的數(shù)據(jù)庫連接不一樣,從而是兩個不同的事務(wù),所以也不會回滾。
8)錯誤的傳播行為
Spring定義了7種傳播行為,我們可以通propagation屬性來指定傳播行為參數(shù),目前只有REQUIRED、REQUIRES_NEW、NESTED會創(chuàng)建新的事務(wù),其他的則會以非事務(wù)的方式運行或者拋出異常

@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…掉了異常
如果沒有異常拋出,則Spring認為程序是正常的,就不會回滾
@Service
public class UserServiceImpl {
@Autowired
UserMapper userMapper;
@Transactional
public void doTest() {
try{
userMapper.deleteById(200115);
int i = 10/0; //模擬發(fā)生異常
}catch (Exception e){
// 異常操作
}
}
}10)手動拋出了錯誤的異常
Spring默認只會回滾RuntimeException和Error對于普通的Exception,不會回滾
如果你想觸發(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ù)回滾的異常類型,可以指定多個異常類型。
默認是在RuntimeException和Error上回滾。
若異常非配置指定的異常類,則事務(wù)失效
@Service
public class UserServiceImpl {
@Autowired
UserMapper userMapper;
@Transactional(rollbackFor = NullPointerException.class)
public void doTest() throws MyException {
userMapper.deleteById(200118);
throw new MyException();
}
}即使rollbackFor有默認值,但阿里巴巴開發(fā)者規(guī)范中,還是要求開發(fā)者重新指定該參數(shù)。
因為如果使用默認值,一旦程序拋出了Exception,事務(wù)不會回滾,這會出現(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)了異常,沒有手動捕獲,會繼續(xù)往上拋,到外層doTest()方法的代理方法中捕獲了異常。所以,這種情況是直接回滾了整個事務(wù),不只回滾單個保存點。
如果只回滾單個保存點,可以將內(nèi)部嵌套事務(wù)放在try/catch中,類似于上面的自己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詳細講解事務(wù)失效的場景的文章就介紹到這了,更多相關(guān)Spring事務(wù)失效內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java創(chuàng)建多線程局域網(wǎng)聊天室實例
這篇文章主要介紹了Java創(chuàng)建多線程局域網(wǎng)聊天室實例,本篇文章通過簡要的案例,講解了該項技術(shù)的了解與使用,以下就是詳細內(nèi)容,需要的朋友可以參考下2021-07-07
SpringBoot項目nohup啟動運行日志過大的解決方案
這篇文章主要介紹了SpringBoot項目nohup啟動運行日志過大的解決方案,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-05-05
mybatis if test判斷BigDecimal遇到的坑及解決
這篇文章主要介紹了mybatis if test判斷BigDecimal遇到的坑及解決方案,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-03-03
Spring Boot JDBC 連接數(shù)據(jù)庫示例
本篇文章主要介紹了Spring Boot JDBC 連接數(shù)據(jù)庫示例,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-02-02

