引言?
在 Spring Boot 中,事務管理是一個非常重要的功能,尤其是在涉及數據庫操作的業務場景中。Spring 提供了強大的事務管理支持,能夠幫助我們簡化事務的管理和控制。本文將詳細介紹 Spring Boot 中事務的用法,包括事務的基本概念、事務的配置、事務的傳播行為、事務的隔離級別以及事務的回滾機制。
1. 事務的基本概念
事務(Transaction)是指一組數據庫操作,這些操作要么全部成功,要么全部失敗。事務的四大特性(ACID)包括:
-
原子性(Atomicity):事務中的所有操作要么全部成功,要么全部失敗。
-
一致性(Consistency):事務執行前后,數據庫的狀態保持一致。
-
隔離性(Isolation):多個事務并發執行時,彼此之間互不干擾。
-
持久性(Durability):事務一旦提交,對數據庫的修改是永久性的。
在 Spring Boot 中,事務管理是通過?@Transactional
?注解來實現的。
2. Spring Boot 中事務的配置
2.1 啟用事務管理
Spring Boot 默認已經集成了事務管理功能,只需要在配置類或啟動類上添加?@EnableTransactionManagement
?注解即可啟用事務管理。
@SpringBootApplication
@EnableTransactionManagement // 啟用事務管理
public class MyApplication {public static void main(String[] args) {SpringApplication.run(MyApplication.class, args);}
}
2.2 配置數據源和事務管理器
Spring Boot 默認使用?DataSourceTransactionManager
?作為事務管理器。如果你使用的是 Spring Data JPA,事務管理器會自動配置。
# application.yml
spring:datasource:url: jdbc:mysql://localhost:3306/mydbusername: rootpassword: rootdriver-class-name: com.mysql.cj.jdbc.Driver
3. 使用?@Transactional
?注解
@Transactional
?是 Spring 提供的事務管理注解,可以標注在類或方法上。標注在類上時,表示該類中的所有方法都啟用事務管理;標注在方法上時,表示該方法啟用事務管理。
@Service
public class UserService {@Autowiredprivate UserRepository userRepository;@Transactional // 開啟事務 表示該方法開啟了事務public void createUser(User user) {userRepository.save(user);}
}
3.2 事務的傳播行為
事務的傳播行為(Propagation)定義了事務方法之間的調用關系。Spring 提供了以下幾種傳播行為:
-
REQUIRED(默認):如果當前存在事務,則加入該事務;如果當前沒有事務,則創建一個新的事務。(適用于大多數業務場景,尤其是需要保證多個操作在同一個事務中執行的場景。 例如,訂單創建時需要同時更新訂單表和庫存表。)
-
REQUIRES_NEW:無論當前是否存在事務,都創建一個新的事務。(適用于需要獨立事務的場景,尤其是日志記錄、審計等操作。 例如,記錄操作日志時,即使主事務失敗,日志記錄仍然需要成功。)
-
SUPPORTS:如果當前存在事務,則加入該事務;如果當前沒有事務,則以非事務方式執行。(適用于不需要強制事務的場景,例如查詢操作。 例如,查詢用戶信息時,如果調用方有事務,則加入事務;如果沒有事務,則以非事務方式執行。)
-
NOT_SUPPORTED:以非事務方式執行操作,如果當前存在事務,則掛起該事務。(適用于不需要事務支持的場景,例如發送消息、調用外部接口等。 例如,發送短信通知時,不需要事務支持。)
-
MANDATORY:如果當前存在事務,則加入該事務;如果當前沒有事務,則拋出異常。(適用于強制要求調用方必須有事務的場景。 例如,某些核心業務邏輯必須在一個事務中執行。)
-
NEVER:以非事務方式執行操作,如果當前存在事務,則拋出異常。(適用于強制要求調用方不能有事務的場景。 例如,某些只讀操作或外部調用。)
-
NESTED:如果當前存在事務,則在嵌套事務內執行;如果當前沒有事務,則創建一個新的事務。(適用于需要部分回滾的場景。 例如,訂單創建時需要更新多個表,如果某個表更新失敗,只需要回滾該表的操作,而不影響其他表的操作。)
-
示例:
@Transactional(propagation = Propagation.REQUIRES_NEW) public void updateUser(User user) {userRepository.save(user); }
3.3 事務的隔離級別
事務的隔離級別(Isolation)定義了事務之間的可見性。Spring 支持以下幾種隔離級別:
-
DEFAULT:使用數據庫的默認隔離級別。(適用于大多數通用場景,尤其是當你對數據庫的默認行為沒有特殊要求時。 如果你不確定應該選擇哪種隔離級別,可以使用 DEFAULT,讓數據庫根據其默認行為處理事務。)
-
READ_UNCOMMITTED:允許讀取未提交的數據變更,可能會導致臟讀、幻讀和不可重復讀。(適用于對數據一致性要求不高的場景,例如統計數據的讀取或日志記錄。 不適用于涉及資金、訂單等對數據一致性要求高的場景。)
-
READ_COMMITTED:只能讀取已提交的數據,可以避免臟讀,但可能會導致幻讀和不可重復讀。(適用于大多數業務場景,尤其是對數據一致性有一定要求但不需要嚴格隔離的場景。 例如,電商系統中的訂單查詢、用戶信息查詢等。)
-
REPEATABLE_READ:確保在同一事務中多次讀取同一數據時結果一致,可以避免臟讀和不可重復讀,但可能會導致幻讀。(適用于對數據一致性要求較高的場景,例如銀行系統中的賬戶余額查詢。 例如,在一個事務中多次讀取同一賬戶的余額時,確保結果一致。)
-
SERIALIZABLE:最高隔離級別,確保事務串行執行,可以避免臟讀、幻讀和不可重復讀(適用于對數據一致性要求極高的場景,例如金融系統中的資金清算、庫存管理等。 由于性能開銷較大,通常只在必要時使用。)
-
示例:
@Transactional(isolation = Isolation.READ_COMMITTED) public User getUserById(Long id) {return userRepository.findById(id).orElse(null); }
3.4 事務的回滾機制
默認情況下,Spring 會在方法拋出?
RuntimeException
?或?Error
?時回滾事務。如果需要自定義回滾規則,可以通過?rollbackFor(哪些異常回滾)
?和?noRollbackFor(哪些異常不會回滾)
?屬性來指定。示例:
@Transactional(rollbackFor = Exception.class) // 所有異常都回滾 public void updateUser(User user) throws Exception {userRepository.save(user);if (user.getName() == null) {throw new Exception("用戶名不能為空"); // 拋出受檢異常} }
4. 事務的嵌套與傳播行為
在復雜的業務場景中,可能會存在事務方法調用事務方法的情況。此時,事務的傳播行為決定了事務的嵌套方式。
-
4.1 嵌套事務示例
@Service public class OrderService {@Autowiredprivate UserService userService;@Transactional //一級事務public void createOrder(Order order) {// 保存訂單orderRepository.save(order);// 調用另一個事務方法userService.updateUser(order.getUser());} }@Service public class UserService {@Transactional(propagation = Propagation.REQUIRES_NEW) //二級事務public void updateUser(User user) {userRepository.save(user);} }
在上面的示例中,
createOrder
?方法調用?updateUser
?方法時,updateUser
?方法會開啟一個新的事務。
5. 事務的注意事項(事務不生效的幾種情況)
-
事務方法的可見性:
-
@Transactional
?只能應用于?public
?方法。如果應用于?private
?或?protected
?方法,事務將不會生效。Spring 的事務管理是基于代理模式實現的,代理對象只能攔截?public
?方法。對于?private
?或?protected
?方法,Spring 無法生成代理,因此事務不會生效。@Service public class UserService {@Autowiredprivate UserRepository userRepository;// 正確:public 方法,事務生效@Transactionalpublic void createUser(User user) {userRepository.save(user);}// 錯誤:private 方法,事務不會生效@Transactionalprivate void updateUser(User user) {userRepository.save(user);}// 錯誤:protected 方法,事務不會生效@Transactionalprotected void deleteUser(Long userId) {userRepository.deleteById(userId);} }
-
-
事務的自我調用問題:
-
如果事務方法調用了同一個類中的另一個事務方法,事務的傳播行為可能不會生效。這是因為Spring 的代理對象只能攔截從外部調用的方法。如果事務方法在同一個類中調用另一個事務方法,實際上是直接調用目標方法,而不是通過代理對象調用,因此事務的傳播行為不會生效。
@Service public class OrderService {@Autowiredprivate OrderRepository orderRepository;@Transactionalpublic void createOrder(Order order) {// 保存訂單orderRepository.save(order);// 調用另一個事務方法(自我調用)updateInventory(order.getProductId(), order.getQuantity());}@Transactional(propagation = Propagation.REQUIRES_NEW)public void updateInventory(Long productId, int quantity) {// 更新庫存邏輯} }
在上面的示例中,
createOrder
?方法調用了?updateInventory
?方法,但由于是自我調用,updateInventory
?方法的事務傳播行為(REQUIRES_NEW
)不會生效。 -
解決方法:
-
將事務方法拆分到不同的類中:
將?updateInventory
?方法移到另一個服務類中,通過依賴注入調用。@Service public class OrderService {@Autowiredprivate OrderRepository orderRepository;@Autowiredprivate InventoryService inventoryService;@Transactionalpublic void createOrder(Order order) {// 保存訂單orderRepository.save(order);// 調用另一個服務類的事務方法inventoryService.updateInventory(order.getProductId(), order.getQuantity());} }@Service public class InventoryService {@Transactional(propagation = Propagation.REQUIRES_NEW)public void updateInventory(Long productId, int quantity) {// 更新庫存邏輯} }
-
-
事務的超時設置:
-
可以通過?
@Transactional(timeout = 10)
?設置事務的超時時間(單位為秒)。如果事務執行時間超過指定時間,事務將自動回滾。@Service public class ReportService {@Autowiredprivate ReportRepository reportRepository;@Transactional(timeout = 10) // 設置事務超時時間為 10 秒public void generateReport() {// 模擬耗時操作for (int i = 0; i < 1000000; i++) {reportRepository.save(new Report("Report " + i));}} }
在上面的示例中,如果?
generateReport
?方法的執行時間超過 10 秒,事務將自動回滾。
-
?
6. 總結
Spring Boot 提供了強大的事務管理功能,通過?@Transactional
?注解可以輕松實現事務的控制。在實際開發中,需要根據業務需求選擇合適的傳播行為和隔離級別,同時注意事務方法的可見性和自我調用問題。
通過本文的介紹,相信你已經掌握了 Spring Boot 中事務的基本用法。如果你有更多問題,歡迎在評論區留言討論!