Spring 事務(wù)神器TransactionTemplate用法及避坑指南
在 Java 后端開(kāi)發(fā)中,事務(wù)管理是保證數(shù)據(jù)一致性的核心環(huán)節(jié)。Spring 框架為我們提供了兩種主流的事務(wù)管理方式,但在實(shí)際開(kāi)發(fā)中,很多開(kāi)發(fā)者都會(huì)陷入 “聲明式事務(wù)不靈活,編程式事務(wù)太繁瑣” 的困境。今天,我們就來(lái)聊聊 Spring 提供的TransactionTemplate—— 一個(gè)能兼顧靈活性與簡(jiǎn)潔性的編程式事務(wù)模板類(lèi),幫你輕松搞定復(fù)雜場(chǎng)景下的事務(wù)控制。
一、為什么需要 TransactionTemplate?
為什么需要 TransactionTemplate?—— 從事務(wù)管理痛點(diǎn)說(shuō)起
在介紹 TransactionTemplate 之前,我們先回顧下 Spring 中兩種傳統(tǒng)事務(wù)管理方式的局限性,看看 TransactionTemplate 是如何解決這些問(wèn)題的。
1.1 聲明式事務(wù)(@Transactional):簡(jiǎn)單但 “死板”
聲明式事務(wù)通過(guò)@Transactional注解實(shí)現(xiàn),只需一行代碼就能開(kāi)啟事務(wù),上手門(mén)檻極低。但它的 “黑箱化” 特性在復(fù)雜場(chǎng)景下會(huì)顯得十分笨拙:
靈活性不足:事務(wù)的隔離級(jí)別、傳播行為等屬性需在注解中固定配置,無(wú)法根據(jù)業(yè)務(wù)邏輯動(dòng)態(tài)調(diào)整;
回調(diào)能力缺失:無(wú)法在事務(wù)提交 / 回滾前后添加自定義邏輯(如日志記錄、緩存刷新);
容易踩坑:依賴(lài) Spring AOP 代理,非 public 方法、自調(diào)用等場(chǎng)景會(huì)導(dǎo)致事務(wù)失效,排查問(wèn)題時(shí)往往無(wú)從下手。
1.2 早期編程式事務(wù):靈活但 “冗余”
在 TransactionTemplate 出現(xiàn)之前,編程式事務(wù)需要直接操作PlatformTransactionManager,手動(dòng)控制事務(wù)的創(chuàng)建、提交和回滾。代碼通常長(zhǎng)這樣:
// 早期編程式事務(wù)示例(繁瑣?。?
public void transfer() {
// 1. 定義事務(wù)屬性
DefaultTransactionDefinition def = new DefaultTransactionDefinition();
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
// 2. 獲取事務(wù)狀態(tài)
TransactionStatus status = transactionManager.getTransaction(def);
try {
// 核心業(yè)務(wù)邏輯:扣錢(qián)、加錢(qián)
accountMapper.decreaseBalance(fromId, amount);
accountMapper.increaseBalance(toId, amount);
// 3. 手動(dòng)提交
transactionManager.commit(status);
} catch (Exception e) {
// 4. 手動(dòng)回滾
transactionManager.rollback(status);
throw e;
}
}這種方式雖然靈活,但每次都要重復(fù)編寫(xiě) “事務(wù)定義 - 獲取狀態(tài) - 提交 / 回滾” 的模板代碼,冗余且容易遺漏異常處理。
1.3 破局者:TransactionTemplate 的價(jià)值
TransactionTemplate 的出現(xiàn),完美平衡了兩種方式的優(yōu)缺點(diǎn):
**保留靈活性:**支持動(dòng)態(tài)調(diào)整事務(wù)屬性、手動(dòng)標(biāo)記回滾、添加事務(wù)回調(diào);
**消除冗余代碼:**封裝了事務(wù)控制的模板邏輯,開(kāi)發(fā)者只需關(guān)注核心業(yè)務(wù)代碼;
**無(wú)代理依賴(lài):**無(wú)需依賴(lài) AOP 代理,避免了聲明式事務(wù)的 “代理失效” 問(wèn)題。
簡(jiǎn)單來(lái)說(shuō),TransactionTemplate 讓你用 “極簡(jiǎn)的代碼” 實(shí)現(xiàn) “靈活的事務(wù)控制”。
二、TransactionTemplate 基礎(chǔ)認(rèn)知:是什么?怎么工作?
2.1 定義與定位
TransactionTemplate 是 Spring 框架提供的編程式事務(wù)管理模板類(lèi),位于org.springframework.transaction.support包下,基于「模板方法模式」設(shè)計(jì)。它的核心作用是:封裝事務(wù)的創(chuàng)建、提交、回滾等重復(fù)邏輯,讓開(kāi)發(fā)者聚焦業(yè)務(wù)本身。
它并非替代@Transactional,而是作為補(bǔ)充 —— 適用于聲明式事務(wù)無(wú)法滿(mǎn)足的復(fù)雜場(chǎng)景(如動(dòng)態(tài)事務(wù)屬性、多步驟事務(wù)回調(diào)等)。
2.2 核心依賴(lài)與配置
TransactionTemplate 本身不實(shí)現(xiàn)事務(wù)邏輯,核心依賴(lài)兩個(gè)組件:
PlatformTransactionManager:事務(wù)管理器,是 Spring 事務(wù)的核心接口,不同數(shù)據(jù)源對(duì)應(yīng)不同實(shí)現(xiàn):
JDBC/MyBatis:DataSourceTransactionManager;
JPA:JpaTransactionManager;
分布式事務(wù):DataSourceTransactionManager(配合 Seata 等框架)。
TransactionDefinition:事務(wù)屬性,包含隔離級(jí)別、傳播行為、超時(shí)時(shí)間、是否只讀等配置。
在 Spring Boot 中,我們只需簡(jiǎn)單配置即可將 TransactionTemplate 注入容器:
@Configuration
public class TransactionConfig {
// 1. 注入數(shù)據(jù)源(Spring Boot自動(dòng)配置,無(wú)需手動(dòng)編寫(xiě))
@Autowired
private DataSource dataSource;
// 2. 配置事務(wù)管理器(MyBatis場(chǎng)景)
@Bean
public PlatformTransactionManager transactionManager() {
return new DataSourceTransactionManager(dataSource);
}
// 3. 配置TransactionTemplate
@Bean
public TransactionTemplate transactionTemplate(PlatformTransactionManager transactionManager) {
TransactionTemplate template = new TransactionTemplate(transactionManager);
// 設(shè)置默認(rèn)事務(wù)屬性(可選,不設(shè)置則用Spring默認(rèn)值)
template.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED); // 隔離級(jí)別:讀已提交
template.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED); // 傳播行為:必須有事務(wù)
template.setTimeout(30); // 超時(shí)時(shí)間:30秒
template.setReadOnly(false); // 非只讀(默認(rèn)false)
return template;
}
}注意:如果引入了spring-boot-starter-jdbc或spring-boot-starter-data-jpa,Spring Boot 會(huì)自動(dòng)配置PlatformTransactionManager,我們只需直接注入 TransactionTemplate 即可,無(wú)需手動(dòng)配置事務(wù)管理器。
2.3 工作流程
TransactionTemplate 的工作流程非常簡(jiǎn)潔,本質(zhì)是 “模板代碼 + 回調(diào)函數(shù)” 的組合:
開(kāi)發(fā)者通過(guò)transactionTemplate.execute(…)方法傳入事務(wù)回調(diào)函數(shù)(TransactionCallback);
TransactionTemplate 自動(dòng)調(diào)用PlatformTransactionManager創(chuàng)建事務(wù);
執(zhí)行回調(diào)函數(shù)中的核心業(yè)務(wù)邏輯;
若業(yè)務(wù)邏輯無(wú)異常,自動(dòng)提交事務(wù);
若業(yè)務(wù)邏輯拋出異常(或手動(dòng)標(biāo)記回滾),自動(dòng)回滾事務(wù);
無(wú)論成功與否,最終釋放事務(wù)資源。
用一張流程圖更直觀:
┌───────────────┐ 1. 傳入回調(diào)函數(shù) ┌──────────────────┐
│ 開(kāi)發(fā)者代碼 │ ────────────────────> │ TransactionTemplate │
└───────────────┘ └──────────────────┘
│
▼
┌───────────────┐ 6. 返回結(jié)果 ┌──────────────────┐
│ 業(yè)務(wù)結(jié)果 │ <──────────────────── ┘ │
└───────────────┘ │ 2. 創(chuàng)建事務(wù) │
▼ │
┌──────────────┐ │
│ PlatformTransactionManager │
└──────────────┘ │
│ │
▼ │
┌───────────────┐ 5. 提交/回滾 ┌──────────────────┐
│ 數(shù)據(jù)庫(kù)事務(wù) │ <──────────────────── │ │
└───────────────┘ │ 3. 執(zhí)行業(yè)務(wù)邏輯 │
▼ │
┌──────────────┐ │
│ 回調(diào)函數(shù)業(yè)務(wù)代碼 │ │
└──────────────┘ │
│ │
▼ │
┌──────────────┐ │
│ 4. 結(jié)果/異常 │ ────────┘
└──────────────┘三、TransactionTemplate 核心用法:從基礎(chǔ)到進(jìn)階
掌握用法是核心,下面通過(guò) “基礎(chǔ)用法 + 場(chǎng)景示例” 帶你全面上手 TransactionTemplate。
3.1 兩種核心回調(diào)函數(shù)
TransactionTemplate 提供了兩種回調(diào)接口,分別對(duì)應(yīng) “需要返回結(jié)果” 和 “不需要返回結(jié)果” 的場(chǎng)景:
3.1.1 TransactionCallback:有返回值
當(dāng)業(yè)務(wù)邏輯需要返回結(jié)果時(shí)(如查詢(xún)事務(wù)執(zhí)行狀態(tài)、返回業(yè)務(wù)數(shù)據(jù)),使用TransactionCallback,其中T為返回值類(lèi)型。
示例:轉(zhuǎn)賬業(yè)務(wù)(有返回值)
@Service
public class TransferService {
@Autowired
private TransactionTemplate transactionTemplate;
@Autowired
private AccountMapper accountMapper;
/**
* 轉(zhuǎn)賬業(yè)務(wù):從fromId扣錢(qián),向toId加錢(qián)
* @return 轉(zhuǎn)賬是否成功
*/
public boolean transfer(Long fromId, Long toId, BigDecimal amount) {
// 調(diào)用execute方法,傳入TransactionCallback回調(diào)
Boolean result = transactionTemplate.execute(status -> {
try {
// 1. 扣減轉(zhuǎn)出方余額
int rows1 = accountMapper.decreaseBalance(fromId, amount);
if (rows1 == 0) {
throw new RuntimeException("轉(zhuǎn)出賬戶(hù)不存在或余額不足");
}
// 2. 增加轉(zhuǎn)入方余額(模擬異常:若toId不存在,會(huì)拋異常觸發(fā)回滾)
int rows2 = accountMapper.increaseBalance(toId, amount);
if (rows2 == 0) {
throw new RuntimeException("轉(zhuǎn)入賬戶(hù)不存在");
}
// 3. 無(wú)異常,返回成功
return true;
} catch (Exception e) {
// 手動(dòng)標(biāo)記回滾(可選,拋出RuntimeException會(huì)自動(dòng)回滾)
status.setRollbackOnly();
log.error("轉(zhuǎn)賬失敗", e);
return false;
}
});
return Boolean.TRUE.equals(result);
}
}3.1.2 TransactionCallbackWithoutResult:無(wú)返回值
當(dāng)業(yè)務(wù)邏輯不需要返回結(jié)果時(shí)(如批量插入、日志記錄),使用TransactionCallbackWithoutResult,簡(jiǎn)化代碼編寫(xiě)。
示例:批量插入用戶(hù)(無(wú)返回值)
@Service
public class UserService {
@Autowired
private TransactionTemplate transactionTemplate;
@Autowired
private UserMapper userMapper;
/**
* 批量插入用戶(hù),確保要么全部成功,要么全部失敗
*/
public void batchInsertUser(List<User> userList) {
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus status) {
try {
for (User user : userList) {
// 模擬部分插入失敗(如用戶(hù)手機(jī)號(hào)重復(fù))
if (StringUtils.isEmpty(user.getPhone())) {
throw new RuntimeException("用戶(hù)手機(jī)號(hào)不能為空");
}
userMapper.insert(user);
}
} catch (Exception e) {
status.setRollbackOnly();
log.error("批量插入用戶(hù)失敗", e);
throw new BusinessException("批量插入失敗,請(qǐng)檢查數(shù)據(jù)");
}
}
});
}
}3.2 進(jìn)階場(chǎng)景:動(dòng)態(tài)調(diào)整事務(wù)屬性
TransactionTemplate 的一大優(yōu)勢(shì)是支持動(dòng)態(tài)修改事務(wù)屬性,無(wú)需修改全局配置。例如,某個(gè)查詢(xún)接口需要 “只讀事務(wù)”(優(yōu)化數(shù)據(jù)庫(kù)性能),而寫(xiě)入接口需要 “讀寫(xiě)事務(wù)”。
示例:動(dòng)態(tài)配置只讀事務(wù)
/**
* 查詢(xún)所有用戶(hù)(只讀事務(wù),優(yōu)化性能)
*/
public List<User> queryAllUser() {
// 1. 定義臨時(shí)事務(wù)屬性(覆蓋默認(rèn)配置)
TransactionDefinition readOnlyDef = new DefaultTransactionDefinition() {
@Override
public int getPropagationBehavior() {
return TransactionDefinition.PROPAGATION_SUPPORTS; // 支持事務(wù)(無(wú)則不創(chuàng)建)
}
@Override
public int getIsolationLevel() {
return TransactionDefinition.ISOLATION_READ_COMMITTED;
}
@Override
public boolean isReadOnly() {
return true; // 只讀事務(wù)
}
@Override
public int getTimeout() {
return 10; // 超時(shí)時(shí)間10秒
}
};
// 2. 傳入動(dòng)態(tài)事務(wù)屬性執(zhí)行
return transactionTemplate.execute(readOnlyDef, status -> {
return userMapper.selectList(null);
});
}3.3 高級(jí)場(chǎng)景:多數(shù)據(jù)源事務(wù)管理
在多數(shù)據(jù)源場(chǎng)景下(如主從庫(kù)分離、業(yè)務(wù)庫(kù)與日志庫(kù)分離),只需為不同數(shù)據(jù)源配置獨(dú)立的 TransactionTemplate 即可。
示例:多數(shù)據(jù)源配置
@Configuration
public class MultiDataSourceTxConfig {
// 主庫(kù)數(shù)據(jù)源(寫(xiě)操作)
@Autowired
@Qualifier("masterDataSource")
private DataSource masterDataSource;
// 從庫(kù)數(shù)據(jù)源(讀操作)
@Autowired
@Qualifier("slaveDataSource")
private DataSource slaveDataSource;
// 主庫(kù)事務(wù)管理器
@Bean("masterTxManager")
public PlatformTransactionManager masterTxManager() {
return new DataSourceTransactionManager(masterDataSource);
}
// 從庫(kù)事務(wù)管理器
@Bean("slaveTxManager")
public PlatformTransactionManager slaveTxManager() {
return new DataSourceTransactionManager(slaveDataSource);
}
// 主庫(kù)TransactionTemplate(寫(xiě)操作)
@Bean("masterTxTemplate")
public TransactionTemplate masterTxTemplate(@Qualifier("masterTxManager") PlatformTransactionManager txManager) {
TransactionTemplate template = new TransactionTemplate(txManager);
template.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
return template;
}
// 從庫(kù)TransactionTemplate(讀操作,默認(rèn)只讀)
@Bean("slaveTxTemplate")
public TransactionTemplate slaveTxTemplate(@Qualifier("slaveTxManager") PlatformTransactionManager txManager) {
TransactionTemplate template = new TransactionTemplate(txManager);
template.setPropagationBehavior(TransactionDefinition.PROPAGATION_SUPPORTS);
template.setReadOnly(true);
return template;
}
}業(yè)務(wù)中使用多數(shù)據(jù)源:
@Service
public class BusinessService {
// 主庫(kù)模板(寫(xiě)操作)
@Autowired
@Qualifier("masterTxTemplate")
private TransactionTemplate masterTxTemplate;
// 從庫(kù)模板(讀操作)
@Autowired
@Qualifier("slaveTxTemplate")
private TransactionTemplate slaveTxTemplate;
// 寫(xiě)操作(主庫(kù))
public void saveOrder(Order order) {
masterTxTemplate.execute(status -> {
orderMapper.insert(order);
return null;
});
}
// 讀操作(從庫(kù))
public Order getOrderById(Long orderId) {
return slaveTxTemplate.execute(status -> {
return orderMapper.selectById(orderId);
});
}
}四、TransactionTemplate vs @Transactional:該怎么選?
很多開(kāi)發(fā)者會(huì)糾結(jié):什么時(shí)候用 TransactionTemplate,什么時(shí)候用@Transactional?我們通過(guò)一張對(duì)比表清晰區(qū)分:
| 對(duì)比維度 | TransactionTemplate(編程式) | @Transactional(聲明式) |
|---|---|---|
| 靈活性 | 高:支持動(dòng)態(tài)事務(wù)屬性、手動(dòng)回滾、事務(wù)回調(diào) | 低:屬性固定,無(wú)回調(diào)能力 |
| 代碼侵入性 | 中:需顯式調(diào)用execute(),但無(wú)冗余代碼 | 低:僅需加注解,完全無(wú)侵入 |
| 適用場(chǎng)景 | 1. 復(fù)雜事務(wù)(多步驟需手動(dòng)控制回滾)2. 動(dòng)態(tài)事務(wù)屬性3. 非 Spring 管理類(lèi)(如工具類(lèi))4. 多數(shù)據(jù)源事務(wù)切換 | 1. 簡(jiǎn)單 CRUD 操作(單步事務(wù))2. 常規(guī)業(yè)務(wù)場(chǎng)景3. 追求代碼簡(jiǎn)潔性 |
| 問(wèn)題排查 | 易:事務(wù)邏輯顯式可見(jiàn),Debug 可直接跟蹤 | 難:依賴(lài) AOP 代理,易出現(xiàn) “事務(wù)失效” 問(wèn)題,排查耗時(shí) |
| 性能 | 略?xún)?yōu):無(wú)代理開(kāi)銷(xiāo),直接調(diào)用 | 略差:依賴(lài) CGLIB/JDK 代理,有輕微性能損耗 |
總結(jié)選擇策略
大多數(shù)簡(jiǎn)單場(chǎng)景(如單表增刪改查):優(yōu)先用@Transactional,開(kāi)發(fā)效率高;
復(fù)雜場(chǎng)景(如動(dòng)態(tài)事務(wù)屬性、多步驟事務(wù)回調(diào)、多數(shù)據(jù)源切換):用 TransactionTemplate,靈活性更強(qiáng);
不確定事務(wù)是否生效的場(chǎng)景:用 TransactionTemplate,避免踩 AOP 代理的坑。
到此這篇關(guān)于Spring 事務(wù)神器TransactionTemplate用法及避坑指南的文章就介紹到這了,更多相關(guān)Spring TransactionTemplate用法內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java數(shù)據(jù)結(jié)構(gòu)之實(shí)現(xiàn)跳表
今天帶大家來(lái)學(xué)習(xí)Java數(shù)據(jù)結(jié)構(gòu)的相關(guān)知識(shí),文中對(duì)用Java實(shí)現(xiàn)跳表作了非常詳細(xì)的圖文解說(shuō)及代碼示例,對(duì)正在學(xué)習(xí)java的小伙伴們有很好地幫助,需要的朋友可以參考下2021-05-05
java.util.Random和concurrent.ThreadLocalRandom使用對(duì)比
這篇文章主要介紹了java.util.Random和concurrent.ThreadLocalRandom使用對(duì)比,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-07-07
Java開(kāi)發(fā)Oracle數(shù)據(jù)庫(kù)連接JDBC Thin Driver 的三種方法
這篇文章主要介紹了Java開(kāi)發(fā)Oracle數(shù)據(jù)庫(kù)連接JDBC Thin Driver 的三種方法,需要的朋友可以參考下2015-12-12
Springboot如何實(shí)現(xiàn)對(duì)配置文件中的明文密碼加密
這篇文章主要介紹了Springboot如何實(shí)現(xiàn)對(duì)配置文件中的明文密碼加密問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-12-12
Java實(shí)現(xiàn)滑動(dòng)驗(yàn)證碼生成(后端工具類(lèi))
這篇文章主要為大家詳細(xì)介紹了Java實(shí)現(xiàn)滑動(dòng)驗(yàn)證碼生成功能中的后端工具類(lèi)部分,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2022-10-10
java常見(jiàn)報(bào)錯(cuò)及解決方案總結(jié)
這篇文章主要介紹了Java編程中常見(jiàn)錯(cuò)誤類(lèi)型及示例,包括語(yǔ)法錯(cuò)誤、空指針異常、數(shù)組下標(biāo)越界、類(lèi)型轉(zhuǎn)換異常、文件未找到異常、除以零異常、非法線(xiàn)程操作異常、方法未定義異常、死鎖和類(lèi)未找到異常,文中通過(guò)代碼將解決方案介紹的非常詳細(xì),需要的朋友可以參考下2025-03-03
Jmeter訪(fǎng)問(wèn)需要登錄的接口如何處理問(wèn)題
這篇文章主要介紹了Jmeter訪(fǎng)問(wèn)需要登錄的接口如何處理問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-11-11
Event?Sourcing事件溯源模式優(yōu)化業(yè)務(wù)系統(tǒng)
這篇文章主要為大家介紹了Event?Sourcing事件溯源模式優(yōu)化業(yè)務(wù)系統(tǒng)示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-07-07
SpringCloud OpenFeign遠(yuǎn)程調(diào)用傳遞請(qǐng)求頭信息方式
這篇文章主要介紹了SpringCloud OpenFeign遠(yuǎn)程調(diào)用傳遞請(qǐng)求頭信息方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2026-03-03

