一、事務基本概念
事務是資料庫系統中的重要概念,它是由一組操作所組成的邏輯機關,這組操作被視為一個整體,要麼全部執行成功,要麼全部復原。事務的隔離級别可以控制多個事務之間的互相影響,而事務的傳播行為可以控制目前事務對其他事務的可見性。
二、Spring 中的事務管理
Spring 提供了兩種事務管理方式:程式設計式事務管理和聲明式事務管理。程式設計式事務管理需要開發人員自己編寫事務管理代碼。而聲明式事務管理則由 Spring 容器負責管理,需要開發人員在配置檔案或者代碼中指定事務管理規則。
聲明式事務管理又分為兩種實作方式:基于 TransactionProxyFactoryBean 的聲明式事務管理和基于 @Transactional 注解的聲明式事務管理。前者需要手動建立代理對象,後者則使用注解方式簡化了配置。
程式設計式事務管理 | 聲明式事務管理 | |
優點 | 可以更加靈活地控制事務 | 代碼簡潔,易于維護 |
可以适應複雜的業務場景 | 使用 AOP 技術,與業務邏輯解耦 | |
可以在代碼中針對不同的異常進行復原或送出 | 配置友善,可以通過配置檔案輕松地進行切換和修改 | |
缺點 | 代碼量較大 | 對于高度定制化的業務場景,可能會出現限制 |
不易維護 | 對于複雜的事務處理,可能需要多個聲明式事務進行組合 | |
建議使用場景 | 應用需要局部控制事務,而非全局控制 | 應用需要全局控制事務 |
處理的業務比較複雜,需要編寫更多的自定義邏輯 | 應用的事務需求相對簡單 |
日常開發中我們多使用 @Transactional注解式事務管理,但在一些需要精細控制事務管理時往往力不從心,而被忽略的 程式設計式事務 就是解決此問題的利器。
三、使用 TransactionTemplate 進行程式設計式事務管理
TransactionTemplate是Spring架構提供的一個事務管理工具類,通過它我們可以手動控制事務的開始、送出和復原等操作,友善我們更加靈活地處理事務,其主要提供了以下功能:
- 在事務内執行一組操作
- 設定事務的傳播行為和隔離級别
- 設定事務的逾時時間
- 優化隻讀事務
下面是一個簡單的例子,示範了如何使用 TransactionTemplate 來實作程式設計式事務管理:
@Service
public class UserServiceImpl implements UserService {
private final UserDao userDao;
private final TransactionTemplate transactionTemplate;
@Autowired
public UserServiceImpl(UserDao userDao, TransactionTemplate transactionTemplate) {
this.userDao = userDao;
this.transactionTemplate = transactionTemplate;
}
@Override
public void transferMoney(Long fromUserId, Long toUserId, Double amount) {
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus status) {
try {
// 減少轉出使用者的餘額
userDao.decreaseBalance(fromUserId, amount);
// 增加轉入使用者的餘額
userDao.increaseBalance(toUserId, amount);
} catch (RuntimeException e) {
// 出現異常時復原事務
status.setRollbackOnly();
throw e;
}
}
});
}
}
其它典型使用場景
1.在一個事務中執行多個操作
@Autowired
private TransactionTemplate transactionTemplate;
public void doMultipleOperations() {
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus status) {
// 執行第一個操作
someDao.insert(foo);
// 執行第二個操作
otherDao.update(bar);
// 如果沒有任何異常,則自動送出事務
}
});
}
2.跨多個資料源執行分布式事務
@Autowired
@Qualifier("db1TransactionManager")
private PlatformTransactionManager db1TransactionManager;
@Autowired
@Qualifier("db2TransactionManager")
private PlatformTransactionManager db2TransactionManager;
public void doDistributedTransaction() {
// 定義一個包含兩個事務管理器的TransactionTemplate
TransactionTemplate template = new TransactionTemplate();
template.setTransactionManagers(db1TransactionManager, db2TransactionManager);
// 在TransactionTemplate中執行跨多個資料源的操作
template.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus status) {
someDao.insert(foo); // 在db1中執行操作
otherDao.update(bar); // 在db2中執行操作
}
});
}
3.處理同步/異步事務
@Service
public class UserServiceImpl implements UserService {
private final TransactionTemplate transactionTemplate;
private final ExecutorService executorService = Executors.newFixedThreadPool(10);
@Autowired
public UserServiceImpl(DataSourceTransactionManager transactionManager) {
this.transactionTemplate = new TransactionTemplate(transactionManager);
}
@Override
public void updateUser(User user) {
// 同步事務
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus status) {
try {
// 更新資料庫操作
userDao.updateUser(user);
} catch (Exception e) {
status.setRollbackOnly();
throw e;
}
}
});
// 異步事務
executorService.submit(() -> {
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus status) {
try {
// 向外部系統發送消息
messageService.sendMessage(user.getName() + "更新了資料");
} catch (Exception e) {
status.setRollbackOnly();
throw e;
}
}
});
});
}
}
在上面的例子中,我們使用了線程池來實作異步操作。在執行異步操作之前,我們先使用TransactionTemplate開啟了一個同步事務,保證資料庫操作與消息發送操作在同一事務中執行。接着,我們通過ExecutorService.submit()方法将消息發送操作送出到線程池中異步執行,同時在異步操作中再次使用TransactionTemplate開啟了一個獨立的事務。這樣,我們就利用TransactionTemplate的傳播行為機制,通過獨立的事務管理異步操作,進而避免了多線程帶來的線程安全問題,同時保證了資料的一緻性和完整性。