當咱們但願一組操做,要麼都成功,要麼都失敗時,每每會考慮利用事務來實現這一點;以前介紹的 db 操做,主要在於單表的 CURD,本文將主要介紹聲明式事務@Transactional
的使用姿式java
<!-- more -->mysql
本篇主要介紹的是jdbcTemplate
配合事務註解@Transactional
的使用姿式,至於 JPA,mybatis 在實際的使用區別上,並不大,後面會單獨說明git
建立一個 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;
爲了體現事務的特色,在不考慮 DDL 的場景下,DML 中的增長,刪除 or 修改屬於不可缺乏的語句了,因此咱們須要先初始化幾個用於測試的數據mybatis
@Service public class SimpleDemo { @Autowired private JdbcTemplate jdbcTemplate; @PostConstruct public void init() { String sql = "replace into money (id, name, money) values (120, '初始化', 200)," + "(130, '初始化', 200)," + "(140, '初始化', 200)," + "(150, '初始化', 200)"; jdbcTemplate.execute(sql); } }
咱們使用replace into
語句來初始化數據,每次 bean 建立以後都會執行,確保每次執行後面你的操做時,初始數據都同樣app
這個註解能夠放在類上,也能夠放在方法上;若是是標註在類上,則這個類的全部公共方法,都支持事務;
若是類和方法上都有,則方法上的註解相關配置,覆蓋類上的註解
下面是一個簡單的事務測試 case
private boolean updateName(int id) { String sql = "update money set `name`='更新' where id=" + id; jdbcTemplate.execute(sql); return true; } public void query(String tag, int id) { String sql = "select * from money where id=" + id; Map map = jdbcTemplate.queryForMap(sql); System.out.println(tag + " >>>> " + map); } private boolean updateMoney(int id) { String sql = "update money set `money`= `money` + 10 where id=" + id; jdbcTemplate.execute(sql); return false; } /** * 運行異常致使回滾 * * @return */ @Transactional public boolean testRuntimeExceptionTrans(int id) { if (this.updateName(id)) { this.query("after updateMoney name", id); if (this.updateMoney(id)) { return true; } } throw new RuntimeException("更新失敗,回滾!"); }
在咱們須要開啓事務的公共方法上添加註解@Transactional
,代表這個方法的正確調用姿式下,若是方法內部執行拋出運行異常,會出現事務回滾
注意上面的說法,正確的調用姿式,事務纔會生效;換而言之,某些 case 下,不會生效
接下來,測試一下上面的方法事務是否生效,咱們新建一個 Bean
@Component public class TransactionalSample { @Autowired private SimpleDemo simpleService; public void testSimpleCase() { System.out.println("============ 事務正常工做 start ========== "); simpleService.query("transaction before", 130); try { // 事務能夠正常工做 simpleService.testRuntimeExceptionTrans(130); } catch (Exception e) { } simpleService.query("transaction end", 130); System.out.println("============ 事務正常工做 end ========== \n"); } }
在上面的調用中,打印了修改以前的數據和修改以後的數據,若是事務正常工做,那麼這兩次輸出應該是一致的
實際輸出結果以下,驗證了事務生效,中間的修改 name 的操做被回滾了
============ 事務正常工做 start ========== transaction before >>>> {id=130, name=初始化, money=200, is_deleted=false, create_at=2020-01-19 16:15:21.0, update_at=2020-01-19 16:15:21.0} after updateMoney name >>>> {id=130, name=更新, money=200, is_deleted=false, create_at=2020-01-19 16:15:21.0, update_at=2020-01-19 16:15:22.0} transaction end >>>> {id=130, name=初始化, money=200, is_deleted=false, create_at=2020-01-19 16:15:21.0, update_at=2020-01-19 16:15:21.0} ============ 事務正常工做 end ==========
在使用註解@Transactional
聲明式事務時,其主要是藉助 AOP,經過代理來封裝事務的邏輯,因此 aop 不生效的場景,也適用於這個事務註解不生效的場景
簡單來說,下面幾種 case,註解不生效
@Transactional
,不生效此外,註解@Transactional
默認只針對運行時異常生效,以下面這種 case,雖然是拋出了異常,可是並不會生效
@Transactional public boolean testNormalException(int id) throws Exception { if (this.updateName(id)) { this.query("after updateMoney name", id); if (this.updateMoney(id)) { return true; } } throw new Exception("聲明異常"); }
若是須要它生效,能夠藉助rollbackFor
屬性來指明,觸發回滾的異常類型
@Transactional(rollbackFor = Exception.class) public boolean testSpecialException(int id) throws Exception { if (this.updateName(id)) { this.query("after updateMoney name", id); if (this.updateMoney(id)) { return true; } } throw new IllegalArgumentException("參數異常"); }
測試一下上面的兩種 case
public void testSimpleCase() { System.out.println("============ 事務不生效 start ========== "); simpleService.query("transaction before", 140); try { // 由於拋出的是非運行異常,不會回滾 simpleService.testNormalException(140); } catch (Exception e) { } simpleService.query("transaction end", 140); System.out.println("============ 事務不生效 end ========== \n"); System.out.println("============ 事務生效 start ========== "); simpleService.query("transaction before", 150); try { // 註解中,指定全部異常都回滾 simpleService.testSpecialException(150); } catch (Exception e) { } simpleService.query("transaction end", 150); System.out.println("============ 事務生效 end ========== \n"); }
輸出結果以下,正好驗證了上面提出的內容
============ 事務不生效 start ========== transaction before >>>> {id=140, name=初始化, money=200, is_deleted=false, create_at=2020-01-19 16:15:21.0, update_at=2020-01-19 16:15:21.0} after updateMoney name >>>> {id=140, name=更新, money=200, is_deleted=false, create_at=2020-01-19 16:15:21.0, update_at=2020-01-19 16:15:22.0} transaction end >>>> {id=140, name=更新, money=210, is_deleted=false, create_at=2020-01-19 16:15:21.0, update_at=2020-01-19 16:15:22.0} ============ 事務不生效 end ========== ============ 事務生效 start ========== transaction before >>>> {id=150, name=初始化, money=200, is_deleted=false, create_at=2020-01-19 16:15:21.0, update_at=2020-01-19 16:15:21.0} after updateMoney name >>>> {id=150, name=更新, money=200, is_deleted=false, create_at=2020-01-19 16:15:21.0, update_at=2020-01-19 16:15:22.0} transaction end >>>> {id=150, name=初始化, money=200, is_deleted=false, create_at=2020-01-19 16:15:21.0, update_at=2020-01-19 16:15:21.0} ============ 事務生效 end ==========
上面的內容,都屬於比較基本的知識點,足以知足咱們通常的業務需求,若是須要進階的話,有必要了解一下屬性信息
如下內容來自: [透徹的掌握 Spring 中@transactional 的使用](http://www.javashuo.com/article/p-emibgjkc-w.html "透徹的掌握 Spring 中@transactional 的使用")
屬性名 | 說明 |
---|---|
name | 當在配置文件中有多個 TransactionManager , 能夠用該屬性指定選擇哪一個事務管理器。 |
propagation | 事務的傳播行爲,默認值爲 REQUIRED。 |
isolation | 事務的隔離度,默認值採用 DEFAULT。 |
timeout | 事務的超時時間,默認值爲-1。若是超過該時間限制但事務尚未完成,則自動回滾事務。 |
read-only | 指定事務是否爲只讀事務,默認值爲 false;爲了忽略那些不須要事務的方法,好比讀取數據,能夠設置 read-only 爲 true。 |
rollback-for | 用於指定可以觸發事務回滾的異常類型,若是有多個異常類型須要指定,各種型之間能夠經過逗號分隔。 |
no-rollback- for | 拋出 no-rollback-for 指定的異常類型,不回滾事務。 |
關於上面幾個屬性的使用實例,以及哪些狀況下,會致使聲明式事務不生效,會新開坑進行說明,敬請期待。。。
系列博文
源碼
盡信書則不如,以上內容,純屬一家之言,因我的能力有限,不免有疏漏和錯誤之處,如發現 bug 或者有更好的建議,歡迎批評指正,不吝感激
下面一灰灰的我的博客,記錄全部學習和工做中的博文,歡迎你們前去逛逛