天天看點

神器TransactionTemplate,讓事務管理更加便捷與安全!

作者:半熵
神器TransactionTemplate,讓事務管理更加便捷與安全!

一、事務基本概念

事務是資料庫系統中的重要概念,它是由一組操作所組成的邏輯機關,這組操作被視為一個整體,要麼全部執行成功,要麼全部復原。事務的隔離級别可以控制多個事務之間的互相影響,而事務的傳播行為可以控制目前事務對其他事務的可見性。

二、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的傳播行為機制,通過獨立的事務管理異步操作,進而避免了多線程帶來的線程安全問題,同時保證了資料的一緻性和完整性。

繼續閱讀