對於mysql而言,關於事務的主要知識點可能幾種在隔離級別上;在Spring體系中,使用事務的時候,還有一個知識點事務的傳遞屬性一樣重要,本文將主要介紹7中傳遞屬性的使用場景mysql
<!-- 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;
在正式開始以前,得先準備一些基礎數據app
@Component public class PropagationDemo { @Autowired private JdbcTemplate jdbcTemplate; @PostConstruct public void init() { String sql = "replace into money (id, name, money) values (420, '初始化', 200)," + "(430, '初始化', 200)," + "(440, '初始化', 200)," + "(450, '初始化', 200)," + "(460, '初始化', 200)," + "(470, '初始化', 200)," + "(480, '初始化', 200)," + "(490, '初始化', 200)"; jdbcTemplate.execute(sql); } }
其次測試事務的使用,咱們須要額外建立一個測試類,後面的測試case都放在類PropagationSample
中; 爲了使輸出結果更加友好,提供了一個封裝的call方法spring-boot
@Component public class PropagationSample { @Autowired private PropagationDemo propagationDemo; private void call(String tag, int id, CallFunc<Integer> func) { System.out.println("============ " + tag + " start ========== "); propagationDemo.query(tag, id); try { func.apply(id); } catch (Exception e) { System.out.println(e.getMessage()); } propagationDemo.query(tag, id); System.out.println("============ " + tag + " end ========== \n"); } @FunctionalInterface public interface CallFunc<T> { void apply(T t) throws Exception; } }
也是默認的傳遞屬性,其特色在於學習
使用方式也比較簡單,不設置@Transactional
註解的propagation屬性,或者設置爲 REQUIRED便可
/** * 若是存在一個事務,則支持當前事務。若是沒有事務則開啓一個新的事務 * * @param id */ @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class) public void required(int id) throws Exception { if (this.updateName(id)) { this.query("required: after updateMoney name", id); if (this.updateMoney(id)) { return; } } throw new Exception("事務回滾!!!"); }
上面就是一個基礎的使用姿式
private void testRequired() { int id = 420; call("Required事務運行", id, propagationDemo::required); }
輸出結果以下
============ Required事務運行 start ========== Required事務運行 >>>> {id=420, name=初始化, money=200, is_deleted=false, create_at=2020-02-02 15:23:26.0, update_at=2020-02-02 15:23:26.0} required: after updateMoney name >>>> {id=420, name=更新, money=200, is_deleted=false, create_at=2020-02-02 15:23:26.0, update_at=2020-02-02 15:23:46.0} 事務回滾!!! Required事務運行 >>>> {id=420, name=初始化, money=200, is_deleted=false, create_at=2020-02-02 15:23:26.0, update_at=2020-02-02 15:23:26.0} ============ Required事務運行 end ==========
其特色是在事務裏面,就事務執行;不然就非事務執行,即
使用姿式和前面基本一致
@Transactional(propagation = Propagation.SUPPORTS, rollbackFor = Exception.class) public void support(int id) throws Exception { if (this.updateName(id)) { this.query("support: after updateMoney name", id); if (this.updateMoney(id)) { return; } } throw new Exception("事務回滾!!!"); }
這個傳遞屬性比較特別,因此咱們的測試case須要兩個,一個事務調用,一個非事務調用
測試事務調用時,咱們新建一個bean: PropagationDemo2
,下面的support方法支持事務運行
@Component public class PropagationDemo2 { @Autowired private PropagationDemo propagationDemo; @Transactional(rollbackFor = Exception.class) public void support(int id) throws Exception { // 事務運行 propagationDemo.support(id); } }
對於非事務調用,則是直接在測試類中調用(請注意下面的call方法,調用的是兩個不一樣bean中的support方法)
private void testSupport() { int id = 430; // 非事務方式,異常不會回滾 call("support無事務運行", id, propagationDemo::support); // 事務運行 id = 440; call("support事務運行", id, propagationDemo2::support); }
輸出結果以下:
============ support無事務運行 start ========== support無事務運行 >>>> {id=430, name=初始化, money=200, is_deleted=false, create_at=2020-02-02 15:23:26.0, update_at=2020-02-02 15:23:26.0} support: after updateMoney name >>>> {id=430, name=更新, money=200, is_deleted=false, create_at=2020-02-02 15:23:26.0, update_at=2020-02-02 15:23:46.0} 事務回滾!!! support無事務運行 >>>> {id=430, name=更新, money=210, is_deleted=false, create_at=2020-02-02 15:23:26.0, update_at=2020-02-02 15:23:46.0} ============ support無事務運行 end ========== ============ support事務運行 start ========== support事務運行 >>>> {id=440, name=初始化, money=200, is_deleted=false, create_at=2020-02-02 15:23:26.0, update_at=2020-02-02 15:23:26.0} support: after updateMoney name >>>> {id=440, name=更新, money=200, is_deleted=false, create_at=2020-02-02 15:23:26.0, update_at=2020-02-02 15:23:46.0} 事務回滾!!! support事務運行 >>>> {id=440, name=初始化, money=200, is_deleted=false, create_at=2020-02-02 15:23:26.0, update_at=2020-02-02 15:23:26.0} ============ support事務運行 end ==========
從上面的輸出,也能夠得出結果:非事務執行時,不會回滾;事務執行時,回滾
須要在一個正常的事務內執行,不然拋異常
使用姿式以下
@Transactional(propagation = Propagation.MANDATORY, rollbackFor = Exception.class) public void mandatory(int id) throws Exception { if (this.updateName(id)) { this.query("mandatory: after updateMoney name", id); if (this.updateMoney(id)) { return; } } throw new Exception("事務回滾!!!"); }
這種傳播屬性的特色是這個方法必須在一個已有的事務中運行,因此咱們的測試case也比較簡單,再也不事務中運行時會怎樣?
private void testMandatory() { int id = 450; // 非事務方式,拋異常,這個必須在一個事務內部執行 call("mandatory非事務運行", id, propagationDemo::mandatory); }
輸出結果
============ mandatory非事務運行 start ========== mandatory非事務運行 >>>> {id=450, name=初始化, money=200, is_deleted=false, create_at=2020-02-02 15:23:26.0, update_at=2020-02-02 15:23:26.0} No existing transaction found for transaction marked with propagation 'mandatory' mandatory非事務運行 >>>> {id=450, name=初始化, money=200, is_deleted=false, create_at=2020-02-02 15:23:26.0, update_at=2020-02-02 15:23:26.0} ============ mandatory非事務運行 end ==========
從上面的輸出可知,直接拋出了異常,並不會執行方法內的邏輯
這個比較有意思,被它標記的方法,老是非事務地執行,若是存在活動事務,則掛起
(實在是沒有想到,有什麼場景須要這種傳播屬性)
一個簡單的使用case以下:
@Transactional(propagation = Propagation.NOT_SUPPORTED, rollbackFor = Exception.class) public void notSupport(int id) throws Exception { if (this.updateName(id)) { this.query("notSupport: after updateMoney name", id); if (this.updateMoney(id)) { return; } } throw new Exception("回滾!"); }
接下來須要好好的想一下咱們的測試用例,首先是它須要在一個事務中調用,外部事物失敗回滾,並不會影響上面這個方法的執行結果
咱們在PropagationDemo2
中,添加測試case以下
@Transactional(rollbackFor = Exception.class) public void notSupport(int id) throws Exception { // 掛起當前事務,以非事務方式運行 try { propagationDemo.notSupport(id); } catch (Exception e) { } propagationDemo.query("notSupportCall: ", id); propagationDemo.updateName(id, "外部更新"); propagationDemo.query("notSupportCall: ", id); throw new Exception("回滾"); }
輸出結果以下
============ notSupport start ========== notSupport >>>> {id=460, name=初始化, money=200, is_deleted=false, create_at=2020-02-02 15:23:26.0, update_at=2020-02-02 15:23:26.0} notSupport: after updateMoney name >>>> {id=460, name=更新, money=200, is_deleted=false, create_at=2020-02-02 15:23:26.0, update_at=2020-02-02 15:23:46.0} notSupportCall: >>>> {id=460, name=更新, money=210, is_deleted=false, create_at=2020-02-02 15:23:26.0, update_at=2020-02-02 15:23:46.0} notSupportCall: >>>> {id=460, name=外部更新, money=210, is_deleted=false, create_at=2020-02-02 15:23:26.0, update_at=2020-02-02 15:23:46.0} 回滾 notSupport >>>> {id=460, name=更新, money=210, is_deleted=false, create_at=2020-02-02 15:23:26.0, update_at=2020-02-02 15:23:46.0} ============ notSupport end ==========
從上面輸出能夠看出
老是非事務地執行,若是存在一個活動事務,則拋出異常。
使用姿式以下
/** * 老是非事務地執行,若是存在一個活動事務,則拋出異常。 * * @param id * @throws Exception */ @Transactional(propagation = Propagation.NEVER, rollbackFor = Exception.class) public void never(int id) throws Exception { if (this.updateName(id)) { this.query("notSupport: after updateMoney name", id); if (this.updateMoney(id)) { return; } } }
咱們的測試就比較簡單了,若是在事務中運行,是否是會拋異常
在PropagationDemo2
中,添加一個事務調用方法
@Transactional(rollbackFor = Exception.class) public void never(int id) throws Exception { propagationDemo.never(id); }
測試代碼
private void testNever() { int id = 470; call("never非事務", id, propagationDemo2::never); }
輸出結果
============ never非事務 start ========== never非事務 >>>> {id=470, name=初始化, money=200, is_deleted=false, create_at=2020-02-02 15:23:26.0, update_at=2020-02-02 15:23:26.0} Existing transaction found for transaction marked with propagation 'never' never非事務 >>>> {id=470, name=初始化, money=200, is_deleted=false, create_at=2020-02-02 15:23:26.0, update_at=2020-02-02 15:23:26.0} ============ never非事務 end ==========
直接拋出了異常,並無執行方法內的業務邏輯
其主要特色以下
上面提出了一個嵌套事務的概念,什麼是嵌套事務呢?
接下來設計兩個測試用例,一個是內部事務回滾;一個是外部事務回滾
@Transactional(propagation = Propagation.NESTED, rollbackFor = Exception.class) public void nested(int id) throws Exception { if (this.updateName(id)) { this.query("nested: after updateMoney name", id); if (this.updateMoney(id)) { return; } } throw new Exception("事務回滾!!!"); }
在PropagationDemo2
這個bean中,添加一個外部事務,捕獲上面方法的異常,所以外部執行正常
@Transactional(rollbackFor = Exception.class) public void nested(int id) throws Exception { propagationDemo.updateName(id, "外部事務修改"); propagationDemo.query("nestedCall: ", id); try { propagationDemo.nested(id); } catch (Exception e) { } }
測試代碼
private void testNested() { int id = 480; call("nested事務", id, propagationDemo2::nested); }
輸出結果以下
============ nested事務 start ========== nested事務 >>>> {id=480, name=初始化, money=200, is_deleted=false, create_at=2020-02-02 15:23:26.0, update_at=2020-02-02 15:23:26.0} nestedCall: >>>> {id=480, name=外部事務修改, money=200, is_deleted=false, create_at=2020-02-02 15:23:26.0, update_at=2020-02-02 15:23:46.0} nested: after updateMoney name >>>> {id=480, name=更新, money=200, is_deleted=false, create_at=2020-02-02 15:23:26.0, update_at=2020-02-02 15:23:46.0} nested事務 >>>> {id=480, name=外部事務修改, money=200, is_deleted=false, create_at=2020-02-02 15:23:26.0, update_at=2020-02-02 15:23:46.0} ============ nested事務 end ==========
仔細看一下上面的結果,外部事務修改的結果都被保存了,內部事務的修改被回滾了,沒有影響最終的結果
@Transactional(propagation = Propagation.NESTED, rollbackFor = Exception.class) public void nested2(int id) throws Exception { if (this.updateName(id)) { this.query("nested: after updateMoney name", id); if (this.updateMoney(id)) { return; } } }
在PropagationDemo2
這個bean中,添加一個外部事務,內部事務正常,可是外部事務拋異常,主動回滾
@Transactional(rollbackFor = Exception.class) public void nested2(int id) throws Exception { // 嵌套事務,外部回滾,會同步回滾內部事務 propagationDemo.updateName(id, "外部事務修改"); propagationDemo.query("nestedCall: ", id); propagationDemo.nested2(id); throw new Exception("事務回滾"); }
測試代碼
private void testNested() { int id = 490; call("nested事務2", id, propagationDemo2::nested2); }
輸出結果以下
============ nested事務2 start ========== nested事務2 >>>> {id=490, name=初始化, money=200, is_deleted=false, create_at=2020-02-02 15:23:26.0, update_at=2020-02-02 15:23:26.0} nestedCall: >>>> {id=490, name=外部事務修改, money=200, is_deleted=false, create_at=2020-02-02 15:23:26.0, update_at=2020-02-02 15:23:46.0} nested: after updateMoney name >>>> {id=490, name=更新, money=200, is_deleted=false, create_at=2020-02-02 15:23:26.0, update_at=2020-02-02 15:23:46.0} 事務回滾 nested事務2 >>>> {id=490, name=初始化, money=200, is_deleted=false, create_at=2020-02-02 15:23:26.0, update_at=2020-02-02 15:23:26.0} ============ nested事務2 end ==========
仔細看上面的輸出,對別case1,其特別在於所有回滾了,內部事務的修改也被回滾了
這個和上面的NESTED有點類似,可是又不同
注意
REQUIRES_NEW
和NESTED
相比,兩個事務之間沒有關係,任何一個回滾,對另一個無影響
測試case和前面差很少,很少作細說...
前面介紹了7中傳播屬性,下面簡單對比和小結一下
事務 | 特色 |
---|---|
REQUIRED | 默認,若是存在事務,則支持當前事務;不存在,則開啓一個新事務 |
SUPPORTS | 若是存在一個事務,支持當前事務。若是沒有事務,則非事務的執行 |
MANDATORY | 須要在一個正常的事務內執行,不然拋異常 |
REQUIRES_NEW | 無論存不存在事務,都開啓一個新事務 |
NOT_SUPPORTED | 無論存不存在,都以非事務方式執行,當存在事務時,掛起事務 |
NEVER | 非事務方式執行,若是存在事務,則拋異常 |
NESTED | 若是不存在事務,則開啓一個事務運行;若是存在事務,則運行一個嵌套事務 |
系列博文
源碼
盡信書則不如,以上內容,純屬一家之言,因我的能力有限,不免有疏漏和錯誤之處,如發現bug或者有更好的建議,歡迎批評指正,不吝感激
下面一灰灰的我的博客,記錄全部學習和工做中的博文,歡迎你們前去逛逛