spring事務為什麼不生效,回滾失效,事務try catch(事務失效)
Spring事務的原理
Spring事務的本質其實就是資料庫Innodb對事務的支援,沒有innodb是事務支援,spring是無法提供事務支援的。真正的資料庫層的事務提交和回滾是通過binlog或者redo log實現的。
對於純jdbc操作資料庫,想要用到事務,需要按照以下的步驟進行:
- 獲取連線
Connection connection = DriverManager.getConnection(url, username, root);
- 開啟事務
connection .setAutoCommit(true/false);
- 執行CRUD
- 提交事務/回滾事務
connection .commit() / connection .rollback();
- 關閉連線
connection .close();
程式碼如下
try {
Connection connection = DriverManager.getConnection(url, username, root);
connection .setAutoCommit(false); //開啟事務,禁止自動提交
preparedStatement = conn.prepareStatement("update t_category t set t.name='測試' where t.id =?");
preparedStatement. setInt(1, 10);
preparedStatement.executeUpdate() ;
connection .commit(); //提交事務
}catch(Exception e ){
connection .rollback();
}
使用Spring的事務管理功能後,我們可以不再寫步驟 2 和 4 的程式碼,而是由Spirng 自動完成。
Spring的事務機制
Spring的事務機制提供了一個PlatformTransactionManager介面,不同的資料訪問技術的事務使用不同的介面實現,如表所示:
資料訪問技術 |
實現 |
---|---|
JDBC |
DataSourceTransactionManager |
JPA |
JapTransactionManager |
Hibernate |
HibernateTransactionManager |
JDO |
HibernateTransactionManager |
分散式事務 |
JtaTransactionManager |
以上參考出處:https://my.oschina.net/xiaolyuh/blog/3109049
以JDBC為例,可以在程式碼配置事務管理器:
@Bean
public PlatformTransactionManager transactionManager() {
DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
transactionManager.setDataSource(druidDataSource());
return transactionManager;
}
使用@Transactional註解在方法上表明該方法需要事務支援。這是一個基於AOP的實現操作。
AOP的代理:
1.JDK動態代理實現(原理是使用反射機制)
具體有如下四步驟:
- 通過實現 InvocationHandler 介面建立自己的呼叫處理器;
- 通過為 Proxy 類指定 ClassLoader 物件和一組 interface 來建立動態代理類; ’通過反射機制獲得動態代理類的建構函式,其唯一引數型別是呼叫處理器介面型別; 通過建構函式建立動態代理類例項,構造時呼叫處理器物件作為引數被傳入。
2.CGLIB代理
1、如果目標物件實現了介面,預設情況下會採用JDK的動態代理實現AOP 2、如果目標物件實現了介面,可以強制使用CGLIB實現AOP 3、如果目標物件沒有實現了介面,必須採用CGLIB庫,spring會自動在JDK動態代理和CGLIB之間轉換
資料庫隔離級別
隔離級別 |
隔離級別的值 |
導致的問題 |
---|---|---|
Read-Uncommitted |
0 |
導致髒讀 |
Read-Committed |
1 |
避免髒讀,允許不可重複讀和幻讀 |
Repeatable-Read |
2 |
避免髒讀,不可重複讀,允許幻讀 |
Serializable |
3 |
序列化讀,事務只能一個一個執行,避免了髒讀、不可重複讀、幻讀。執行效率慢,使用時慎重 |
Spring 事務的7個傳播屬性
常量名稱 |
常量解釋 |
---|---|
PROPAGATION_REQUIRED |
支援當前事務,如果當前沒有事務,就新建一個事務。這是最常見的選擇,也是 Spring 預設的事務的傳播。 |
PROPAGATION_REQUIRES_NEW |
新建事務,如果當前存在事務,把當前事務掛起。新建的事務將和被掛起的事務沒有任何關係,是兩個獨立的事務,外層事務失敗回滾之後,不能回滾內層事務執行的結果,內層事務失敗丟擲異常,外層事務捕獲,也可以不處理回滾操作 |
PROPAGATION_SUPPORTS |
支援當前事務,如果當前沒有事務,就以非事務方式執行。 |
PROPAGATION_MANDATORY |
支援當前事務,如果當前沒有事務,就丟擲異常。 |
PROPAGATION_NOT_SUPPORTED |
以非事務方式執行操作,如果當前存在事務,就把當前事務掛起。 |
PROPAGATION_NEVER |
以非事務方式執行,如果當前存在事務,則丟擲異常。 |
PROPAGATION_NESTED |
如果一個活動的事務存在,則執行在一個巢狀的事務中。如果沒有活動事務,則按REQUIRED屬性執行。它使用了一個單獨的事務,這個事務擁有多個可以回滾的儲存點。內部事務的回滾不會對外部事務造成影響。它只對DataSourceTransactionManager事務管理器起效。 |
注:Spring的預設事務傳播特性是PROPAGATION_REQUIRED,MySQL預設的隔離級別是Repeatable-Read
事務的巢狀例子
package com.yudianxx.springBootDemo.transation;
import com.yudianxx.springBootDemo.mapper.image.ImageCategoryMapper;
import com.yudianxx.springBootDemo.model.image.Category;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.interceptor.TransactionAspectSupport;
@Service
@Slf4j
public class TransactionTestServiceImpl implements TransactionTestService {
@Autowired
TransactionTestServiceImpl transactionTestService;
@Autowired
ImageCategoryMapper imageCategoryMapper;
/**
* 事務測試
*
* @return
*/
@Override
@Transactional(propagation = Propagation.REQUIRED)
public void testTransactional() {
Category category = Category.builder().name("事務測試").build();
/*// 情況 1.
try {
a(category); //內部類呼叫,不走AOP,事務不起作用,加入a()報錯了,插入仍然有效,相當於普通呼叫
b(category);
} catch (Exception e) {
e.printStackTrace();
}*/
/*
// 情況2.
transactionTestService.a(category); //解決情況一的問題
transactionTestService.b(category);
// throw new RuntimeException(); //沒有try catch ,父、子同一事務,父報錯,全回滾
*/
/*// 情況3.
try{
transactionTestService.a(category);
transactionTestService.b(category);
throw new RuntimeException(); //父、子同一事務,子方法沒有拋異常,父雖然拋了異常但是被catch到,等於沒丟擲過,所以都不會回滾
}catch (Exception e){
}*/
/*// 情況4.
try {
transactionTestService.a(category);
transactionTestService.b(category);
throw new RuntimeException();
} catch (Exception e) {
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); //強制回滾,假如子是REQUIRES_NEW,則子不回滾
}*/
/*// 情況 5.
try {
transactionTestService.a(category);
transactionTestService.b(category);
throw new RuntimeException();
} catch (Exception e) {
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); //強制回滾,假如子是REQUIRES_NEW,則子不回滾
}*/
// 情況6.
try {
transactionTestService.a(category);
transactionTestService.b(category);
// transactionTestService.c(category); //不回滾
// transactionTestService.d(category); //回滾
// transactionTestService.e(category); //不回滾 e是另外的事務
// transactionTestService.f(category); //a、b不回滾,f回滾
transactionTestService.g(category); //a、b、g都不回滾
} catch (Exception e) {
}
}
// @Transactional(propagation = Propagation.REQUIRES_NEW)
// 會單獨起一個事務,成功了則插入,不受其他事務影響
@Transactional(propagation = Propagation.REQUIRED)
public void a(Category category) {
log.info("進入A方法");
category.setName("事務測試a");
imageCategoryMapper.insert(category);
}
@Transactional(propagation = Propagation.REQUIRED)
public void b(Category category) {
log.info("進入B方法");
category.setName("事務測試b");
imageCategoryMapper.insert(category);
}
@Transactional(propagation = Propagation.REQUIRED)
public void c(Category category) {
log.info("進入c方法");
try {
int j = 1 / 0;
} catch (Exception e) {
// throw e; //如果是把錯誤丟擲來了,上層捕獲了就會回滾事務的,如果沒有throw,這個方法自己處理了異常就不會拋,相當於沒拋異常正常執行
//簡單的說,throw e 了相當於沒有加try catch
}
}
@Transactional(propagation = Propagation.REQUIRED)
public void d(Category category) {
log.info("進入d方法");
int j = 1 / 0;
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void e(Category category) {
log.info("進入e方法");
int j = 1 / 0;
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void f(Category category) {
log.info("進入f方法");
category.setName("事務測試f");
imageCategoryMapper.insert(category);
int j = 1 / 0;
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void g(Category category) {
log.info("進入g方法");
category.setName("事務測試g");
imageCategoryMapper.insert(category);
try {
int j = 1 / 0;
} catch (Exception e) {
}
}
}
PROPAGATION_REQUIRED(spring 預設)
情況6中, transactionTestService.a(category) 的時候spring已經起了事務,這時呼叫 transactionTestService.b(category), transactionTestService.b(category)看到自己已經執行在 transactionTestService.a(category) 的事務內部,就不再起新的事務,加入到a的事務。
PROPAGATION_REQUIRES_NEW
情況6中, transactionTestService.e(category) 是新的事務,a和b的事務會掛起,e會新起一個事務。a、b、e回不回滾主要看是否丟擲異常。
spring 什麼情況下進行事務回滾?
Spring、EJB的宣告式事務預設情況下都是在丟擲unchecked exception後才會觸發事務的回滾 unchecked異常,即執行時異常runntimeException 回滾事務;
checked異常,即Exception可try{}捕獲的不會回滾.當然也可配置spring引數讓其回滾. 配置如下:
@Transactional( rollbackFor = Exception.class)
spring的事務邊界是在呼叫業務方法之前開始的,業務方法執行完畢之後來執行commit or rollback(Spring預設取決於是否丟擲runtime異常). 如果丟擲runtime exception 並在你的業務方法中沒有catch到的話,事務會回滾。 一般不需要在業務方法中catch異常,如果非要catch,在做完你想做的工作後(比如關閉檔案等)一定要丟擲runtime exception,否則spring會將你的操作commit,這樣就會產生髒資料.所以你的catch程式碼是畫蛇添足。
結論:
- 無論內外報 非RuntimeException 錯誤,都不會回滾。
- 如果加上rollbackFor = Exception.class,無論內外怎麼報錯,都會回滾。
參考: https://my.oschina.net/xiaolyuh/blog/3109049 https://www.cnblogs.com/pjjlt/p/10926398.html
本文參與 騰訊雲自媒體分享計劃 ,歡迎熱愛寫作的你一起參與! 本文分享自作者個人站點/部落格:https://blog.csdn.net/yudianxiaoxiao複製 如有侵權,請聯絡 [email protected] 刪除。 來源:spring事務為什麼不生效,回滾失效,事務try catch - 雲+社群 - 騰訊雲 (tencent.com)