怎么在微信做企業(yè)網(wǎng)站app開發(fā)費(fèi)用標(biāo)準(zhǔn)
Spring 事務(wù)失效場景
文章目錄
- Spring 事務(wù)失效場景
- 前言
- 事務(wù)不生效
- 未開啟事務(wù)
- 事務(wù)方法未被Spring管理
- 訪問權(quán)限問題
- 基于接口的代理
- 源碼解讀
- CGLIB代理
- 方法用final修飾
- 同一類中的方法調(diào)用
- 多線程調(diào)用
- 不支持事務(wù)
- 事務(wù)不回滾
- 設(shè)置錯(cuò)誤的事務(wù)傳播機(jī)制
- 捕獲了異常
- 手動(dòng)拋了別的異常
- 自定義了回滾異常
- 事務(wù)被手動(dòng)提交
- 其它
- 大事務(wù)問題
- 縮小事務(wù)范圍
- 手動(dòng)提交事務(wù)
- 異步處理
- 事務(wù)的性能和并發(fā)性
前言
# Spring事務(wù)詳解
Spring
事務(wù)是用于解決數(shù)據(jù)庫操作中的一致性和隔離性問題的機(jī)制。數(shù)據(jù)庫事務(wù)是一組操作,要么全部成功執(zhí)行,要么全部回滾,以確保數(shù)據(jù)的完整性和一致性。Spring
事務(wù)管理的主要目的是確保在多個(gè)數(shù)據(jù)庫操作中,要么所有操作都成功提交,要么所有操作都回滾,從而保持?jǐn)?shù)據(jù)的一致性。它提供了以下幾個(gè)方面的解決方案:
-
原子性(
Atomicity
):事務(wù)要么全部成功執(zhí)行,要么全部回滾,確保數(shù)據(jù)庫操作的原子性。 -
一致性(
Consistency
):事務(wù)在執(zhí)行前后,數(shù)據(jù)庫的狀態(tài)應(yīng)保持一致。如果事務(wù)執(zhí)行失敗,數(shù)據(jù)庫應(yīng)該回滾到事務(wù)開始之前的狀態(tài)。 -
隔離性(
Isolation
):事務(wù)應(yīng)該在相互之間隔離,以避免并發(fā)操作引起的問題。它確保了在并發(fā)環(huán)境下,每個(gè)事務(wù)都能夠獨(dú)立地執(zhí)行,并且不會(huì)相互干擾。 -
持久性(
Durability
):一旦事務(wù)提交,其結(jié)果應(yīng)該持久保存在數(shù)據(jù)庫中,即使發(fā)生系統(tǒng)故障或重啟。
Spring
事務(wù)管理通過使用注解或編程方式來定義事務(wù)邊界,它可以應(yīng)用于各種數(shù)據(jù)訪問技術(shù)(如JDBC
、Hibernate
、JPA
等)。它還提供了不同的傳播行為和隔離級別,以滿足不同的業(yè)務(wù)需求。- 通過使用
Spring
事務(wù),開發(fā)人員可以簡化數(shù)據(jù)庫操作的管理,并確保數(shù)據(jù)的一致性和可靠性,從而提高應(yīng)用程序的可靠性和性能。
事務(wù)不生效
未開啟事務(wù)
- 如果使用的是
springboot
項(xiàng)目,springboot
通過DataSourceTransactionManagerAutoConfiguration
類,默認(rèn)開啟了事務(wù)。只需要配置spring.datasource
相關(guān)參數(shù)即可。
- 如果使用的是傳統(tǒng)的
spring
項(xiàng)目,則需要在applicationContext.xml
文件中,手動(dòng)配置事務(wù)相關(guān)參數(shù)。如果忘了配置,事務(wù)肯定是不會(huì)生效的。
事務(wù)方法未被Spring管理
- 未將類標(biāo)記為
Spring
管理的組件:確保類被標(biāo)記為@Service
、@Component
或其他適當(dāng)?shù)淖⒔?#xff0c;以便Spring
能夠掃描并管理該類。 - 未使用
@Transactional
注解標(biāo)記事務(wù)方法:確保需要進(jìn)行事務(wù)管理的方法被標(biāo)記為@Transactional
注解,以便Spring
能夠識(shí)別并應(yīng)用事務(wù)管理。
訪問權(quán)限問題
基于接口的代理
- 當(dāng)使用基于代理的事務(wù)管理時(shí),
Spring
會(huì)在運(yùn)行時(shí)生成一個(gè)代理對象來管理事務(wù)。這個(gè)代理對象會(huì)攔截被注解的方法,并在方法執(zhí)行前后進(jìn)行事務(wù)的開啟、提交或回滾等操作。 - 默認(rèn)情況下,
Spring
的事務(wù)代理是基于接口實(shí)現(xiàn)的,因此只有public
方法才能被代理。如果事務(wù)方法是private
、protected
或默認(rèn)訪問級別的,Spring
無法生成代理對象,從而導(dǎo)致事務(wù)不生效。
/*** 不生效*/
@Transactional
private void ransactionOne() {User user = new User();user.setUsername("張三");userMapper.insertUser(user);methodOne();int a = 3 / 0;logger.info(String.valueOf(a));
}
源碼解讀
-
判斷是否是
public
的
-
有事務(wù)的類
CGLIB代理
- 如果使用基于類的代理,即使用
CGLIB
代理,Spring
可以代理非public
方法??梢酝ㄟ^配置proxy-target-class
屬性為true
來啟用基于類的代理。 - 如果使用的是基于類的代理,事務(wù)方法可以是非
public
的,但需要啟用基于類的代理。 SpringBoot
事務(wù)示例:通過在@EnableTransactionManagement
注解上設(shè)置proxyTargetClass
屬性為true
來啟用基于類的代理。
@Configuration
@EnableTransactionManagement(proxyTargetClass = true)
public class AppConfig {// 配置其他的Bean和組件
}
- 使用基于類的代理時(shí),非
public
方法也可以被代理。但需要注意,啟用基于類的代理可能會(huì)帶來一些性能開銷,因此只有在確實(shí)需要代理非public
方法時(shí)才應(yīng)使用。 - 確保在配置類上添加了
@EnableTransactionManagement
注解,并根據(jù)需要設(shè)置proxyTargetClass
屬性,就可以在Spring Boot
中使用基于類的代理來進(jìn)行事務(wù)管理了。 Spring
使用CGLIB
庫的Enhancer
類來生成代理對象,生成的代理對象中包含EnhancerBySpringCGLIB
作為標(biāo)識(shí)符
方法用final修飾
- 在
Spring
中,事務(wù)是通過動(dòng)態(tài)代理來實(shí)現(xiàn)的。當(dāng)一個(gè)類被代理時(shí),Spring
會(huì)創(chuàng)建一個(gè)代理對象來包裝原始對象,從而在方法調(diào)用前后添加事務(wù)處理邏輯。然而,對于final
方法,由于無法重寫,因此無法創(chuàng)建代理對象,事務(wù)管理器也就無法對其進(jìn)行事務(wù)處理。
@Service
public class UserService {@Transactionalpublic final void add(UserModel userModel){saveData(userModel);updateData(userModel);}
}
同一類中的方法調(diào)用
this
是被真實(shí)對象,所以會(huì)直接走methodTwo
的業(yè)務(wù)邏輯,而不會(huì)走切面邏輯,所以事務(wù)失敗。
/*** 同一個(gè)類中的方法調(diào)用*/
@Override
public void transactionSix() {User user = new User();user.setUsername("張三");userMapper.insertUser(user);// 沒有事務(wù)的方法調(diào)用有事務(wù)的方式,相當(dāng)于使用 this 調(diào)用不會(huì)走 AOP 的邏輯methodTwo();
}@Transactional(rollbackFor = Throwable.class)
public void methodTwo() {User user = new User();user.setUsername("李四");userMapper.insertUser(user);int a = 5 / 0;logger.info(String.valueOf(a));
}
- 解決方法可以是在方法上添加
@Transactional
注解 @EnableAspectJAutoProxy(exposeProxy = true)
在啟動(dòng)類中添加,會(huì)由Cglib
代理實(shí)現(xiàn)。- 如果只想讓
methodTwo
的事務(wù)生效,可以把methodTwo
寫到一個(gè)新的service
,用service
調(diào)用
/*** 同一個(gè)類中的方法調(diào)用,調(diào)用 service 的方法,insetUser 方法事務(wù)可以生效*/
@Override
public void transactionSeven(){User user = new User();user.setUsername("張三");userMapper.insertUser(user);transactionHelpService.insetUser();
}@Transactional(rollbackFor = Throwable.class)
@Override
public void insetUser() {User user = new User();user.setUsername("李四");userMapper.insertUser(user);int a = 5 / 0;logger.info(String.valueOf(a));
}
多線程調(diào)用
- 同一個(gè)事務(wù),其實(shí)是指同一個(gè)數(shù)據(jù)庫連接,只有擁有同一個(gè)數(shù)據(jù)庫連接才能同時(shí)提交和回滾。如果在不同的線程,拿到的數(shù)據(jù)庫連接肯定是不一樣的,所以是不同的事務(wù)。
不支持事務(wù)
-
數(shù)據(jù)庫本身無法支持事務(wù)的場景下,例如使用
Mysql
的MyISAM
引擎 -
在
MySQL5.5
往后,默認(rèn)采用InnoDB存儲(chǔ)引擎
,在這之前采用MyISAM存儲(chǔ)引擎
。 -
InnoDB
是MySQL
的默認(rèn)事務(wù)型引擎
,它被設(shè)計(jì)用來處理大量的短期(short-lived)事務(wù)
??梢源_保事務(wù)的完整提交(Commit)和回滾(Rollback)
。 -
MyISAM
提供了大量的特性,包括全文索引、壓縮、空間函數(shù)(GIS)
等,但MyISAM
不支持事務(wù)、行級鎖、外鍵
,有一個(gè)毫無疑問的缺陷就是崩潰后無法安全恢復(fù)
。
事務(wù)不回滾
設(shè)置錯(cuò)誤的事務(wù)傳播機(jī)制
Propagation.NEVER
:這種類型的傳播特性不支持事務(wù),如果有事務(wù)則會(huì)拋異常。- 目前只有這三種傳播特性才會(huì)創(chuàng)建新事務(wù):
REQUIRED
,REQUIRES_NEW
,NESTED
捕獲了異常
- 在代碼中手動(dòng)
try...catch
了異常
@Transactional
@Override
public void testOne() {try {User user = new User();user.setUsername("張三");userMapper.insertUser(user);int a = 3 / 0;} catch (Exception e) {logger.error(e.getMessage(), e);}
}
- 在
Spring Boot
中,事務(wù)是通過AOP
(面向切面編程)機(jī)制實(shí)現(xiàn)的。當(dāng)使用@Transactional
注解標(biāo)記一個(gè)方法時(shí),Spring
會(huì)在方法開始前創(chuàng)建一個(gè)事務(wù),并在方法執(zhí)行結(jié)束后根據(jù)方法的執(zhí)行結(jié)果來決定是否提交或回滾事務(wù)。 - 如果在方法執(zhí)行期間發(fā)生異常,
Spring
會(huì)捕獲該異常并將事務(wù)標(biāo)記為“回滾”。這意味著在方法執(zhí)行結(jié)束后,Spring
會(huì)自動(dòng)回滾該事務(wù)并撤銷對數(shù)據(jù)庫的任何更改。 - 如果在方法中捕獲了異常并處理了它,那么
Spring
就無法感知到該異常,并且不會(huì)將事務(wù)標(biāo)記為“回滾”。這意味著在方法執(zhí)行結(jié)束后,Spring
會(huì)將事務(wù)提交,而不是回滾,這可能會(huì)導(dǎo)致不一致的數(shù)據(jù)狀態(tài)。
手動(dòng)拋了別的異常
spring
事務(wù),默認(rèn)情況下只會(huì)回滾RuntimeException
(運(yùn)行時(shí)異常)和Error
(錯(cuò)誤),對于普通的Exception
(非運(yùn)行時(shí)異常),它不會(huì)回滾。- 如果在執(zhí)行方法時(shí)拋出了
RuntimeException
類型的異常,Spring
會(huì)認(rèn)為這個(gè)異常是不可恢復(fù)的,也就是說無法通過異常處理來修復(fù)。在這種情況下,Spring
會(huì)回滾事務(wù),撤銷之前的所有數(shù)據(jù)庫操作,以保證數(shù)據(jù)的一致性。 - 如果拋出的是非
RuntimeException
類型的異常,Spring
會(huì)認(rèn)為這個(gè)異常是可以恢復(fù)的,意味著可以通過異常處理來修復(fù)。在這種情況下,Spring
不會(huì)回滾事務(wù),而是允許應(yīng)用程序繼續(xù)執(zhí)行。 - 這個(gè)機(jī)制的主要目的是保護(hù)數(shù)據(jù)的一致性。在大多數(shù)情況下,如果發(fā)生了非
RuntimeException
類型的異常,應(yīng)用程序可能會(huì)嘗試通過其他方式來處理異常并繼續(xù)執(zhí)行。而如果發(fā)生了RuntimeException
類型的異常,應(yīng)用程序可能無法繼續(xù)執(zhí)行,因此需要回滾事務(wù)以撤銷之前的數(shù)據(jù)庫操作。
/*** 在代碼中手動(dòng)拋出別的異常 事務(wù)不回滾*/
@Override
public void testTwo() throws Exception {try {User user = new User();user.setUsername("張三");userMapper.insertUser(user);int a = 3 / 0;} catch (Exception e) {logger.error(e.getMessage(), e);throw new Exception("test");}
}
自定義了回滾異常
-
在使用
@Transactional
注解聲明事務(wù)時(shí),有時(shí)我們想自定義回滾的異常,spring
也是支持的。可以通過設(shè)置rollbackFor
參數(shù) -
rollbackFor
參數(shù)指定了回滾事務(wù)異常的類型,需要檢查拋出的異常是否是指定的異常,如果不一致則回滾不了
-
開發(fā)規(guī)范中會(huì)提示需要指定
rollbackFor
參數(shù),事務(wù)默認(rèn)只會(huì)在捕獲到未被處理的RuntimeException
或 Error 時(shí)才會(huì)回滾。如果你的代碼中捕獲了異常并進(jìn)行了處理,但沒有再次拋出RuntimeException
或 Error,事務(wù)將不會(huì)回滾。確保在捕獲異常時(shí),將異常重新拋出或手動(dòng)觸發(fā)回滾。
事務(wù)被手動(dòng)提交
- 如果在代碼中手動(dòng)調(diào)用了
commit()
方法來提交事務(wù),而沒有調(diào)用rollback()
方法來回滾事務(wù),事務(wù)將不會(huì)回滾。確保在需要回滾的情況下,正確地調(diào)用了回滾方法。
其它
大事務(wù)問題
縮小事務(wù)范圍
- 將大事務(wù)拆分為多個(gè)小事務(wù),并使用嵌套事務(wù)來管理它們
手動(dòng)提交事務(wù)
- 如果需要更細(xì)粒度的控制,可以在代碼中手動(dòng)管理事務(wù)。使用
TransactionTemplate
類可以手動(dòng)啟動(dòng)、提交或回滾事務(wù)
異步處理
- 對于非關(guān)鍵的操作,你可以考慮使用異步處理來減少事務(wù)的時(shí)間。
- 將一些長時(shí)間運(yùn)行的操作放在異步任務(wù)中,這樣可以減少事務(wù)的持有時(shí)間,從而減少事務(wù)的大小和影響范圍。
事務(wù)的性能和并發(fā)性
- 大事務(wù)可能會(huì)導(dǎo)致性能問題和并發(fā)性競爭。在設(shè)計(jì)事務(wù)時(shí),要考慮事務(wù)的持有時(shí)間和鎖競爭情況。盡量將事務(wù)范圍縮小到最小,避免長時(shí)間持有事務(wù)鎖,以提高性能和并發(fā)性。