Spring源碼解析之編程式事務
一、前言
在Spring中,事務有兩種實現方式:
編程式事務管理: 編程式事務管理使用TransactionTemplate可實現更細粒度的事務控制。聲明式事務管理: 基於Spring AOP實現。其本質是對方法前後進行攔截,然後在目標方法開始之前創建或者加入一個事務,在執行完目標方法之後根據執行情況提交或者回滾事務。
聲明式事務管理不需要入侵代碼,通過@Transactional就可以進行事務操作,更快捷而且簡單(尤其是配合spring boot自動配置,可以說是精簡至極!),且大部分業務都可以滿足,推薦使用。
其實不管是編程式事務還是聲明式事務,最終調用的底層核心代碼是一致的。本文主要介紹編程式事務的一些應用,以及獨有的源碼分析,再在其他文章中進入核心源碼貫穿式講解。
二、編程式事務解析
編程式事務,Spring已經給我們提供好瞭模板類TransactionTemplate,可以很方便的使用,如下圖:
TransactionTemplate全路徑名是:org.springframework.transaction.support.TransactionTemplate。看包名也知道瞭這是spring對事務的模板類。(spring動不動就是各種Template…),看下類圖先:
實現瞭TransactionOperations、InitializingBean這2個接口(熟悉spring源碼的知道這個InitializingBean又是老套路),我們來看下接口源碼如下:
public interface TransactionOperations { @Nullable <T> T execute(TransactionCallback<T> action) throws TransactionException; } public interface InitializingBean { void afterPropertiesSet() throws Exception; }
如上圖,TransactionOperations這個接口用來執行事務的回調方法,InitializingBean這個是典型的spring bean初始化流程中的預留接口,專用用來在bean屬性加載完畢時執行的方法。
回到正題,TransactionCallback和TransactionCallbackWithoutResult做瞭什麼
@FunctionalInterface public interface TransactionCallback<T> { @Nullable T doInTransaction(TransactionStatus status); } public abstract class TransactionCallbackWithoutResult implements TransactionCallback<Object> { @Override @Nullable public final Object doInTransaction(TransactionStatus status) { doInTransactionWithoutResult(status); return null; } protected abstract void doInTransactionWithoutResult(TransactionStatus status); }
可見TransactionCallbackWithResult實現瞭TransactionCallback接口,重寫瞭doIntransaction方法,在內部調用瞭doInTransactionWithoutResult方法,幫我們返回瞭null,所以,我們就不需要再指定返回值瞭。
TransactionTemplate的2個接口的impl方法做瞭什麼?
@Override public void afterPropertiesSet() { if (this.transactionManager == null) { throw new IllegalArgumentException("Property 'transactionManager' is required"); } } @Override public <T> T execute(TransactionCallback<T> action) throws TransactionException { // 內部封裝好的事務管理器 if (this.transactionManager instanceof CallbackPreferringPlatformTransactionManager) { return ((CallbackPreferringPlatformTransactionManager) this.transactionManager).execute(this, action); }// 需要手動獲取事務,執行方法,提交事務的管理器 else {// 1.獲取事務狀態 TransactionStatus status = this.transactionManager.getTransaction(this); T result; try {// 2.執行業務邏輯 result = action.doInTransaction(status); } catch (RuntimeException ex) { // 應用運行時異常 -> 回滾 rollbackOnException(status, ex); throw ex; } catch (Error err) { // Error異常 -> 回滾 rollbackOnException(status, err); throw err; } catch (Throwable ex) { // 未知異常 -> 回滾 rollbackOnException(status, ex); throw new UndeclaredThrowableException(ex, "TransactionCallback threw undeclared checked exception"); }// 3.事務提交 this.transactionManager.commit(status); return result; } }
如上圖所示,實際上afterPropertiesSet隻是校驗瞭事務管理器不為空,execute()才是核心方法,execute主要步驟:
1.getTransaction()獲取事務
2.doInTransaction()執行業務邏輯,這裡就是用戶自定義的業務代碼。如果是沒有返回值的,就是doInTransactionWithoutResult()。
3.commit()事務提交:調用AbstractPlatformTransactionManager的commit,rollbackOnException()異常回滾:調用AbstractPlatformTransactionManager的rollback(),事務提交回滾。源碼見後續文章
三、編程式事務示例
public class SpringTransactionExample { private static String url = "jdbc:mysql://localhost:3306/spring_transaction?useSSL=false&characterEncoding=utf-8&autoReconnect=true"; private static String user = "root"; private static String password = "root"; public static void main(String[] args) { // 獲取數據源 final DataSource ds = new DriverManagerDataSource(url, user, password); // 編程式事務 final TransactionTemplate template = new TransactionTemplate(); // 設置事務管理器 template.setTransactionManager(new DataSourceTransactionManager(ds)); template.execute(new TransactionCallback<Object>() { @Override public Object doInTransaction(TransactionStatus transactionStatus) { Connection conn = DataSourceUtils.getConnection(ds); Object savePoint = null; try { { // 插入 PreparedStatement prepare = conn.prepareStatement("insert into person(id, name) values (?, ?)"); prepare.setString(1, "1"); prepare.setString(2, "1111"); prepare.executeUpdate(); } // 設置保存點,回滾的化,不會回滾保存點之前的操作 savePoint = transactionStatus.createSavepoint(); { // 插入 PreparedStatement prepare = conn.prepareStatement("insert into person(id, name) values (?, ?)"); prepare.setString(1, "2"); prepare.setString(2, "222"); prepare.executeUpdate(); } { // 更新 PreparedStatement prepare = conn.prepareStatement("update person set name = ? where id = ?"); prepare.setString(1, "jak"); prepare.setInt(2, 6); prepare.executeUpdate(); // 模擬異常 // int i = 1 / 0; } } catch (SQLException e) { e.printStackTrace(); } catch (Exception e) { System.out.println("更新失敗"); if (savePoint != null) { // 回滾到保存點 transactionStatus.rollbackToSavepoint(savePoint); } else { transactionStatus.setRollbackOnly(); } } return null; } }); } }
四、TransactionCallback
編程式事務帶返回值
public class TransactionCallBackTest { private static String url = "jdbc:mysql://localhost:3306/spring_transaction?useSSL=false&characterEncoding=utf-8&autoReconnect=true"; private static String user = "root"; private static String password = "root"; public static void main(String[] args) { // 獲取數據源 final DataSource ds = new DriverManagerDataSource(url, user, password); // 編程式事務 final TransactionTemplate template = new TransactionTemplate(); // 設置事務管理器 template.setTransactionManager(new DataSourceTransactionManager(ds)); Connection connection = DataSourceUtils.getConnection(ds); test1(template, connection); test2(template, connection); } // 方式一: 匿名內部類 @SuppressWarnings("all") public static void test1(TransactionTemplate template, Connection connection) { // TransactionCallback有返回值 template.execute(new TransactionCallback<Object>() { @Override public Object doInTransaction(TransactionStatus status) { try { // 插入 PreparedStatement prepare = connection.prepareStatement("insert into person(id, name) values (?, ?)"); prepare.setInt(1, 1); prepare.setString(2, "jak"); prepare.executeUpdate(); // 模擬異常 // int i = 1 / 0; System.out.println("數據已插入"); } catch (SQLException e) { e.printStackTrace(); } catch (Exception e) { System.out.println("更新失敗"); status.setRollbackOnly(); } return null; } }); } // 方式二:lamda表達式 @SuppressWarnings("all") public static void test2(TransactionTemplate template, Connection connection) { template.execute((status) -> { try { // 插入 PreparedStatement prepare = connection.prepareStatement("insert into person(id, name) values (?, ?)"); prepare.setInt(1, 2); prepare.setString(2, "hyd"); prepare.executeUpdate(); // 模擬異常 // int i = 1 / 0; System.out.println("數據已插入"); } catch (SQLException e) { e.printStackTrace(); } catch (Exception e) { System.out.println("更新失敗"); status.setRollbackOnly(); } return null; }); } }
五、TransactionCallbackWithoutResult
編程式事務不帶返回值
public class TransactionCallbackWithoutResultTest { private static String url = "jdbc:mysql://localhost:3306/spring_transaction?useSSL=false&characterEncoding=utf-8&autoReconnect=true"; private static String user = "root"; private static String password = "root"; @SuppressWarnings("all") public static void test(TransactionTemplate template, Connection connection) { template.execute(new TransactionCallbackWithoutResult() { // doInTransactionWithoutResult無返回值 @Override public void doInTransactionWithoutResult(TransactionStatus status) { try { // 插入 PreparedStatement prepare = connection.prepareStatement("insert into person(id, name) values (?, ?)"); prepare.setInt(1, 1); prepare.setString(2, "jak"); prepare.executeUpdate(); // 模擬異常 // int i = 1 / 0; System.out.println("數據已插入"); } catch (SQLException e) { e.printStackTrace(); } catch (Exception e) { System.out.println("更新失敗"); status.setRollbackOnly(); } } }); } public static void main(String[] args) { // 獲取數據源 final DataSource ds = new DriverManagerDataSource(url, user, password); // 編程式事務 final TransactionTemplate template = new TransactionTemplate(); // 設置事務管理器 template.setTransactionManager(new DataSourceTransactionManager(ds)); Connection connection = DataSourceUtils.getConnection(ds); test(template, connection); } }
踩坑指南,上述方式 不知道為啥,事務一直不回滾,改為jdbcTemplate方式,可以正常回滾,不知道什麼原因
public class jdbcTemplateTest { private static String url = "jdbc:mysql://localhost:3306/spring_transaction?useSSL=false&characterEncoding=utf-8&autoReconnect=true"; private static String user = "root"; private static String password = "root"; @SuppressWarnings("all") public static void test(TransactionTemplate template, JdbcTemplate jdbcTemplate) { template.execute(new TransactionCallbackWithoutResult() { // doInTransactionWithoutResult無返回值 @SneakyThrows @Override public void doInTransactionWithoutResult(TransactionStatus status) { try { // 插入 jdbcTemplate.execute("insert into person(id, name) values (2, 'jak')"); // 模擬異常 int i = 1 / 0; System.out.println("數據已插入"); } catch (Exception e) { // 標記事務回滾 status.setRollbackOnly(); } } }); } public static void main(String[] args) { // 獲取數據源 final DataSource ds = new DriverManagerDataSource(url, user, password); // 編程式事務 final TransactionTemplate template = new TransactionTemplate(); // jdbcTemplate final JdbcTemplate jdbcTemplate = new JdbcTemplate(); // 設置事務管理器 template.setTransactionManager(new DataSourceTransactionManager(ds)); // 配置數據源 jdbcTemplate.setDataSource(ds); test(template, jdbcTemplate); } }
參考文章、參考文章
到此這篇關於Spring源碼解析之編程式事務的文章就介紹到這瞭,更多相關Spring編程式事務內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!
推薦閱讀:
- springboot編程式事務TransactionTemplate的使用說明
- Spring超詳細講解事務
- 詳細談談Spring事務是如何管理的
- Java Spring AOP源碼解析之事務實現原理
- JAVA Spring中讓人頭痛的JAVA大事務問題要如何解決你知道嗎