當初一開始用 Spring,習慣只會 @Transactional 這招。後來才發現 TransactionTemplate 這東西,發現它在某些場景真的有夠方便。

@Transactional 的限制

@Transactional 很直觀啦,加一個 annotation 就搞定交易管理。但踩坑的地方是:

  1. Proxy 限制 - 同個 class 內部呼叫不會觸發 proxy,交易會爆掉
  2. 粒度問題 - 整個方法才是一個交易,沒辦法在同一個方法裡控制多段獨立的交易
  3. Rollback 控制 - 用 rollbackFor 有點硬,不能細粒度決定要不要 rollback

TransactionTemplate 的救世主角色

TransactionTemplate 提供了「程式化」的交易控制。不用 annotation,直接寫程式邏輯,搭配 callback 來執行。

基本用法

首先注入 PlatformTransactionManager:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
@Service
public class OrderService {

@Autowired
private PlatformTransactionManager transactionManager;

private TransactionTemplate transactionTemplate;

@PostConstruct
public void init() {
this.transactionTemplate = new TransactionTemplate(transactionManager);
}

public void processOrder(Long orderId) {
// execute() 會自動開啟交易、提交、rollback
transactionTemplate.execute(status -> {
Order order = findOrder(orderId);
order.setStatus("PROCESSING");
saveOrder(order);

// 這邊的所有操作都在同一個交易裡面
updateInventory(order);
createInvoice(order);

return null; // 或回傳任何物件
});
}
}

細粒度控制 Rollback

TransactionTemplate 最威的地方就是可以在交易中途決定要不要 rollback:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public void complexTransition() {
transactionTemplate.execute(status -> {
try {
saveMainData();
updateRelatedData();
} catch (SpecificException e) {
// 只在特定情況下 rollback
status.setRollbackOnly();
log.warn("Need rollback because: {}", e.getMessage());
}

return null;
});
}

如果呼叫 status.setRollbackOnly(),即使沒有拋 exception,交易也會 rollback。這招對於「執行某些操作失敗但不想拋 exception」的場景真的很好用。

同一個方法裡多個獨立交易

這是 TransactionTemplate 超猛的地方。用 @Transactional 無法做到的事:

1
2
3
4
5
6
7
8
9
10
11
12
public void processBatch() {
List<Item> items = getItems();

for (Item item : items) {
transactionTemplate.execute(status -> {
// 每次迴圈都是獨立的交易
// 其中一個失敗不會影響其他的
processItem(item);
return null;
});
}
}

這樣做的好處是,即使某個 item 處理失敗,後面的 item 還是能繼續處理。用 @Transactional 的話,第一個失敗整個方法都得 rollback。

設定交易隔離等級

TransactionTemplate 也支援設定隔離等級:

1
2
3
4
5
6
7
8
9
10
@PostConstruct
public void init() {
this.transactionTemplate = new TransactionTemplate(transactionManager);
// 設定為 READ_COMMITTED,預設是 ISOLATION_DEFAULT
this.transactionTemplate.setIsolationLevel(
TransactionDefinition.ISOLATION_READ_COMMITTED
);
// 設定 timeout,避免長時間持有 lock
this.transactionTemplate.setTimeout(30);
}

跟 @Transactional 的差別比較

項目 @Transactional TransactionTemplate
使用難度 簡單,加 annotation 稍複雜,要寫 callback
同方法多交易 不支援 支援
Proxy 限制 有(內部呼叫失效) 沒有
細粒度控制 差(只能 rollbackFor) 好(status.setRollbackOnly)
效能 有 overhead 稍好

實務建議

  • 簡單情況 → 用 @Transactional,快速又清爽
  • 複雜邏輯 → 用 TransactionTemplate,控制力強
  • 混合使用 → 可以同時用兩個,但要避免嵌套的 proxy 問題

TransactionTemplate 初看有點繁瑣,但用過幾次就會覺得超香。特別是要處理批次資料或複雜的交易流程時,這招真的能救你一命。