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!

推薦閱讀: