基於可靠消息方案的分佈式事務(二):Java中的事務

前言:在上一篇文章 基於可靠消息方案的分佈式事務:Lottor介紹 中介紹了常見的分佈式事務的解決方案以及筆者基於可靠消息方案實現的分佈式事務組件Lottor的原理,並展現了應用的控制檯管理。在正式介紹Lottor的具體實現以前,本文首先將會介紹Java中的事務管理,具體來講是Spring的事務管理。PS:有不少讀者提問Lottor是否開源,這裏統一回答:是開源的,Lottor目前在筆者所在公司的內部項目應用,而且筆者在將耦合的業務代碼重構,將會在下一篇文章同步更新到GitHub,敬請期待。本文較長,適合電腦閱讀。若是已對Java中的事務瞭解,可略過本文,歡迎關注本系列文章。java

Java 事務的類型

事務管理是應用系統開發中必不可少的一部分。Java事務的類型有三種:JDBC事務、JTA(Java Transaction API)事務、容器事務。 常見的容器事務如Spring事務,容器事務主要是J2EE應用服務器提供的,容器事務大可能是基於JTA完成,這是一個基於JNDI的,至關複雜的API實現。首先介紹J2EE開發中的兩個事務:JDBC事務和JTA事務。spring

JDBC 事務

JDBC的一切行爲包括事務是基於一個Connection的,在JDBC中是經過Connection對象進行事務管理。在JDBC中,經常使用的和事務相關的方法是: setAutoCommit、commit、rollback等。sql

默認狀況下,當咱們建立一個數據庫鏈接時,會運行在自動提交模式(Auto-commit)下。這意味着,任什麼時候候咱們執行一條SQL完成以後,事務都會自動提交。因此咱們執行的每一條SQL都是一個事務,而且若是正在運行DML或者DDL語句,這些改變會在每一條SQL語句結束的時存入數據庫。有時候咱們想讓一組SQL語句成爲事務的一部分,那樣咱們就能夠在全部語句運行成功的時候提交,而且若是出現任何異常,這些語句做爲事務的一部分,咱們能夠選擇將其所有回滾。數據庫

public static void main(String[] args) {
        Connection con = null;
        try {
            con = DBConnection.getConnection();
            con.setAutoCommit(false);
            insertAccountData(con, 1, "Pankaj");
            insertAddressData(con, 1, "Albany Dr", "San Jose", "USA");
        }
        catch (SQLException | ClassNotFoundException e) {
            e.printStackTrace();
            try {
                con.rollback();
                System.out.println("JDBC Transaction rolled back successfully");
            }
            catch (SQLException e1) {
                System.out.println("SQLException in rollback" + e.getMessage());
            }
        }
        finally {
            try {
                if (con != null)
                    con.close();
            }
            catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
複製代碼

上面的代碼實現了一個簡單的插入帳號和地址信息的功能,經過事務來控制插入操做,要麼都提交,要麼都回滾。express

JDBC爲使用Java進行數據庫的事務操做提供了最基本的支持。經過JDBC事務,咱們能夠將多個SQL語句放到同一個事務中,保證一個 JDBC 事務不能跨越多個數據庫!因此,若是涉及到多數據庫的操做或者分佈式場景,JDBC事務就無能爲力了。編程

JTA 事務

一般,JDBC事務就能夠解決數據的一致性等問題,鑑於他用法相對簡單,因此不少人關於Java中的事務只知道有JDBC事務,或者有人知道框架中的事務(好比Hibernate、Spring)等。可是,因爲JDBC沒法實現分佈式事務,而現在的分佈式場景愈來愈多,因此,JTA事務就應運而生。後端

JTA(Java Transaction API),Java事務API容許應用程序執行分佈式事務,也就是說事務能夠訪問或更新兩個或更多網絡上的計算機資源。JTA指定事務管理器和分佈式事務系統中涉及的各方之間的標準Java接口:應用程序,應用程序服務器和控制對受事務影響的共享資源的訪問的資源管理器。一個事務定義了徹底成功或根本不產生結果的邏輯工做單元。數組

Java 事務編程接口(JTA:Java Transaction API)和 Java 事務服務 (JTS;Java Transaction Service) 爲 J2EE 平臺提供了分佈式事務服務。分佈式事務(Distributed Transaction)包括事務管理器(Transaction Manager)和一個或多個支持 XA 協議的資源管理器 ( Resource Manager )。咱們能夠將資源管理器看作任意類型的持久化數據存儲;事務管理器承擔着全部事務參與單元的協調與控制。JTA 事務有效的屏蔽了底層事務資源,使應用能夠以透明的方式參入到事務處理中;可是與本地事務相比,XA 協議的系統開銷大,在系統開發過程當中應慎重考慮是否確實須要分佈式事務。若確實須要分佈式事務以協調多個事務資源,則應實現和配置所支持 XA 協議的事務資源,如 JMS、JDBC 數據庫鏈接池等。服務器

JTA裏面提供了 java.transaction.UserTransaction ,裏面定義了下面幾個方法:微信

  • begin()- 開始一個分佈式事務,(在後臺 TransactionManager 會建立一個 Transaction 事務對象並把此對象經過 ThreadLocale 關聯到當前線程上 )
  • commit()- 提交事務(在後臺 TransactionManager 會從當前線程下取出事務對象並把此對象所表明的事務提交)
  • rollback()- 回滾事務(在後臺 TransactionManager 會從當前線程下取出事務對象並把此對象所表明的事務回滾)
  • getStatus()- 返回關聯到當前線程的分佈式事務的狀態 (Status 對象裏邊定義了全部的事務狀態)
  • setRollbackOnly()- 標識關聯到當前線程的分佈式事務將被回滾

值得注意的是,不是使用了UserTransaction就能把普通的JDBC操做直接轉成JTA操做,JTA對DataSource、Connection和Resource 都是有要求的,只有符合XA規範,而且實現了XA規範的相關接口的類才能參與到JTA事務中來。

關於XA規範,讀者能夠根據前一篇文章進行補充,目前主流的數據庫都支持XA規範。使用的流程以下:

要想使用用 JTA 事務,那麼就須要有一個實現 javax.sql.XADataSource 、 javax.sql.XAConnection 和 javax.sql.XAResource 接口的 JDBC 驅動程序。一個實現了這些接口的驅動程序將能夠參與 JTA 事務。一個 XADataSource 對象就是一個 XAConnection 對象的工廠。XAConnection 是參與 JTA 事務的 JDBC 鏈接。

要使用JTA事務,必須使用XADataSource來產生數據庫鏈接,產生的鏈接爲一個XA鏈接。

XA鏈接(javax.sql.XAConnection)和非XA(java.sql.Connection)鏈接的區別在於:XA能夠參與JTA的事務,並且不支持自動提交。

除了數據庫,還有JBoss、JMS的消息中間件ActiveMQ等不少組件都是遵照XA協議,2PC和3PC也是符合XA規範的。具體JTA更多的內容,本文再也不展開,後面有機會專門深刻介紹JTA事務。

Spring 事務管理

Spring支持編程式事務管理和聲明式事務管理兩種方式。Spring全部的事務管理策略類都繼承自PlatformTransactionManager接口,類圖以下:

PlatformTransactionManager類圖

public interface PlatformTransactionManager {
    TransactionStatus getTransaction(TransactionDefinition var1) throws TransactionException;

    void commit(TransactionStatus var1) throws TransactionException;

    void rollback(TransactionStatus var1) throws TransactionException;
}
複製代碼

其中,#getTransaction(TransactionDefinition)方法根據指定的傳播行爲返回當前活動的事務或建立一個新的事務,這個方法裏面的參數是TransactionDefinition類,這個類定義了一些基本的事務屬性。 其餘兩個方法,分別是提交和回滾,傳入TransactionStatus事務狀態值。

事務屬性

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Transactional {
    @AliasFor("transactionManager")
    String value() default "";

    @AliasFor("value")
    String transactionManager() default "";
	// 定義事務的傳播規則
    Propagation propagation() default Propagation.REQUIRED;
	// 指定事務的隔離級別
    Isolation isolation() default Isolation.DEFAULT;
	// 對於長時間運行的事務定義超時時間
    int timeout() default -1;
	// 指定事務爲只讀
    boolean readOnly() default false;
	// rollback-for指定事務對哪些檢查型異常應當回滾而不提交
    Class<? extends Throwable>[] rollbackFor() default {};
	// 致使事務回滾的異常類名字數組
    String[] rollbackForClassName() default {};
	// no-rollback-for指定事務對哪些異常應當繼續執行而不回滾
    Class<? extends Throwable>[] noRollbackFor() default {};
	// 不會致使事務回滾的異常類名字數組
    String[] noRollbackForClassName() default {};
}
複製代碼

其實經過 @Transactional能夠了解事務屬性的定義。事務屬性描述了事務策略如何應用到方法上,事務屬性包含5個方面:

  • 傳播行爲
  • 隔離級別
  • 回滾規則
  • 事務超時
  • 是否只讀

Spring 事務傳播屬性

傳播行爲定義了客戶端與被調用方法之間的事務邊界,即傳播規則回答了這樣的一個問題,新的事務應該被啓動仍是掛起,或者方法是否要在事務環境中運行。Spring定義了7個以PROPAGATION_開頭的常量表示它的傳播屬性。在TransactionDefinition中定義了以下的常量:

名稱 解釋
PROPAGATION_REQUIRED 0 支持當前事務,若是當前沒有事務,就新建一個事務。這是最多見的選擇,也是Spring默認的事務的傳播。
PROPAGATION_SUPPORTS 1 支持當前事務,若是當前沒有事務,就以非事務方式執行。
PROPAGATION_MANDATORY 2 支持當前事務,若是當前沒有事務,就拋出異常。
PROPAGATION_REQUIRES_NEW 3 新建事務,若是當前存在事務,把當前事務掛起。
PROPAGATION_NOT_SUPPORTED 4 以非事務方式執行操做,若是當前存在事務,就把當前事務掛起。
PROPAGATION_NEVER 5 以非事務方式執行,若是當前存在事務,則拋出異常。
PROPAGATION_NESTED 6 若是當前存在事務,則在嵌套事務內執行。若是當前沒有事務,則進行與PROPAGATION_REQUIRED相似的操做。

Spring 隔離級別

隔離級別定義了一個事務可能受其餘併發事務影響的程度。多事務併發可能會致使髒讀、幻讀、不可重複讀等各類讀現象。在TransactionDefinition中定義了以下的常量:

水果 價格 數量
ISOLATION_DEFAULT -1 這是一個PlatfromTransactionManager默認的隔離級別,使用數據庫默認的事務隔離級別。另外四個與JDBC的隔離級別相對應
ISOLATION_READ_UNCOMMITTED 1 這是事務最低的隔離級別,它充許另一個事務能夠看到這個事務未提交的數據。這種隔離級別會產生髒讀,不可重複讀和幻讀。
ISOLATION_READ_COMMITTED 2 保證一個事務修改的數據提交後才能被另一個事務讀取。另一個事務不能讀取該事務未提交的數據。
ISOLATION_REPEATABLE_READ 4 這種事務隔離級別能夠防止髒讀,不可重複讀。可是可能出現幻讀。
ISOLATION_SERIALIZABLE 8 這是花費最高代價可是最可靠的事務隔離級別。事務被處理爲順序執行。除了防止髒讀,不可重複讀外,還避免了幻讀。

是否只讀

若是事務只對後端的數據庫進行讀操做,數據庫能夠利用事務ID只讀特性來進行一些特定的優化。經過將事務設置爲只讀,你就能夠給數據庫一個機會,讓他應用它認爲合適的優化措施。由於是否只讀是在事務啓動的時候由數據庫實施的,因此只有對那些具有可能啓動一個新事務的傳播行爲(PROPAGATION_REQUIRED,PROPAGATION_REQUIRED_NEW,PROPAGATION_NESTED)的方法來講,纔有意義。

事務超時

爲了使應用程序很好地運行,事務不能運行太長時間。由於超時時鐘會在事務開始時啓動,因此只有對那些具有可能啓動一個新事務的傳播行爲(同上幾種)的方法來講,纔有意義。默認設置爲底層事務系統的超時值,若是底層數據庫事務系統沒有設置超時值,那麼就是none,沒有超時限制。

事務回滾

事務回滾規則定義了哪些異常會致使事務回滾而哪些不會。默認狀況下,事務只有在遇到運行時期異常纔回滾,而在遇到檢查型異常時不會回滾。就是拋出的異常爲RuntimeException的子類(Errors也會致使事務回滾),而拋出checked異常則不會致使事務回滾。能夠明確的配置在拋出那些異常時回滾事務,包括checked異常。也能夠明肯定義那些異常拋出時不回滾事務。還能夠經過編程的setRollbackOnly()方法來指示一個事務必須回滾,在調用完setRollbackOnly()後所能執行的惟一操做就是回滾。

配置 Spring 事務管理器

Spring中使用xml進行以下的配置便可:

<!-- 配置事務管理器 -->  
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">  
        <property name="dataSource" ref="dataSource" />  
    </bean>  
複製代碼

若是是Spring Boot,咱們只須要設置好DataSource便可,DataSourceTransactionManagerAutoConfiguration會自動配置好DataSourceTransactionManager

@Bean
		@ConditionalOnMissingBean(PlatformTransactionManager.class)
		public DataSourceTransactionManager transactionManager( DataSourceProperties properties) {
			DataSourceTransactionManager transactionManager = new DataSourceTransactionManager(
					this.dataSource);
			if (this.transactionManagerCustomizers != null) {
				this.transactionManagerCustomizers.customize(transactionManager);
			}
			return transactionManager;
		}
複製代碼

配置了事務管理器後,事務固然仍是得咱們本身去操做,Spring提供了兩種事務管理的方式:編程式事務管理和聲明式事務管理,下面分別看一下如何應用。

編程式事務管理

編程式事務管理咱們能夠經過PlatformTransactionManager實現來進行事務管理,一樣的Spring也爲咱們提供了模板類TransactionTemplate進行事務管理,下面主要介紹模板類。

模板類

咱們須要在配置文件中配置:

<!--配置事務管理的模板-->
    <bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate">
        <property name="transactionManager" ref="transactionManager"></property>
        <!--定義事務隔離級別,-1表示使用數據庫默認級別-->
        <property name="isolationLevelName" value="ISOLATION_DEFAULT"></property>
        <property name="propagationBehaviorName" value="PROPAGATION_REQUIRED"></property>
    </bean>
複製代碼

TransactionTemplate幫咱們封裝了許多代碼,節省了咱們的工做。專門建了一張account表,用於模擬存錢的一個場景。

//BaseSeviceImpl.java
    @Override
    public void insert(String sql, boolean flag) throws Exception {
        dao.insertSql(sql);
        // 若是flag 爲 true ,拋出異常
        if (flag) {
            throw new Exception("has exception!!!");
        }
    }
    //獲取總金額
    @Override
    public Integer sum() {
        return dao.sum();
    }
複製代碼
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:spring-test.xml"})
public class TransactionTest{
    @Resource
    private TransactionTemplate transactionTemplate;
    @Autowired
    private BaseSevice baseSevice;

    @Test
    public void transTest() {
        System.out.println("before transaction");
        Integer sum1 = baseSevice.sum();
        System.out.println("before transaction sum: "+sum1);
        System.out.println("transaction....");
        transactionTemplate.execute(new TransactionCallbackWithoutResult() {
            @Override
            protected void doInTransactionWithoutResult(TransactionStatus status) {
                try {
                    baseSevice.insert("INSERT INTO account VALUES (100);",false);
                    baseSevice.insert("INSERT INTO account VALUES (100);",false);
                } catch (Exception e) {
                    status.setRollbackOnly();
                    e.printStackTrace();
                }
           }
        });
        System.out.println("after transaction");
        Integer sum2 = baseSevice.sum();
        System.out.println("after transaction sum: "+sum2);
    }
}
複製代碼

對於拋出Exception類型的異常且須要回滾時,須要捕獲異常並經過調用status對象的setRollbackOnly()方法告知事務管理器當前事務須要回滾。

聲明式事務管理

聲明式事務管理有兩種經常使用的方式,一種是基於tx和aop命名空間的xml配置文件,一種是基於@Transactional註解,隨着Spring和Java的版本愈來愈高,越趨向於使用註解的方式。

基於tx和aop命名空間的xml配置文件

<tx:advice id="advice" transaction-manager="transactionManager">
        <tx:attributes>
            <tx:method name="insert" propagation="REQUIRED" read-only="false" rollback-for="Exception"/>
        </tx:attributes>
    </tx:advice>

    <aop:config>
        <aop:pointcut id="pointCut" expression="execution (* com.blueskykong.service.*.*(..))"/>
        <aop:advisor advice-ref="advice" pointcut-ref="pointCut"/>
    </aop:config>
複製代碼
@Test
    public void transTest() {
        System.out.println("before transaction");
        Integer sum1 = baseSevice.sum();
        System.out.println("before transaction sum: "+sum1);
        System.out.println("transaction....");
        try{
            baseSevice.insert("INSERT INTO account VALUES (100);",true);
        } catch (Exception e){
            e.printStackTrace();
        }
        System.out.println("after transaction");
        Integer sum2 = baseSevice.sum();
        System.out.println("after transaction sum: "+sum2);
    }
複製代碼

基於@Transactional註解

最經常使用的方式,也是簡單的方式。只須要在配置文件中開啓對註解事務管理的支持。

<tx:annotation-driven transaction-manager="transactionManager"/>
複製代碼

而Spring boot啓動類必需要開啓事務,而開啓事務用的註解就是@EnableTransactionManagement

@Transactional(rollbackFor=Exception.class)
    public void insert(String sql, boolean flag) throws Exception {
        dao.insertSql(sql);
        // 若是flag 爲 true ,拋出異常
        if (flag){
            throw new Exception("has exception!!!");
        }
    }
複製代碼

指定出現Exception異常的時候回滾,遇到檢查性的異常須要回滾,默認狀況下非檢查性異常,包括error也會自動回滾。

總結

本文主要介紹了Java事務的類型:JDBC事務、JTA(Java Transaction API)事務、容器事務。簡要介紹了JDBC事務和JTA事務,詳細介紹了Spring的事務管理,Spring對事務管理是經過事務管理器來實現的。Spring提供了許多內置事務管理器實現,如數據源事務管理器DataSourceTransactionManager、集成JPA的事務管理JpaTransactionManager等。分別介紹了Spring事務屬性包含的5個方面,以及Spring提供的兩種事務管理的方式:編程式事務管理和聲明式事務管理。其中聲明式的事務管理,最爲便捷、使用的更多。經過本文的介紹,但願讀者在接觸分佈式事務時,首先對Java中的事務可以熟悉。JTA事務時,其實也引出了分佈式事務的相關概念,對應2PC和3PC的XA規範。

推薦閱讀

基於可靠消息方案的分佈式事務

訂閱最新文章,歡迎關注個人公衆號

微信公衆號

參考

  1. Spring的事務管理機制
  2. JTA 深度歷險 - 原理與實現
  3. Java中的事務——JDBC事務和JTA事務
  4. Spring事務管理詳解
相關文章
相關標籤/搜索