這期內容當中小編將會給大家帶來有關Spring中@Transactional事務不生效如何解決,文章內容豐富且以專業的角度為大家分析和敘述,閱讀完這篇文章希望大家可以有所收獲。
事務管理在系統開發中是不可缺少的一部分,Spring
提供了很好事務管理機制,主要分為編程式事務
和聲明式事務
兩種。
編碼式事務管理:將事務控制代碼編寫在業務代碼之中。
聲明式事務管理:基于AOP(面向切面編程),事務管理與業務邏輯解耦。兩種實現:(1)在配置文件(xml)中配置。(2)基于@Transactional注解。
@Transactional 可以作用在接口
、類
、類方法
。
作用于類:當把@Transactional 注解放在類上時,表示所有該類的public
方法都配置相同的事務屬性信息。
作用于方法:當類配置了@Transactional,方法也配置了@Transactional,方法的事務會覆蓋類的事務配置信息。
作用于接口:不推薦這種使用方法,因為一旦標注在Interface上并且配置了Spring AOP 使用CGLib動態代理,將會導致@Transactional注解失效
MySql 的 MyISAM 引擎不支持回滾,如果需要自動回滾事務,需要將MySql的引擎設置成InnoDB;
//@Transactional注解在private方法上會失效@Transactionalprivate void deleteUser() throws MyException{userMapper.deleteUserA();int i = 1/0;userMapper.deleteUserB();}
idea直接會給出提示Methods annotated with ‘@Transactional’ must be overridable ,原理很簡單,private修飾的方式,spring無法生成動態代理,AOP代理分別在intercept()和invoke()方法判斷是否進行事務攔截,這兩個方法都會間接調用AbstractFallbackTransactionAttributeSource類的computeTransactionAttribute方法來獲取事務控制的相關屬性。這其中有以下一段代碼
/** * Same signature as {@link #getTransactionAttribute}, but doesn't cache the result. * {@link #getTransactionAttribute} is effectively a caching decorator for this method. * <p>As of 4.1.8, this method can be overridden. * @since 4.1.8 * @see #getTransactionAttribute */ protected TransactionAttribute computeTransactionAttribute(Method method, Class<?> targetClass) {// Don't allow no-public methods as required.if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {return null;}//... }
這段代碼會導致no-public的方法無法進入事務控制,所以一定要確保自己需要進行事務控制的方法包含public修飾符。
當異常被捕獲后,并且沒有再拋出,那么deleteUserA是不會回滾的,例如:
@Transactionalpublic void deleteUser() {userMapper.deleteUserA();try {int i = 1 / 0;userMapper.deleteUserB();} catch (Exception e) {e.printStackTrace();}}
異步雖然拋出了,但是拋出的是非RuntimeException類型的異常,依舊不會生效,例如:
@Transactionalpublic void deleteUser() throws MyException{userMapper.deleteUserA();try {int i = 1 / 0;userMapper.deleteUserB();} catch (Exception e) {throw new MyException();}}
注解為事務范圍的方法中,事務的回滾僅僅對于unchecked的異常有效。對于checked異常無效。也就是說事務回滾僅僅發生在,出現RuntimeException或Error的時候。通俗一點就是:代碼中出現的空指針等異常,會被回滾。而文件讀寫、網絡超時問題等,spring就沒法回滾了。
解決方案:如果指定了回滾異常類型為Exception,那么就可以回滾Checked類型異常了。
@Transactional(rollbackFor = Exception.class)
java里面將派生于Error或者RuntimeException(比如空指針,1/0)的異常稱為unchecked異常,其他繼承自java.lang.Exception得異常統稱為Checked Exception,如IOException、TimeoutException等
如果先調用deleteUser(),那么deleteUserA()是不會回滾的,其原因就是@Transactional根本沒生成代理,例如:
public void deleteUser() throws MyException{deleteUser2(); // 事物失效}@Transactionalpublic void deleteUser2() throws MyException{userMapper.deleteUserA();int i = 1 / 0;userMapper.deleteUserB();}
項目中沒有配置事務管理器,需要在配置類或者配置文件中配置,因為項目是多數據源的,所以要區別配置不同數據源的事務管理器,如下:
@Primary@Bean(name = "db1")public DataSource getDataSource() {return createDataSource();}@Bean(name = "db1TransactionManager")public PlatformTransactionManager txManager(@Qualifier("db1") DataSource dataSource) {return new DataSourceTransactionManager(dataSource);}
@Bean(name = "db2") public DataSource getDataSource() { return buildDataSource(); } @Bean(name = "db2TransactionManager") public PlatformTransactionManager txManager(@Qualifier("db2") DataSource dataSource) { return new DataSourceTransactionManager(dataSource); }
可以看到,兩個事務管理器配置了不同的beanName,接下來只需要 在需要事務控制的位置加上該事務管理器的name就可以完美解決!
@Override @Transactional(value = "db1TransactionManager",rollbackFor = Exception.class) public int updateOrInsert(BaseRequest<BankTemplateDto> param) { ... }
如下的方式deleteUserA()也不會回滾,因為spring實現事務的原理是通過ThreadLocal把數據庫連接綁定到當前線程中,新開啟一個線程獲取到的連接就不是同一個了,例如:
@Transactionalpublic void deleteUser() throws MyException{userMapper.deleteUserA();try {//休眠1秒,保證deleteUserA先執行Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}new Thread(() -> {int i = 1/0;userMapper.deleteUserB();}).start(); }
注意傳播屬性的設置,一般情況下,propagation屬性無需配置。會使用默認配置,即:PROPAGATION_REQUIRED,有些propagation屬性會導致事務不會觸發,一定要注意:
PROPAGATION_SUPPORTS: 如果存在事務,則進入事務;否則,以非事務方式運行。
PROPAGATION_NOT_SUPPORTED: 如果存在事務,則掛起事務,并以非事務方式運行。
PROPAGATION_NEVER: 以非事務形式運行,如果存在事務,則拋出異常。
propagation屬性
propagation
代表事務的傳播行為,默認值為 Propagation.REQUIRED
,其他的屬性信息如下:
Propagation.REQUIRED
:如果當前存在事務,則加入該事務,如果當前不存在事務,則創建一個新的事務。( 也就是說如果A方法和B方法都添加了注解,在默認傳播模式下,A方法內部調用B方法,會把兩個方法的事務合并為一個事務 )
Propagation.SUPPORTS
:如果當前存在事務,則加入該事務;如果當前不存在事務,則以非事務的方式繼續運行。
Propagation.MANDATORY
:如果當前存在事務,則加入該事務;如果當前不存在事務,則拋出異常。
Propagation.REQUIRES_NEW
:重新創建一個新的事務,如果當前存在事務,暫停當前的事務。( 當類A中的 a 方法用默認Propagation.REQUIRED
模式,類B中的 b方法加上采用 Propagation.REQUIRES_NEW
模式,然后在 a 方法中調用 b方法操作數據庫,然而 a方法拋出異常后,b方法并沒有進行回滾,因為Propagation.REQUIRES_NEW
會暫停 a方法的事務 )
Propagation.NOT_SUPPORTED
:以非事務的方式運行,如果當前存在事務,暫停當前的事務。
Propagation.NEVER
:以非事務的方式運行,如果當前存在事務,則拋出異常。
Propagation.NESTED
:和 Propagation.REQUIRED 效果一樣。
isolation 屬性
isolation
:事務的隔離級別,默認值為 Isolation.DEFAULT
。
Isolation.DEFAULT:使用底層數據庫默認的隔離級別。
Isolation.READ_UNCOMMITTED
Isolation.READ_COMMITTED
Isolation.REPEATABLE_READ
Isolation.SERIALIZABLE
timeout 屬性
timeout
:事務的超時時間,默認值為 -1。如果超過該時間限制但事務還沒有完成,則自動回滾事務。
readOnly 屬性
readOnly
:指定事務是否為只讀事務,默認值為 false;為了忽略那些不需要事務的方法,比如讀取數據,可以設置 read-only 為 true。
rollbackFor 屬性
rollbackFor
:用于指定能夠觸發事務回滾的異常類型,可以指定多個異常類型。
noRollbackFor屬性**
noRollbackFor
:拋出指定的異常類型,不回滾事務,也可以指定多個異常類型
上述就是小編為大家分享的Spring中@Transactional事務不生效如何解決了,如果剛好有類似的疑惑,不妨參照上述分析進行理解。如果想知道更多相關知識,歡迎關注億速云行業資訊頻道。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。