前面幾篇博文介紹了聲明式事務@Transactional
的使用姿式,只知道正確的使用姿式可能還不夠,還得知道什麼場景下不生效,避免採坑。本文將主要介紹讓事務不生效的幾種 casemysql
<!-- more -->git
本文的 case,將使用聲明式事務,首先咱們建立一個 SpringBoot 項目,版本爲2.2.1.RELEASE
,使用 mysql 做爲目標數據庫,存儲引擎選擇Innodb
,事務隔離級別爲 RRgithub
在項目pom.xml
文件中,加上spring-boot-starter-jdbc
,會注入一個DataSourceTransactionManager
的 bean,提供了事務支持spring
<dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency>
進入 spring 配置文件application.properties
,設置一下 db 相關的信息sql
## DataSource spring.datasource.url=jdbc:mysql://127.0.0.1:3306/story?useUnicode=true&characterEncoding=UTF-8&useSSL=false spring.datasource.username=root spring.datasource.password=
新建一個簡單的表結構,用於測試數據庫
CREATE TABLE `money` ( `id` int(11) unsigned NOT NULL AUTO_INCREMENT, `name` varchar(20) NOT NULL DEFAULT '' COMMENT '用戶名', `money` int(26) NOT NULL DEFAULT '0' COMMENT '錢', `is_deleted` tinyint(1) NOT NULL DEFAULT '0', `create_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '建立時間', `update_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新時間', PRIMARY KEY (`id`), KEY `name` (`name`) ) ENGINE=InnoDB AUTO_INCREMENT=551 DEFAULT CHARSET=utf8mb4;
在聲明式事務的使用教程200119-SpringBoot 系列教程之聲明式事務 Transactional 中,也提到了一些事務不生效的方式,好比聲明式事務註解@Transactional
主要是結合代理實現,結合 AOP 的知識點,至少能夠得出放在私有方法上,類內部調用都不會生效,下面進入詳細說明多線程
事務生效的前提是你的數據源得支持事務,好比 mysql 的 MyISAM 引擎就不支持事務,而 Innodb 支持事務app
下面的 case 都是基於 mysql + Innodb 引擎ide
爲後續的演示 case,咱們準備一些數據以下
@Service public class NotEffectDemo { @Autowired private JdbcTemplate jdbcTemplate; @PostConstruct public void init() { String sql = "replace into money (id, name, money) values" + " (520, '初始化', 200)," + "(530, '初始化', 200)," + "(540, '初始化', 200)," + "(550, '初始化', 200)"; jdbcTemplate.execute(sql); } }
簡單來說就是指非直接訪問帶註解標記的方法 B,而是經過類普通方法 A,而後由 A 訪問 B
下面是一個簡單的 case
/** * 非直接調用,不生效 * * @param id * @return * @throws Exception */ @Transactional(rollbackFor = Exception.class) public boolean testCompileException2(int id) throws Exception { if (this.updateName(id)) { this.query("after update name", id); if (this.update(id)) { return true; } } throw new Exception("參數異常"); } public boolean testCall(int id) throws Exception { return testCompileException2(id); }
上面兩個方法,直接調用testCompleException
方法,事務正常操做;經過調用testCall
間接訪問,在不生效
測試 case 以下:
@Component public class NotEffectSample { @Autowired private NotEffectDemo notEffectDemo; public void testNotEffect() { testCall(530, (id) -> notEffectDemo.testCall(530)); } private void testCall(int id, CallFunc<Integer, Boolean> func) { System.out.println("============ 事務不生效case start ========== "); notEffectDemo.query("transaction before", id); try { // 事務能夠正常工做 func.apply(id); } catch (Exception e) { } notEffectDemo.query("transaction end", id); System.out.println("============ 事務不生效case end ========== \n"); } @FunctionalInterface public interface CallFunc<T, R> { R apply(T t) throws Exception; } }
輸出結果以下:
============ 事務不生效case start ========== transaction before >>>> {id=530, name=初始化, money=200, is_deleted=false, create_at=2020-02-03 13:44:11.0, update_at=2020-02-03 13:44:11.0} after update name >>>> {id=530, name=更新, money=200, is_deleted=false, create_at=2020-02-03 13:44:11.0, update_at=2020-02-03 13:44:11.0} transaction end >>>> {id=530, name=更新, money=210, is_deleted=false, create_at=2020-02-03 13:44:11.0, update_at=2020-02-03 13:44:11.0} ============ 事務不生效case end ==========
從上面的輸出能夠看到,事務並無回滾,主要是由於類內部調用,不會經過代理方式訪問
在私有方法上,添加@Transactional
註解也不會生效,私有方法外部不能訪問,因此只能內部訪問,上面的 case 不生效,這個固然也不生效了
/** * 私有方法上的註解,不生效 * * @param id * @return * @throws Exception */ @Transactional private boolean testSpecialException(int id) throws Exception { if (this.updateName(id)) { this.query("after update name", id); if (this.update(id)) { return true; } } throw new Exception("參數異常"); }
直接使用時,下面這種場景不太容易出現,由於 IDEA 會有提醒,文案爲: Methods annotated with '@Transactional' must be overridable
@Transactional
註解默認處理運行時異常,即只有拋出運行時異常時,纔會觸發事務回滾,不然並不會如
/** * 非運行異常,且沒有經過 rollbackFor 指定拋出的異常,不生效 * * @param id * @return * @throws Exception */ @Transactional public boolean testCompleException(int id) throws Exception { if (this.updateName(id)) { this.query("after update name", id); if (this.update(id)) { return true; } } throw new Exception("參數異常"); }
測試 case 以下
public void testNotEffect() { testCall(520, (id) -> notEffectDemo.testCompleException(520)); }
輸出結果以下,事務並未回滾(若是須要解決這個問題,經過設置@Transactional
的 rollbackFor 屬性便可)
============ 事務不生效case start ========== transaction before >>>> {id=520, name=初始化, money=200, is_deleted=false, create_at=2020-02-03 13:44:11.0, update_at=2020-02-03 13:44:11.0} after update name >>>> {id=520, name=更新, money=200, is_deleted=false, create_at=2020-02-03 13:44:11.0, update_at=2020-02-03 13:44:11.0} transaction end >>>> {id=520, name=更新, money=210, is_deleted=false, create_at=2020-02-03 13:44:11.0, update_at=2020-02-03 13:44:11.0} ============ 事務不生效case end ==========
這個場景可能並很少見,在標記事務的方法內部,另起子線程執行 db 操做,此時事務一樣不會生效
下面給出兩個不一樣的姿式,一個是子線程拋異常,主線程 ok;一個是子線程 ok,主線程拋異常
/** * 子線程拋異常,主線程沒法捕獲,致使事務不生效 * * @param id * @return */ @Transactional(rollbackFor = Exception.class) public boolean testMultThread(int id) throws InterruptedException { new Thread(new Runnable() { @Override public void run() { updateName(id); query("after update name", id); } }).start(); new Thread(new Runnable() { @Override public void run() { boolean ans = update(id); query("after update id", id); if (!ans) { throw new RuntimeException("failed to update ans"); } } }).start(); Thread.sleep(1000); System.out.println("------- 子線程 --------"); return true; }
上面這種場景不生效很好理解,子線程的異常不會被外部的線程捕獲,testMultThread
這個方法的調用不拋異常,所以不會觸發事務回滾
public void testNotEffect() { testCall(540, (id) -> notEffectDemo.testMultThread(540)); }
輸出結果以下
============ 事務不生效case start ========== transaction before >>>> {id=540, name=初始化, money=200, is_deleted=false, create_at=2020-02-03 13:44:11.0, update_at=2020-02-03 13:44:11.0} after update name >>>> {id=540, name=更新, money=200, is_deleted=false, create_at=2020-02-03 13:44:11.0, update_at=2020-02-03 13:44:11.0} Exception in thread "Thread-3" java.lang.RuntimeException: failed to update ans at com.git.hui.boot.jdbc.demo.NotEffectDemo$2.run(NotEffectDemo.java:112) at java.lang.Thread.run(Thread.java:748) after update id >>>> {id=540, name=更新, money=210, is_deleted=false, create_at=2020-02-03 13:44:11.0, update_at=2020-02-03 13:44:11.0} ------- 子線程 -------- transaction end >>>> {id=540, name=更新, money=210, is_deleted=false, create_at=2020-02-03 13:44:11.0, update_at=2020-02-03 13:44:11.0} ============ 事務不生效case end ==========
/** * 子線程拋異常,主線程沒法捕獲,致使事務不生效 * * @param id * @return */ @Transactional(rollbackFor = Exception.class) public boolean testMultThread2(int id) throws InterruptedException { new Thread(new Runnable() { @Override public void run() { updateName(id); query("after update name", id); } }).start(); new Thread(new Runnable() { @Override public void run() { boolean ans = update(id); query("after update id", id); } }).start(); Thread.sleep(1000); System.out.println("------- 子線程 --------"); update(id); query("after outer update id", id); throw new RuntimeException("failed to update ans"); }
上面這個看着好像沒有毛病,拋出線程,事務回滾,惋惜兩個子線程的修改並不會被回滾
測試代碼
public void testNotEffect() { testCall(550, (id) -> notEffectDemo.testMultThread2(550)); }
從下面的輸出也能夠知道,子線程的修改並不在同一個事務內,不會被回滾
============ 事務不生效case start ========== transaction before >>>> {id=550, name=初始化, money=200, is_deleted=false, create_at=2020-02-03 13:52:38.0, update_at=2020-02-03 13:52:38.0} after update name >>>> {id=550, name=更新, money=200, is_deleted=false, create_at=2020-02-03 13:52:38.0, update_at=2020-02-03 13:52:40.0} after update id >>>> {id=550, name=更新, money=210, is_deleted=false, create_at=2020-02-03 13:52:38.0, update_at=2020-02-03 13:52:40.0} ------- 子線程 -------- after outer update id >>>> {id=550, name=更新, money=220, is_deleted=false, create_at=2020-02-03 13:52:38.0, update_at=2020-02-03 13:52:41.0} transaction end >>>> {id=550, name=更新, money=210, is_deleted=false, create_at=2020-02-03 13:52:38.0, update_at=2020-02-03 13:52:40.0} ============ 事務不生效case end ==========
上一篇關於傳播屬性的博文中,介紹了其中有幾種是不走事務執行的,因此也須要額外注意下,詳情能夠參考博文 200202-SpringBoot 系列教程之事務傳遞屬性
下面小結幾種@Transactional
註解事務不生效的 case
系列博文
源碼
盡信書則不如,以上內容,純屬一家之言,因我的能力有限,不免有疏漏和錯誤之處,如發現 bug 或者有更好的建議,歡迎批評指正,不吝感激
下面一灰灰的我的博客,記錄全部學習和工做中的博文,歡迎你們前去逛逛