
前面介紹的幾篇事務的博文,主要是利用@Transactional注解的聲明式使用姿勢,其好處在於使用簡單,侵入性低,可辨識性高(一看就知道使用了事務);然而缺點也比較明顯,不夠靈活,稍不注意,可能就因為姿勢不對,導致事務不生效
本文將介紹另外一種事務的使用姿勢,借助TransactionTemplate的編程式事務
I. 配置
本篇主要介紹的是jdbcTemplate+transactionTemplate來完成一個編程式事務的實例 demo
創建一個 SpringBoot 項目,版本為2.2.1.RELEASE,使用 mysql 作為目標數據庫,存儲引擎選擇Innodb,事務隔離級別為 RR
1. 項目配置
在項目pom.xml文件中,加上spring-boot-starter-jdbc,會注入一個DataSourceTransactionManager的 bean,提供了事務支持
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
2. 數據庫配置
進入 spring 配置文件application.properties,設置一下 db 相關的信息
## 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=
3. 數據庫
新建一個簡單的表結構,用於測試
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;
II. 使用說明
1. 初始化
創建幾條數據,用於事務操作
@Service
public class ManualDemo {
@Autowired
private TransactionTemplate transactionTemplate;
@Autowired
private JdbcTemplate jdbcTemplate;
@PostConstruct
public void init() {
String sql = "replace into money (id, name, money) values (220, '初始化', 200)";
jdbcTemplate.execute(sql);
}
}
2. 使用 case
為了演示事務的特性,我們設計幾個簡單的 sql 操作,並拋出異常,引發回滾,如下
- 在 doUpdate 方法中,顯示更新 name,輸出更新的結果,然后再更新 money 的值,最后拋出一個異常,希望事務回滾
private boolean doUpdate(int id) throws Exception {
if (this.updateName(id)) {
this.query("after updateMoney name", id);
if (this.updateMoney(id)) {
return true;
}
}
throw new Exception("參數異常");
}
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;
}
上面這一端邏輯,如果看了前面幾篇博文,會比較熟悉,區別在於 doUpdate 方法上面沒有添加@Transactional注解,當下它的調用並不會在事務中執行
接下來我們看一下編程式事務的核心寫法
public void testTransaction(int id) {
transactionTemplate.execute(new TransactionCallback<Boolean>() {
@Override
public Boolean doInTransaction(TransactionStatus transactionStatus) {
try {
return doUpdate(id);
} catch (Exception e) {
transactionStatus.setRollbackOnly();
return false;
}
}
});
}
如上,將方法的調用,封裝在transactionTemplate.execute的調用中,通過設置transactionStatus.setRollbackOnly()來標記回滾
通過前面幾篇博文的學習我們知道實際使用時,事務的隔離級別,傳遞屬性也很重要,在編程式事務中,當然也是可以設置的
// 設置隔離級別
transactionTemplate.setIsolationLevel(TransactionDefinition.ISOLATION_DEFAULT);
// 設置傳播屬性
transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
最后寫一個測試代碼,驗證一下是否生效
@Component
public class TransactionalSample {
@Autowired
private ManualDemo manualDemo;
public void testManualCase() {
System.out.println("======= 編程式事務 start ========== ");
manualDemo.query("transaction before", 220);
manualDemo.testTransaction(220);
manualDemo.query("transaction end", 220);
System.out.println("======= 編程式事務 end ========== ");
}
}
輸出結果如下,最終數據 big 沒有被修改
======= 編程式事務 start ==========
transaction before >>>> {id=220, name=初始化, money=200, is_deleted=false, create_at=2020-02-03 13:52:39.0, update_at=2020-02-03 13:52:39.0}
after updateMoney name >>>> {id=220, name=更新, money=200, is_deleted=false, create_at=2020-02-03 13:52:39.0, update_at=2020-02-03 13:52:39.0}
transaction end >>>> {id=220, name=初始化, money=200, is_deleted=false, create_at=2020-02-03 13:52:39.0, update_at=2020-02-03 13:52:39.0}
======= 編程式事務 end ==========
III. 其他
0. 系列博文&源碼
系列博文
- 180926-SpringBoot 高級篇 DB 之基本使用
- 190407-SpringBoot 高級篇 JdbcTemplate 之數據插入使用姿勢詳解
- 190412-SpringBoot 高級篇 JdbcTemplate 之數據查詢上篇
- 190417-SpringBoot 高級篇 JdbcTemplate 之數據查詢下篇
- 190418-SpringBoot 高級篇 JdbcTemplate 之數據更新與刪除
- 200119-SpringBoot 系列教程之聲明式事務 Transactional
- 200120-SpringBoot 系列教程之事務隔離級別知識點小結
- 200202-SpringBoot 系列教程之事務傳遞屬性
- 200203-SpringBoot 系列教程之事務不生效的幾種 case
源碼
- 工程:https://github.com/liuyueyi/spring-boot-demo
- 實例源碼: https://github.com/liuyueyi/spring-boot-demo/blob/master/spring-boot/101-jdbctemplate-transaction
1. 一灰灰 Blog
盡信書則不如,以上內容,純屬一家之言,因個人能力有限,難免有疏漏和錯誤之處,如發現 bug 或者有更好的建議,歡迎批評指正,不吝感激
下面一灰灰的個人博客,記錄所有學習和工作中的博文,歡迎大家前去逛逛
- 一灰灰 Blog 個人博客 https://blog.hhui.top
- 一灰灰 Blog-Spring 專題博客 http://spring.hhui.top

