數據庫事務,是指作為單個邏輯工作單元執行的一系列操作,要么完全地執行,要么完全地不執行。
事務處理可以確保除非事務性單元內的所有操作都成功完成,否則不會永久更新面向數據的資源。通過將一組相關操作組合為一個要么全部成功要么全部失敗的單元,可以簡化錯誤恢復并使應用程序更加可靠。
一個邏輯工作單元要成為事務,必須滿足所謂的 ACID (原子性、一致性、隔離性和持久性)屬性。事務是數據庫運行中的邏輯工作單位。
實際工作中我們更多的是結合 Spring 來做項目的這時我們要滿足的情況是這種。
Controller層: UserService:addUser(); Service層(UserService): addUser(): insertUser()+ insertLog() Dao層: UserDao:insertUser(); LogDao: insertDao();
可以看出我們在 Service 中是可能調用多個 Dao 的方法來操作數據庫中的數據的,我們要做的就是要保證 UserService 中的 addUser() 方法中的相關操作滿足事務的要求
常見的開啟 Spring 事務方式:@Transactional
事務開啟后,通過 AOP 機制生成一個代理數據庫連接對象并將其放入 DataSource 實例的某個 DataSourceTransactionManager 相關對象容器中。在整個事務中,業務代碼中所有的數據庫連接都應該是同一個連接,不使用該連接的 Sql 是不會被回滾的。業務代碼出現異常時會執行回滾操作
底層實現:
圖片
隔離級別( @Transactional ( isolation = Isolation.DEFAULT ) ): 為了解決數據庫容易出現的問題,分級加鎖處理策略
隔離級別 | 描述 |
| Spring 默認隔離級別,以連接的數據庫的事務隔離級別為準;Mysql (可重復讀) |
| 讀未提交:最低的隔離級別,其含義是允許一個事務讀取另外一個事務沒有提交的數據。未提交讀是一種危險的隔離級別,所以一般在我們實際的開發中應用不廣,但是它的優點在于并發能力高,適合那些對數據一致性沒有要求而追求高并發的場景,它的最大壞處是出現臟讀 |
| 讀已提交:是指一個事務只能讀取另外一個事務已經提交的數據,不能讀取未提交的數據 |
| 可重復讀:目標是克服讀寫提交中出現的不可重復讀的現象,因為在讀寫提交的時候,可能出現一些值的變化,影響當前事務的執行 |
| 串行化,數據庫最高的隔離級別,它會要求所有的 SQL 都會按照順序執行,這樣就可以克服上述隔離級別出現的各種問題,所以它能夠完全保證數據的一致性 |
超時時間 ( @Transactional ( timeout = 30 ) ): 定義一個事務執行過程多久算超時,以便超時后回滾。可以防止長期運行的事務占用資源.對應注解中的屬性 timeout (注意點:這個超時時間在數據庫事務超時的范疇內的)
是否只讀 ( @Transactional ( readOnly = true ) ):表示這個事務只讀取數據但不更新數據
回滾機制( @Transactional ( rollbackFor = Exception.class ):定義遇到異常時回滾策略
傳播機制( @Transactional ( propagation = Propagation.REQUIRED ): 對事務的傳播特性進行定義,共有 7 種類型 (一個事務內調用另外一個事務)
事務行為 | 說明 |
PROPAGATION_REQUIRED | 如果當前上下文中存在事務,那么加入該事務,如果不存在事務,創建一個事務,這是默認的傳播屬性值 |
PROPAGATION_SUPPORTS | 如果當前上下文存在事務,則支持事務加入事務,如果不存在事務,則使用非事務的方式執行 |
PROPAGATION_MANDATORY | 支持當前事務,假設當前沒有事務,就拋出異常 |
PROPAGATION_REQUIRES_NEW | 每次都會新建一個事務,并且同時將上下文中的事務掛起,執行當前新建事務完成以后,上下文事務恢復再執行 |
PROPAGATION_NOT_SUPPORTED | 如果當前上下文中存在事務,則掛起當前事務,然后新的方法在沒有事務的環境中執行 |
PROPAGATION_NEVER | 如果當前上下文中存在事務,則拋出異常,否則在無事務環境上執行代碼 |
PROPAGATION_NESTED | 如果當前存在事務,則在嵌套事務內執行。如果當前沒有事務,則執行與 PROPAGATION_REQUIRED 類似的操作。 |
示例代碼:
@Servicepublic class UserService { @Transactional private void add(UserModel userModel) { saveData(userModel); updateData(userModel); }}
原因:Spring 要求被代理方法必須得是 public 的
也就是說,如果我們自定義的事務方法(即目標方法),它的訪問權限不是 public,而是 private、 default 或 protected 的話, Spring 則不會提供事務功能
示例代碼:
@Servicepublic class UserService { @Transactional public final void add(UserModel userModel){ saveData(userModel); updateData(userModel); }}
原因:
Spring 事務底層使用了 AOP,也就是通過 JDK 動態代理或者 CGLIB,幫我們生成了代理類,在代理類中實現的事務功能。但如果某個方法用 final 修飾了,那么在它的代理類中,就無法重寫該方法,而添加事務功能。
注意:如果某個方法是 static 修飾的,同樣無法通過動態代理,變成事務方法。
示例代碼:
@Servicepublic class UserService { @Autowired private UserMapper userMapper; public void add(UserModel userModel) { userMapper.insertUser(userModel); updateStatus(userModel); } @Transactional public void updateStatus(UserModel userModel) { doSameThing(); }}
原因:
我們看到在事務方法 add 中,直接調用事務方法 updateStatus。從前面介紹的內容可以知道, updateStatus 方法擁有事務的能力是因為 Spring AOP 生成代理了對象,但是這種方法直接調用了 this 對象的方法,并不會從 IOC 拿到加上 AOP 事務相關方法的動態代理對象 所以 updateStatus 方法不會生成事務
示例代碼:
//@Servicepublic class UserService { @Transactional public void add(UserModel userModel) { saveData(userModel); updateData(userModel); } }
原因:
使用 Spring 事務的前提是:對象要被 Spring IOC 管理,需要創建 bean 實例
示例代碼:
@Servicepublic class UserService { @Autowired private UserMapper userMapper; @Autowired private RoleService roleService; @Transactional public void add(UserModel userModel) throws Exception { userMapper.insertUser(userModel); new Thread(() -> { roleService.doOtherThing(); }).start(); }}@Servicepublic class RoleService { @Transactional public void doOtherThing() { }}
原因:同一個事務,其實是指同一個數據庫連接,只有擁有同一個數據庫連接才能同時提交和回滾。如果在不同的線程,拿到的數據庫連接肯定是不一樣的,所以是不同的事務。
示例代碼:
@Servicepublic class UserService { @Autowired private UserMapper userMapper; @Autowired private RoleService roleService; @Transactional(propagation = Propagation.REQUIRED) public void add(UserModel userModel) throws Exception { userMapper.insertUser(userModel); new Thread(() -> { roleService.doOtherThing(); }).start(); }}@Servicepublic class RoleService { @Transactional(propagation = Propagation.NEVER) public void doOtherThing() { }}
原因:RoleService 中 doOtherThing() 方法上設置的事物傳播類型為 Propagation.NEVER,即存在事務就拋出異常
示例代碼:
@Slf4j@Servicepublic class UserService { @Transactional public void add(UserModel userModel) { try { saveData(userModel); updateData(userModel); } catch (Exception e) { log.error(e.getMessage(), e); } }}
原因:如果想要 Spring 事務能夠正常回滾,必須拋出它能夠處理的異常。如果沒有拋異常,則 Spring 認為程序是正常的。
示例代碼:
@Slf4j@Servicepublic class UserService { @Transactional public void add(UserModel userModel) throws Exception { saveData(userModel); updateData(userModel); }}
原因:@Transactional 默認的異常類型是 RuntimeException,如果出現非 RuntimeException,則 Spring 事務處理不了對應的異常,認為程序是正常的,則不會回滾事務,此時我們可以指定異常類型如 @Transactional(rollbackFor = Exception.class)
比如 Mysql 中的 MyISAM 引擎是不支持事務操作的, InnoDB 才是支持事務的引擎
本文通過對 @Transactional 注解相關介紹,列舉出可能會出現事務失效的場景。發生最多就是自身調用、異常被吃、異常拋出類型不匹配這三個。由于平時業務繁重,有時候會忽視 @Transactional 注解使用規范,導致事務沒有生效或者沒有正常回滾,造成較大的數據異常。希望可以幫助大家日常使用 @Transactional 時避坑。
本文鏈接:http://www.tebozhan.com/showinfo-26-11732-0.html@Transactional注解使用以及事務失效的場景
聲明:本網頁內容旨在傳播知識,若有侵權等問題請及時與本網聯系,我們將在第一時間刪除處理。郵件:2376512515@qq.com
上一篇: 玩轉SpringBoot—自動裝配解決Bean的復雜配置
下一篇: React中的依賴注入,看這一篇就夠了!