文章目錄
- 1. 簡介
- 1.1 什么是事務
- 1.2 什么是Spring事務管理
- 1.3 @Transactional注解的作用
- 2. @Transactional注解的使用
- 2.1 如何在Spring中使用@Transactional
- 2.2 @Transactional的屬性配置
- 3. @Transactional的工作原理
- 3.1 Spring如何管理事務
- 3.2 @Transactional的底層實現
- 4. @Transactional的傳播行為
- 4.1 傳播行為的種類
- 4.2 每種傳播行為的具體作用和使用場景
- 5. @Transactional的隔離級別
- 5.1 事務的隔離級別有哪些
- 5.2 如何在@Transactional中設置隔離級別
- 6. @Transactional的只讀設置
- 6.1 什么是只讀事務
- 6.2 如何在@Transactional中設置只讀
- 7. @Transactional的回滾規則
- 7.1 默認的回滾規則是什么
- 7.2 如何自定義回滾規則
- 8. @Transactional的常見問題
- 8.1 @Transactional不生效的原因及解決辦法
- 8.1.1 原因1:事務管理器配置錯誤或缺失
- 8.1.2 原因2:使用@Transactional注解的方法在同一個類中調用
- 8.1.3 原因3:事務傳播行為配置不正確
- 8.2 @Transactional的使用注意事項
- 9.實例
1. 簡介
1.1 什么是事務
在計算機科學中,事務通常指的是一系列操作,這些操作作為一個整體一起執行,要么全部成功,要么全部失敗。事務是保證數據一致性的一種機制,它有四個基本特性,通常被稱為ACID屬性:
- 原子性(Atomicity):事務是一個原子操作單元,其對數據的修改要么全都執行,要么全都不執行。
- 一致性(Consistency):事務必須使數據庫從一個一致性狀態變換到另一個一致性狀態。
- 隔離性(Isolation):在并發環境中,一個事務的執行不應該被其他事務干擾。即一個事務內部的操作及使用的數據對并發的其他事務是隔離的,并發執行的各個事務之間不能互相干擾。
- 持久性(Durability):一旦事務完成(即成功提交),其結果就必須能夠永久保存在數據庫中。
1.2 什么是Spring事務管理
Spring事務管理是Spring框架中的一個重要組成部分,它提供了一種抽象機制來管理事務。Spring事務管理可以處理編程式和聲明式的兩種事務管理方式。
編程式事務管理:這種方式需要在代碼中明確地進行事務管理,包括開始事務、提交事務、回滾事務等。
聲明式事務管理:這種方式只需要通過注解或XML配置來管理事務,無需在代碼中進行事務管理。這種方式的主要優點是不需要編寫大量的事務管理代碼,使業務代碼更加簡潔、清晰。
在Spring中,最常用的聲明式事務管理方式就是使用@Transactional
注解。
1.3 @Transactional注解的作用
在Spring框架中,@Transactional
是用于管理事務的關鍵注解。它可以應用于接口定義、類定義、公開方法上。最常見的使用場景是在服務層的方法上使用@Transactional
注解來控制事務。
當在方法上使用@Transactional
注解時,Spring會為這個方法創建一個代理,當這個方法被調用時,代理會首先啟動一個新的事務,然后調用原始的方法。如果這個方法成功完成(即沒有拋出異常),代理會提交事務;如果這個方法拋出了異常,代理會回滾事務。
通過使用@Transactional
注解,我們可以很方便地進行事務控制,無需在業務代碼中添加大量的事務管理代碼,使代碼更加簡潔、清晰。
2. @Transactional注解的使用
2.1 如何在Spring中使用@Transactional
在Spring框架中,我們可以通過在方法上使用@Transactional
注解來聲明該方法是事務性的。當這個方法被調用時,Spring就會為這個方法創建一個事務。
例如,我們可以在一個服務類中的方法上使用@Transactional
注解:
@Service
public class UserService {@Autowiredprivate UserRepository userRepository;@Transactionalpublic void createUser(User user) {userRepository.save(user);}
}
在上述代碼中,createUser
方法被@Transactional
注解,所以當調用createUser
方法時,Spring會為這個方法創建一個事務。
2.2 @Transactional的屬性配置
@Transactional
注解包含一些屬性,我們可以通過設置這些屬性來改變事務的行為。
- propagation:事務的傳播行為。默認值是
Propagation.REQUIRED
,表示如果當前存在事務,則加入該事務;如果當前沒有事務,則創建一個新的事務。 - isolation:事務的隔離級別。默認值是
Isolation.DEFAULT
,表示使用底層數據庫的默認隔離級別。 - timeout:事務的超時時間。默認值是-1,表示永不超時。
- readOnly:是否為只讀事務。默認值是false,表示這是一個讀寫事務。
- rollbackFor:需要進行回滾的異常類數組。默認值是空數組,表示所有的
RuntimeException
及其子類都會導致事務回滾。 - noRollbackFor:不需要進行回滾的異常類數組。默認值是空數組。
例如,我們可以這樣配置@Transactional
注解:
@Transactional(propagation = Propagation.REQUIRES_NEW,isolation = Isolation.READ_COMMITTED,timeout = 3600,readOnly = false,rollbackFor = {CustomException.class},noRollbackFor = {AnotherCustomException.class})
public void someMethod() {// ...
}
在上述代碼中,我們設置了@Transactional
注解的各種屬性,包括傳播行為、隔離級別、超時時間、是否只讀以及回滾規則。
3. @Transactional的工作原理
3.1 Spring如何管理事務
Spring的事務管理分為編程式事務管理和聲明式事務管理兩種。編程式事務管理需要在代碼中顯式地進行事務管理,而聲明式事務管理則通過注解或XML配置來管理事務,大大簡化了事務管理的復雜性。
Spring的事務管理是通過AOP(面向切面編程)來實現的。當你在一個方法上使用@Transactional
注解時,Spring會在運行時為這個方法創建一個代理對象,這個代理對象會包含事務管理的代碼。當你調用這個方法時,實際上是調用了代理對象的方法。
在代理對象的方法中,Spring會首先檢查當前是否存在一個事務。如果不存在,Spring會創建一個新的事務。然后,Spring會執行你的方法。如果你的方法執行成功,Spring會提交事務;如果你的方法執行失敗,并拋出了未被捕獲的異常,Spring會回滾事務。
3.2 @Transactional的底層實現
@Transactional
注解的實現主要依賴于Spring的AOP和事務管理器。
當Spring啟動時,它會創建一個AnnotationTransactionAttributeSource
的實例,這個實例會找到所有使用了@Transactional
注解的方法,并為這些方法生成相應的事務屬性定義。
然后,Spring會創建一個TransactionInterceptor
的實例,這個攔截器會在運行時攔截到所有的事務方法調用,并根據事務屬性定義來決定如何處理事務。
最后,Spring會創建一個ProxyFactoryBean
的實例,這個實例會為所有的事務方法生成代理對象。這些代理對象會在調用事務方法時,首先調用`Transaction
4. @Transactional的傳播行為
4.1 傳播行為的種類
在Spring的@Transactional
注解中,傳播行為(propagation behavior)定義了事務的邊界。Spring定義了7種傳播行為:
Propagation.REQUIRED
:支持當前事務,如果當前沒有事務,就新建一個事務。這是最常見的選擇。Propagation.SUPPORTS
:支持當前事務,如果當前沒有事務,就以非事務方式執行。Propagation.MANDATORY
:支持當前事務,如果當前沒有事務,就拋出異常。Propagation.REQUIRES_NEW
:新建事務,如果當前存在事務,把當前事務掛起。Propagation.NOT_SUPPORTED
:以非事務方式執行操作,如果當前存在事務,就把當前事務掛起。Propagation.NEVER
:以非事務方式執行,如果當前存在事務,則拋出異常。Propagation.NESTED
:如果當前存在事務,則在嵌套事務內執行。如果當前沒有事務,則執行與Propagation.REQUIRED
類似的操作。
4.2 每種傳播行為的具體作用和使用場景
-
Propagation.REQUIRED
:這是最常用的傳播行為。在大多數情況下,我們希望如果一個事務已經存在,那么就在這個事務中執行;否則,就創建一個新的事務。 -
Propagation.SUPPORTS
:這種傳播行為在調用者并不一定需要事務,但是如果有現成的事務可以加入,那么就使用它。如果沒有事務,那么就按非事務方式執行。 -
Propagation.MANDATORY
:這種傳播行為適用于如果沒有一個已經存在的事務,那么就拋出異常的情況。也就是說,這個方法必須在一個已經存在的事務中執行。 -
Propagation.REQUIRES_NEW
:這種傳播行為在每次都需要創建一個新的事務,并且也會將當前的事務(如果存在)掛起,直到新的事務完成。這種傳播行為適用于需要在自己的新事務中執行的情況。 -
Propagation.NOT_SUPPORTED
:這種傳播行為適用于不需要事務的情況。如果有一個已經存在的事務,那么這個事務會被掛起,直到這個方法執行完成。 -
Propagation.NEVER
:這種傳播行為適用于如果存在一個事務,那么就拋出異常的情況。也就是說,這個方法必須在沒有事務的情況下執行。 -
Propagation.NESTED
:這種傳播行為適用于需要在一個嵌套事務中執行的情況。如果當前的事務存在,那么就在這個事務的嵌套事務中執行。如果當前的事務不存在,那么就創建一個新的事務。這種傳播行為需要特定的事務管理器支持。
5. @Transactional的隔離級別
事務的隔離級別是用來解決事務并發執行時可能出現的問題,如臟讀、不可重復讀、幻讀等。不同的隔離級別提供了不同程度的隔離。在Spring的@Transactional
注解中,可以通過設置隔離級別來控制事務的隔離程度。
5.1 事務的隔離級別有哪些
事務的隔離級別主要有以下四種:
ISOLATION_READ_UNCOMMITTED
(讀未提交):這是最低的隔離級別。允許一個事務讀取另一個事務未提交的數據。可能導致臟讀、不可重復讀和幻讀。ISOLATION_READ_COMMITTED
(讀已提交):這個隔離級別允許一個事務讀取另一個事務已提交的數據。可以避免臟讀,但仍可能導致不可重復讀和幻讀。ISOLATION_REPEATABLE_READ
(可重復讀):這個隔離級別確保一個事務在整個過程中多次讀取同一行數據時,每次讀取的結果都是相同的。可以避免臟讀和不可重復讀,但仍可能導致幻讀。ISOLATION_SERIALIZABLE
(串行化):這是最高的隔離級別。它要求事務序列化執行,即一個事務只能在另一個事務完成后才能開始。可以避免臟讀、不可重復讀和幻讀,但執行效率較低。
5.2 如何在@Transactional中設置隔離級別
在@Transactional
注解中,可以通過設置isolation
屬性來指定事務的隔離級別。例如:
@Transactional(isolation = Isolation.READ_COMMITTED)
public void someMethod() {// 業務邏輯
}
在這個例子中,someMethod
方法的事務隔離級別被設置為READ_COMMITTED
。當然,你可以根據實際需求選擇其他的隔離級別。需要注意的是,不同的數據庫可能支持不同的隔離級別,因此在設置隔離級別時,需要考慮數據庫的實際支持情況。
6. @Transactional的只讀設置
在Spring的@Transactional
注解中,可以通過設置readOnly
屬性來指定事務是否為只讀。只讀事務可以幫助數據庫引擎優化事務,提高查詢速度。
6.1 什么是只讀事務
只讀事務是指該事務只進行數據的讀取操作,不進行任何的數據修改操作(包括:INSERT、UPDATE、DELETE等)。在某些場景下,我們知道事務只需要讀取數據,不需要修改數據,這時,我們可以將事務設置為只讀,以幫助數據庫做一些優化,比如避免一些不必要的鎖操作,從而提高查詢效率。
6.2 如何在@Transactional中設置只讀
在@Transactional
注解中,可以通過設置readOnly
屬性來指定事務是否為只讀。例如:
@Transactional(readOnly = true)
public void someMethod() {// 業務邏輯
}
在這個例子中,someMethod
方法的事務被設置為只讀。這意味著,在這個事務中,我們只能進行數據的查詢操作,不能進行數據的修改操作。
需要注意的是,這個只讀設置只是一個提示,具體的行為可能會根據實際的事務管理器的實現有所不同。例如,某些事務管理器可能會在只讀事務中允許進行數據的修改操作,但這通常不是一個好的做法,因為它可能會導致數據的不一致。所以,當我們將一個事務設置為只讀時,我們應該確保在事務中不進行任何數據的修改操作。
7. @Transactional的回滾規則
在Spring的@Transactional
注解中,可以通過設置rollbackFor
和noRollbackFor
屬性來自定義事務的回滾規則。回滾規則決定了在哪些異常情況下事務需要回滾,哪些異常情況下事務可以繼續提交。
7.1 默認的回滾規則是什么
默認情況下,@Transactional
注解的回滾規則是:當事務中發生運行時異常(RuntimeException
)或者錯誤(Error
)時,事務會回滾;當發生受檢異常(Checked Exception
)時,事務不會回滾。
7.2 如何自定義回滾規則
在@Transactional
注解中,可以通過設置rollbackFor
和noRollbackFor
屬性來自定義事務的回滾規則。例如:
@Transactional(rollbackFor = {CustomException.class}, noRollbackFor = {AnotherCustomException.class})
public void someMethod() {// 業務邏輯
}
在這個例子中,我們為someMethod
方法自定義了回滾規則:
- 當方法拋出
CustomException
異常時,事務會回滾。這里的CustomException
可以是任何繼承自Throwable
的自定義異常類。 - 當方法拋出
AnotherCustomException
異常時,事務不會回滾。同樣,AnotherCustomException
可以是任何繼承自Throwable
的自定義異常類。
通過這種方式,我們可以根據實際需求靈活地自定義事務的回滾規則。需要注意的是,如果同時設置了rollbackFor
和noRollbackFor
,那么noRollbackFor
的優先級更高,即如果一個異常同時滿足rollbackFor
和noRollbackFor
的條件,那么事務不會回滾。
8. @Transactional的常見問題
在使用@Transactional
注解時,可能會遇到一些常見的問題。本節將介紹@Transactional
不生效的原因及解決辦法,以及使用@Transactional
的注意事項。
8.1 @Transactional不生效的原因及解決辦法
8.1.1 原因1:事務管理器配置錯誤或缺失
如果事務管理器沒有正確配置或者缺失,@Transactional
注解將無法正常工作。
解決辦法:確保已經正確配置了事務管理器。對于Spring Boot項目,通常會自動配置事務管理器。對于非Spring Boot項目,需要手動配置事務管理器,例如:
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"><property name="dataSource" ref="dataSource"/>
</bean>
8.1.2 原因2:使用@Transactional注解的方法在同一個類中調用
當一個沒有使用@Transactional
注解的方法調用同一個類中的使用@Transactional
注解的方法時,事務注解不會生效。
解決辦法1:將使用@Transactional
注解的方法移到另一個類中,然后在原類中通過依賴注入的方式調用這個方法。例如:
@Service
public class AService {@Autowiredprivate BService bService;public void methodA() {bService.methodB();}
}@Service
public class BService {@Transactionalpublic void methodB() {// 業務邏輯}
}
解決辦法2:將使用 SpringContextUtil.getBean(this.getClass()).methodB()
從spring中再獲取一邊當前bean的代理對象并調用。例如:
@Service
public class BService {public void methodA() {SpringContextUtil.getBean(this.getClass()).methodB();}@Transactionalpublic void methodB() {// 業務邏輯}
}
在Spring中,當一個類中的一個方法調用另一個帶有@Transactional
注解的方法時,通常情況下,@Transactional
注解不會生效。這是因為Spring事務管理是基于代理的,當一個方法內部調用另一個方法時,實際上是在同一個對象實例上進行的調用,而不是通過代理對象。因此,事務管理功能不會被觸發。
然而,通過使用SpringContextUtil.getBean(this.getClass()).methodB();
這種寫法,我們可以繞過這個限制。這是因為SpringContextUtil.getBean(this.getClass())
會從Spring容器中獲取當前類的代理對象,然后通過這個代理對象調用methodB()
方法。由于調用是通過代理對象進行的,因此@Transactional
注解會生效,觸發事務管理功能。
請注意,這種寫法雖然可以解決問題,但并不是最佳實踐。在實際項目中,我們應該盡量避免在一個類的方法中直接調用另一個帶有@Transactional
注解的方法。更好的做法是將這兩個方法分別放在不同的服務類中,然后通過依賴注入的方式來調用這些服務類的方法。這樣可以更好地遵循單一職責原則,使代碼更加清晰和可維護。
8.1.3 原因3:事務傳播行為配置不正確
如果事務傳播行為配置不正確,可能會導致@Transactional
注解不生效。
解決辦法:檢查@Transactional
注解的propagation
屬性設置,確保它符合實際需求。根據不同的業務場景,選擇合適的事務傳播行為。
8.2 @Transactional的使用注意事項
-
確保事務管理器配置正確。對于Spring Boot項目,通常會自動配置事務管理器。對于非Spring Boot項目,需要手動配置事務管理器。
-
避免在同一個類中調用使用
@Transactional
注解的方法。將使用@Transactional
注解的方法移到另一個類中,然后在原類中通過依賴注入的方式調用這個方法。 -
根據實際需求,合理設置事務的傳播行為、隔離級別、超時時間、只讀屬性等。
-
不要在
private
、protected
、final
方法上使用@Transactional
注解,因為這些方法無法被代理,導致@Transactional
注解無法生效。 -
如果需要自定義事務的回滾規則,可以通過設置
rollbackFor
和noRollbackFor
屬性來實現。注意noRollbackFor
的優先級更高。 -
在使用
@Transactional
注解時,盡量遵循最小化事務范圍的原則,即只在需要進行事務控制的方法上使用@Transactional
注解。這樣可以降低事務的開銷,提高系統性能。
假設我們正在開發一個電商系統,系統中有一個訂單服務(OrderService)和庫存服務(InventoryService)。當用戶下單時,需要同時更新訂單和庫存信息。為了保證數據的一致性,我們可以使用@Transactional
注解來確保這兩個操作在同一個事務中完成。如果其中一個操作失敗,整個事務將回滾,從而避免數據不一致的問題。
首先,我們創建訂單服務(OrderService)和庫存服務(InventoryService):
@Service
public class OrderService {public void createOrder() {// 創建訂單的邏輯}
}@Service
public class InventoryService {public void updateInventory() {// 更新庫存的邏輯}
}
9.實例
接下來,我們創建一個業務服務(BusinessService),該服務將負責調用訂單服務和庫存服務。在該服務中,我們使用@Transactional
注解來確保這兩個操作在同一個事務中完成:
@Service
public class BusinessService {@Autowiredprivate OrderService orderService;@Autowiredprivate InventoryService inventoryService;@Transactionalpublic void placeOrder() {try {orderService.createOrder();inventoryService.updateInventory();} catch (Exception e) {// 處理異常,例如記錄日志等throw e;}}
}
現在,當我們調用BusinessService
的placeOrder
方法時,訂單創建和庫存更新操作將在同一個事務中進行。如果其中一個操作失敗,整個事務將回滾,從而確保數據的一致性。
這個例子展示了如何使用@Transactional
注解解決實際問題。在實際項目中,我們可以根據業務需求靈活地使用@Transactional
注解來控制事務,確保數據的一致性和完整性。