目錄
- 注解 `@Transactional` 的屬性引數
- `propagation` 事務的傳播機制
- `isolation` 事務的隔離級別
- 常用資料庫的默認隔離級別
- `readOnly` 事務的讀寫性
- 事務的只讀性概念
- 應用場景
- `timeout` 超時時間
- `rollbackFor` 和 `rollbackForClassName` 遇到時回滾
- `noRollbackFor` 和 `noRollbackForClassName` 遇到時不回滾
- `value` 指定使用的事務管理器
- `spring` 事務不生效的原因
- 資料庫引擎不支持事務
- `@Transactional` 所在類非 `spring` 容器的 `bean`
- 方法不是 `public` 的
- 資料源沒有配置事務管理器
- 事務的 `propagation` 傳播機制設定錯誤
- `catch` 陳述句沒有拋出例外
- 拋出的例外型別錯誤
- 確保業務和事務入口在同一個執行緒
- 自身呼叫問題
- 案列一
- 案列二
- 案列三
- 案列四
- 案列五
- 案列六
- 案列七
注解 @Transactional 的屬性引數
| 屬性 | 型別 | 描述 |
|---|---|---|
value | String | 可選,指定事務管理器 |
propagation | enum: Propagation | 可選,指定事務傳播行為 |
isolation | enum: Isolation | 可選,指定事務隔離級別 |
readOnly | boolean | 讀寫或只讀事務,默認讀寫 |
| timeout | int (in seconds granularity) | 事務超時時間設定 |
rollbackFor | Class 物件陣列,必須繼承自Throwable | 導致事務回滾的例外類陣列 |
rollbackForClassName | 類名陣列,必須繼承自Throwable | 導致事務回滾的例外類名字陣列 |
| noRollbackFor | Class物件陣列,必須繼承自Throwable | 不會導致事務回滾的例外類陣列 |
| noRollbackForClassName | 類名陣列,必須繼承自Throwable | 不會導致事務回滾的 |
propagation 事務的傳播機制
事務的傳播機制:如果在開始當前事務之前,一個事務背景關系已經存在,此時有若干選項可以指定一個事務性方法的執行行為
列舉 Propagation 中定義了 7 個傳播機制的值
Propagation.REQUIRED:如果當前沒有事務,就新建一個事務,如果已經存在一個事務中,加入到這個事務中,是spring默認的傳播機制Propagation.SUPPORTS:持當前事務,如果當前有事務,就以事務方式執行;如果當前沒有事務,就以非事務方式執行Propagation.MANDATORY:使用當前的事務,且必須在一個已有的事務中執行,如果當前不存在事務,否則拋出例外Propagation.REQUIRES_NEW:不管是否存在事務,都創建一個新的事務,原來的掛起,新的執行完畢,繼續執行老的事務Propagation.NOT_SUPPORTED:以非事務方式執行,如果當前存在事務,就把當前事務掛起Propagation.NEVER:以非事務方式執行,且必須在一個沒有的事務中執行,如果當前存在事務,則拋出例外Propagation.NESTED:如果當前存在事務,則在嵌套事務內執行;如果當前沒有事務,則執行與Propagation.REQUIRED類似的操作
isolation 事務的隔離級別
隔離級別:若干個并發的事務之間的隔離程度,與我們開發時候主要相關的場景包括:臟讀取、重復讀、幻讀
列舉 Isolation 中定義了 5 個表示隔離級別的值
Isolation.DEFAULT:使用各個資料庫默認的隔離級別,是spring默認的隔離級別Isolation.READ_UNCOMMITTED:讀取未提交資料(會出現臟讀, 不可重復讀)Isolation.READ_COMMITTED:讀取已提交資料(會出現不可重復讀和幻讀)Isolation.REPEATABLE_READ:可重復讀(會出現幻讀)Isolation.SERIALIZABLE:串行化
常用資料庫的默認隔離級別
MYSQL:默認為REPEATABLE_READSQLSERVER:默認為READ_COMMITTEDOracle:默認為READ_COMMITTED
readOnly 事務的讀寫性
默認情況下是 false(不指定只讀性);設定為 true 的含義: 該方法下使用的是只讀操作,如果進行其他非讀操作,則會跑出例外
事務的只讀性概念
從這一點設定的時間點開始(時間點 a),到這個事務結束的程序中,其他事務所提交的資料,該事務將看不見!!即查詢中不會出現別人在時間點 a 之后提交的資料
應用場景
- 如果你一次執行單條查詢陳述句,則沒有必要啟用事務的只讀性支持,資料庫默認支持
SQL執行期間的讀一致性 - 如果你一次執行多條查詢陳述句,例如統計查詢,報表查詢,在這種場景下,多條查詢
SQL必須保證整體的讀一致性;否則,在前條SQL查詢之后,后條SQL查詢之前,資料被其他用戶改變,則該次整體的統計查詢將會出現讀資料不一致的狀態,此時,就有必要啟用事務的只讀性支持
是一次執行多次查詢來統計某些資訊,這時為了保證資料整體的一致性,要用只讀事務
timeout 超時時間
- 用于設定事務處理的時間長度,阻止可能出現的長時間的阻塞系統或者占用系統資源,單位為秒
- 如果超時設定事務回滾,并拋出
TransactionTimedOutException例外
rollbackFor 和 rollbackForClassName 遇到時回滾
- 用來指明回滾的條件是哪些例外類或者例外類名
spring默認情況下會對運行期例外RunTimeException進行事務回滾,如果遇到checked例外就不回滾
noRollbackFor 和 noRollbackForClassName 遇到時不回滾
用來指明不回滾的條件是哪些例外類或者例外類名
value 指定使用的事務管理器
value主要用來指定不同的事務管理器,主要用來滿足在同一個系統中,存在不同的事務管理器的場景需要- 比如,在
spring中宣告了兩種事務管理器txManager1,txManager2,然后用戶可以根據需要,修改這個引數來指定特定的txManage
存在多個事務管理器的情況:在一個系統中,需要訪問多個資料源,則必然會配置多個事務管理器
spring 事務不生效的原因
spring 團隊建議在具體的 類或類的方法上 使用 @Transactional 注解,而不要使用在類所要實作的任何介面上,在介面上使用 @Transactional 注解,只能當你設定了基于介面的代理時它才生效,因為注解是不能繼承的,這就意味著如果正在使用基于類的代理時,那么事務的設定將不能被基于類的代理所識別,而且物件也將不會被事務代理所包裝
資料庫引擎不支持事務
比如我們常用的 mysql,從 mysql 5.5.5 開始的默認存盤引擎是 InnoDB,之前默認的都是 MyISAM,引擎 MyISAM 是不支持事務操作的,需要改成 InnoDB 才能支持,所以這點要值得注意,底層引擎不支持事務再怎么搞都是白搭
@Transactional 所在類非 spring 容器的 bean
// @Service
public class OrderServiceImpl implements OrderService {
@Transactional
public void updateOrder(Order order) {
// update order
}
}
如果此時把 @Service 注解注釋掉,這個類就不會被加載成一個 bean,那這個類就不會被 spring 管理了,事務自然就失效了
方法不是 public 的
spring 事務官方檔案
Method visibility and @Transactional
When you use proxies, you should apply the @Transactional annotation only to methods with
public visibility. If you do annotate protected, private or package-visible methods with the
@Transactional annotation, no error is raised, but the annotated method does not exhibit the
configured transactional settings. If you need to annotate non-public methods, consider using
AspectJ (described later).
@Transactional 只能用于 public 的方法上,否則事務不會失效;如果要用在非 public 方法上,可以開啟 AspectJ 代理模式
資料源沒有配置事務管理器
@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
當前資料源如果沒有配置事務管理器,那事務是不會生效的
事務的 propagation 傳播機制設定錯誤
@Service
public class OrderServiceImpl implements OrderService {
@Transactional
public void update(Order order) {
updateOrder(order);
}
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void updateOrder(Order order) {
// update order
}
}
Propagation.NOT_SUPPORTED:以非事務方式執行,如果當前存在事務,就把當前事務掛起
catch 陳述句沒有拋出例外
@Service
public class ClassServiceImpl implements ClassService {
@Override
@Transactional
public void insertClassByException(ClassDo classDo) {
classMapper.insertClass(classDo);
try {
int i = 1 / 0;
} catch (Exception e) {
e.printStackTrace();
}
}
}
把例外吃了,然后又不拋出來,事務也不會回滾!
拋出的例外型別錯誤
@Service
public class OrderServiceImpl implements OrderService {
@Transactional
public void updateOrder(Order order) {
try {
// update order
} catch {
throw new Exception("更新錯誤");
}
}
}
@Transactional 默認回滾的是 RuntimeException,而 Exception 是 RuntimeException 的父類,事務不生效的
如果你想觸發其他例外的回滾,需要在注解上配置一下,如
@Transactional(rollbackFor = Exception.class)
確保業務和事務入口在同一個執行緒
@Transactional
@Override
public void save(User user1, User user2) {
new Thread(() -> {
saveError(user1, user2);
System.out.println(1 / 0);
}).start();
}
自身呼叫問題
案列一
@Transactional 的事務開啟,或者是基于介面的或者是基于類的代理被創建,所以在同一個類中一個無事務的方法呼叫另一個有事務的方法,事務是不會起作用的 (這就是業界老問題:類內部方法呼叫事務不生效的問題原因)

因為 addInfo()上沒有事務,而 addInfo() 呼叫 create() 的時候是類內部呼叫,沒有走代理類,也就沒有事務切面
案列二
事務生效

案列三
事務生效

案列四
事務生效

案列五
事務生效

這里雖然是方法內部呼叫,但是事務切入了 addInfo() 方法,所以即使內部拋出例外,也是可以生效的
案列六
事務不生效

案列七
事務不生效

這是我們解決方法內部呼叫事務不生效的最常用方法之一:內部維護一個注入自己的 bean,然后使用這個屬性來呼叫方法,其實還有一種方法,那就是利用 Aop 背景關系來獲取代理物件((TestService)AopContext.currentProxy()).create();,然后通過代理物件來呼叫,這里需要注意:Aop 背景關系 spring 默認是關閉的,需要手動開啟
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/298355.html
標籤:java
上一篇:怎么樣才能做到對多種資料型別排序?C語言快速排序——qsort函式及其模擬實作
下一篇:Redis工程實作
