1. 程式人生 > >Spring事務實現之程式設計式事務

Spring事務實現之程式設計式事務


程式設計式事務(TranscationTemplate)

廢話不多說,直接看配置檔案。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
    xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.1.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.1.xsd"
>
<context:component-scan base-package="com.wangcc.transcation.template" /> <!-- 引入配置檔案 --> <bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"> <property name="location" value="classpath:jdbc.properties"
/>
<property name="ignoreUnresolvablePlaceholders" value="true" /> </bean> <!-- 配置資料來源 --> <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close"> <property name="driverClassName" value="${driver}" /> <property name="url" value="${url}" /> <property name="username" value="${username}" /> <property name="password" value="${password}" /> <!-- 初始化連線大小 --> <property name="initialSize" value="${initialSize}"></property> <!-- 連線池最大數量 --> <property name="maxActive" value="${maxActive}"></property> <!-- 連線池最大空閒 --> <property name="maxIdle" value="${maxIdle}"></property> <!-- 連線池最小空閒 --> <property name="minIdle" value="${minIdle}"></property> <!-- 獲取連線最大等待時間 --> <property name="maxWait" value="${maxWait}"></property> <!-- Druid內建提供一個StatFilter,用於統計監控資訊。 StatFilter的別名是stat,這個別名對映配置資訊儲存在: druid-xxx.jar!/META-INF/druid-filter.properties。 在spring中使用別名配置方式如下 --> <!-- 配置監控統計攔截的filters,去掉後監控介面sql無法統計 --> <property name="filters" value="stat" /> <property name="timeBetweenEvictionRunsMillis" value="3000" /> <property name="minEvictableIdleTimeMillis" value="300000" /> <property name="validationQuery" value="SELECT 'x'" /> <property name="testWhileIdle" value="true" /> <property name="testOnBorrow" value="false" /> <property name="testOnReturn" value="false" /> <property name="poolPreparedStatements" value="true" /> <property name="maxPoolPreparedStatementPerConnectionSize" value="20" /> </bean> <!-- mybatis配置 --> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dataSource" /> <!-- 自動掃描mapping.xml檔案 --> <property name="mapperLocations" value="classpath*:mapper/*.xml"></property> <property name="configLocation" value="classpath:mybatis-config.xml"></property> <!-- com.ulic.gpolicyutils --> <property name="typeAliasesPackage" value="com.wangcc.transcation.template.entity"></property> </bean> <!-- DAO介面所在包名,Spring會自動查詢其下的類 --> <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <property name="basePackage" value="com.wangcc.transcation.template.dao" /> <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"></property> </bean> <!-- (事務管理)transaction manager, use JtaTransactionManager for global tx --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource" /> </bean> <bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate"> <property name="transactionManager" ref="transactionManager"/> <!--ISOLATION_DEFAULT 表示由使用的資料庫決定 --> <property name="isolationLevelName" value="ISOLATION_DEFAULT"/> <property name="propagationBehaviorName" value="PROPAGATION_REQUIRED"/> </bean> </beans>

在進行事務管理之前的配置可以忽略不看,就是進行了資料來源以及Spring與Mybatis結合的一些配置。

我們在配置TransactionManager的時候注入了dataSource資料來源屬性。

然後在配置TransactionTemplate的時候又將TransactionManager注入其中,並且配置了隔離級別與事務傳播級別。注意,這兩個關鍵的引數是在TransactionTemplate中指定的。

那我們很明顯的可以知道TransactionTemplate這個類就是我們實現程式設計式事務關鍵的核心類。

我們來看下我們是怎麼使用呼叫這個類的。

@Service
public class AccountService {
    @Autowired
    private AccountDao accountDao;
    @Autowired
    private TransactionTemplate template;

    public void transfer(String inUserName, String outUserName, Double money) {
        template.execute(new TransactionCallbackWithoutResult() {
            @Override
            protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {
                accountDao.reduceMoney(outUserName, money);
                int i = 1 / 0;
                accountDao.addMoney(inUserName, money);
            }
        });
    }
}

非常簡單,當我們想要對一個方法使用程式設計式的事務的時候,我們只需要呼叫TransactionTemplate的execute方法即可實現程式設計式的事務,非常的簡單,但是你也可以發現,耦合度非常非常的高,這也是為什麼我們之後大多會使用切面來宣告事務的原因。

要實現程式設計式的事務,只需要呼叫execute方法,然後使用一個匿名類來構造execute方法的入參,入參型別是TransactionCallback,接著將我們具體的方法內容放置在該型別中的doInTransaction方法中。但是你看上述的程式碼,並不是實現了doInTransaction方法呀,彆著急TransactionCallbackWithoutResult是TransactionCallback的一個子類,doInTransactionWithoutResult是該類重寫的doInTransaction呼叫的一個方法。

程式設計式的事務實現起來非常簡單,但是很顯然耦合度過高,已不推薦使用,也應該是實現Spring事務的唯一一種沒有使用到代理模式思想的實現方式。而其他的所謂的三種實現Spring事務的方法,核心都是動態代理,當然你也可以說是攔截器,但是歸根節點還是動態代理。

程式設計式事務原始碼解析

看完了如何實現程式設計式事務,我們就來簡單分析下程式設計式事務實現的原理把。

顯然我們分析的核心肯定是TransactionTemplate類,我們先看這個類的宣告

public class TransactionTemplate extends DefaultTransactionDefinition
        implements TransactionOperations, InitializingBean {

我們發現他繼承了DefaultTransactionDefinition這個類

實現了InitializingBean介面,這個介面已經很熟悉了,會在建立bean之後執行他的afterPropertiesSet方法。

還實現了TransactionOperations介面,正是這個介面,定義了我們剛才多次提到的execute方法。

  • DefaultTransactionDefinition

我們先來看看他的父類DefaultTransactionDefinition

public class DefaultTransactionDefinition implements TransactionDefinition, Serializable {
    public static final String PREFIX_PROPAGATION = "PROPAGATION_";

    /** Prefix for the isolation constants defined in TransactionDefinition */
    public static final String PREFIX_ISOLATION = "ISOLATION_";

    /** Prefix for transaction timeout values in description strings */
    public static final String PREFIX_TIMEOUT = "timeout_";

    /** Marker for read-only transactions in description strings */
    public static final String READ_ONLY_MARKER = "readOnly";


    /** Constants instance for TransactionDefinition */
    static final Constants constants = new Constants(TransactionDefinition.class);

    private int propagationBehavior = PROPAGATION_REQUIRED;

    private int isolationLevel = ISOLATION_DEFAULT;

    private int timeout = TIMEOUT_DEFAULT;

    private boolean readOnly = false;

    private String name;
    //....
    }

我們發現他給出了預設的isolationLevel(事務隔離級別),propagationBehavior(事務傳播屬性),timeout(超時時間),readOnly(是否只讀)等等。

而所有的事務隔離級別,事務傳播屬性值的定義都在DefaultTransactionDefinition實現的介面TransactionDefinition中。

我們在DefaultTransactionDefinition中看到有isolationLevel和propagationBehavior這兩個Fields。所以這個時候我們就明白了在配置檔案中指定的事務隔離級別和事務傳播屬性就是DefaultTransactionDefinition中的這兩個屬性。

關於父類DefaultTransactionDefinition的介紹就到這裡,主要就是給出了預設的事務隔離級別和事務傳播屬性,並且暴露出屬性和給出值讓使用者可以自定義事務隔離級別和事務傳播屬性等。

  • InitializingBean

    TransactionTemplate實現了InitializingBean介面,那我們就有必要看看實現這個介面的目的是什麼。

    @Override
    public void afterPropertiesSet() {
        if (this.transactionManager == null) {
            throw new IllegalArgumentException("Property 'transactionManager' is required");
        }
    }

    方法很簡單,目的也一目瞭然,就是在建立這個Bean之後,一定要向其中注入了transactionManager屬性,否則就要報錯,他必須有transactionManager屬性,因為真正去實現事務的類還是在transactionManager中。

  • TransactionOperations

    這個介面定義了execute方法。

    也是我們在程式中直接呼叫的方法,我們需要重點關注。

    @Override
    public <T> T execute(TransactionCallback<T> action) throws TransactionException {
        if (this.transactionManager instanceof CallbackPreferringPlatformTransactionManager) {
            return ((CallbackPreferringPlatformTransactionManager) this.transactionManager).execute(this, action);
        }
        else {
            //得到事務
            TransactionStatus status = this.transactionManager.getTransaction(this);
            T result;
            try {
                //開啟事務,呼叫回撥方法
                result = action.doInTransaction(status); //即我們具體業務邏輯實現的地方
            }
            catch (RuntimeException ex) {
                // Transactional code threw application exception -> rollback
                //如果是RuntimeException異常,回顧
                rollbackOnException(status, ex);
                throw ex;
            }
            catch (Error err) {
                // Transactional code threw error -> rollback
                //如果是Error 虛擬機器產生的異常,回滾
                rollbackOnException(status, err);
                throw err;
            }
            catch (Exception ex) {
                // Transactional code threw unexpected exception -> rollback
                rollbackOnException(status, ex);
                throw new UndeclaredThrowableException(ex, "TransactionCallback threw undeclared checked exception");
            }
            //提交事務
            this.transactionManager.commit(status);
            return result;
        }
    }