事務原理及應用

一、核心概念

    1、概念

  數據庫事務:數據庫事務( transaction)是訪問並可能操作各種數據項的一個數據庫操作序列,這些操作要麼全部執行,要麼全部不執行,是一個不可分割的工作單位。

  事務概念擴展:事務概念來源於數據庫事務,擴展爲事務是一個由有限操作集合組成的邏輯單元,包括文件系統,消息隊列,一組不可分割的方法操作等。

     事務操作的目的:

   ① 數據一致,指事務提交時保證事務內的所有操作都成功完成,並且更改永久生效;事務回滾時,保證能夠恢復到事務執行之前的狀態。

   ② 操作隔離,指多個同時執行的事務之間應該相互獨立,互不影響。

 2、事務的四個特性:ACID

  • 原子性(Atomicity):事務是一個原子操作,由一系列動作組成。事務的原子性確保動作要麼全部完成,要麼完全不起作用。
  • 一致性(Consistency):一旦事務完成(不管成功還是失敗),系統必須確保它所建模的業務處於一致的狀態,而不會是部分完成部分失敗。在現實中的數據不應該被破壞。
  • 隔離性(Isolation):可能有許多事務會同時處理相同的數據,因此每個事務都應該與其他事務隔離開來,防止數據損壞。
  • 持久性(Durability):一旦事務完成,無論發生什麼系統錯誤,它的結果都不應該受到影響,這樣就能從任何系統崩潰中恢復過來。通常情況下,事務的結果被寫到持久化存儲器中。

  併發事務帶來的問題

  • 丟失更新(Lost Update): 當兩個或多個事務選擇同一行,最初的事務修改的值,會被後面的事務修改的值覆蓋。
  • 髒讀(Dirty Reads): 當一個事務正在訪問數據,並且對數據進行了修改,而這種修改還沒有提 交到數據庫中,這時,另外一個事務也訪問這個數據,然後使用了這個數據。
  • 不可重複讀(NonRepeatable Reads): 一個事務在讀取某些數據後的某個時間,再次讀取以前讀過的數據,卻發現和以前讀出的數據不一致。
  • 幻讀(Phantom Reads): 一個事務按照相同的查詢條件重新讀取以前查詢過的數據,卻發現其他事務插入了滿足其查詢條件的新數據。

  爲了解決上述提到的事務併發問題,數據庫提供一定的事務隔離機制來解決這個問題。MySQL的InnoDB引擎提供四種隔離級別(即ACID中的隔離性)

  • 讀未提交(READ UNCOMMITTED),能解決幻讀問題問題
  • 讀已提交(READ COMMITTED),能解決不可重複讀取、幻讀問題;
  • 可重複讀(REPEATABLE READ)(默認),能解決髒讀、不可重複讀、幻讀問題;
  • 串行化(SERIALIZABLE)

  InnoDB默認的隔離級別是REPEATABLE READ,其可避免髒讀和不可重複讀,但不能避免幻讀。

 3、傳播行爲(Spring針對方法嵌套調用時事務的創建行爲定義了七種事務傳播機制)

  事務的第一個方面是傳播行爲(propagation behavior)。當事務方法被另一個事務方法調用時,必須指定事務應該如何傳播。例如:方法可能繼續在現有事務中運行,也可能開啓一個新事務,並在自己的事務中運行。Spring定義了七種傳播行爲:

傳播行爲含義

PROPAGATION_REQUIRED

表示當前方法必須運行在事務中。如果當前事務存在,方法將會在該事務中運行。否則,會啓動一個新的事務

PROPAGATION_SUPPORTS

表示當前方法不需要事務上下文,但是如果存在當前事務的話,那麼該方法會在這個事務中運行

PROPAGATION_MANDATORY

表示該方法必須在事務中運行,如果當前事務不存在,則會拋出一個異常

PROPAGATION_REQUIRED_NEW

表示當前方法必須運行在它自己的事務中。一個新的事務將被啓動。如果存在當前事務,在該方法執行期間,當前事務會被掛起。如果使用JTATransactionManager的話,則需要訪問TransactionManager

PROPAGATION_NOT_SUPPORTED

表示該方法不應該運行在事務中。如果存在當前事務,在該方法運行期間,當前事務將被掛起。如果使用JTATransactionManager的話,則需要訪問TransactionManager

PROPAGATION_NEVER

表示當前方法不應該運行在事務上下文中。如果當前正有一個事務在運行,則會拋出異常

PROPAGATION_NESTED

表示如果當前已經存在一個事務,那麼該方法將會在嵌套事務中運行。嵌套的事務可以獨立於當前事務進行單獨地提交或回滾。如果當前事務不存在,那麼其行爲與PROPAGATION_REQUIRED一樣。注意各廠商對這種傳播行爲的支持是有所差異的。可以參考資源管理器的文檔來確認它們是否支持嵌套事務

    4、事務執行原理(以mysql innodb引擎爲例)

  1). redo log

  redo log 即重做日誌,是用來實現事務的持久性。該日誌文件由兩部分組成:重做日誌緩衝(redo log buffer)以及重做日誌文件(redo log),前者是在內存中,後者在磁盤中。當事務提交之後會把所有修改信息都會存到該日誌中, 用於在刷新髒頁到磁盤時,發生錯誤時, 進行數據恢復使用。

start transaction;
select balance from bank where name="Tom";
-- 生成 重做日誌 balance=8000
update bank set balance = balance - 2000;
-- 生成 重做日誌 account=2000
update finance set account = account + 2000;
commit;

 執行流程如圖所示: 

  mysql 爲了提升性能不會把每次的修改都實時同步到磁盤,而是會先存到Buffer Pool(緩衝池)裏 頭,把這個當作緩存來用。然後使用後臺線程將緩存池刷新到磁盤。 當在執行刷新時,宕機或者斷電,可能會丟失部分數據。所以引入了redo log來記錄已成功提交事務的修改信息,並且在事務提交時會把redo log持久化到磁盤,系統重啓之後在讀取redo log恢復最新數據。 簡單來說 , redo log是用來恢復數據的用於保障,已提交事務的持久化特性 ;

2). undo log

 undo log 即回滾日誌,用於記錄數據被修改前的信息。他正好跟前面所說的重做日誌所記錄的相 反,重做日誌記錄數據被修改後的信息。undo log主要記錄的是數據的邏輯變化,爲了在發生錯誤時回滾之前的操作,需要將之前的操作都記錄下來,然後在發生錯誤時纔可以回滾。

    undo log 記錄事務修改之前版本的數據信息,因此假如由於系統錯誤或者rollback操作而回滾的話 可以根據undo log的信息來進行回滾到沒被修改前的狀態。

5、spring事務原理

 純JDBC操作數據庫事務步驟:

  1.     獲取連接 Connection con = DriverManager.getConnection()
  2.     開啓事務con.setAutoCommit(true/false);
  3.     執行CRUD
  4.     提交事務/回滾事務 con.commit() / con.rollback();
  5.     關閉連接 conn.close();

  使用Spring的事務管理功能後,步驟 2 和 4 的代碼,而是由Spirng 自動完成。以註解方式爲例,配置文件開啓註解驅動,在相關的類和方法上通過註解@Transactional標識。Spring 在啓動的時候會去解析生成相關的bean,這時候會查看擁有相關注解的類和方法,並且爲這些類和方法生成代理,並根據@Transaction的相關參數進行相關配置注入,這樣就在代理中增加了事務代碼邏輯(開啓正常提交事務,異常回滾事務)。而實質實現事務功能是通過數據庫的事務處理(見上文,4、事務執行原理)。

二、應用場景

1、JDBC事務

在JDBC中處理事務,都是通過Connection完成的。同一事務中所有的操作,都在使用同一個Connection對象。

Connection的三個方法與事務有關:

  • setAutoCommit(boolean):設置是否爲自動提交事務,如果true(默認值爲true)表示自動提交,也就是每條執行的SQL語句都是一個單獨的事務,如果設置爲false,那麼相當於開啓了事務了;
  • commit():提交結束事務。
  • rollback():回滾結束事務。

JDBC處理事務的代碼格式:

try{
     con.setAutoCommit(false);//開啓事務
     ......
     con.commit();//try的最後提交事務      
} catch() {
    con.rollback();//回滾事務
}

eg:

import cn.itcast.jdbc.JdbcUtils;
import org.junit.Test;
import java.sql.Connection;
import java.sql.SQLException;

public class Demo1 {
    /*
    * 演示轉賬方法
    * 所有對Connect的操作都在Service層進行的處理
    * 把所有connection的操作隱藏起來,這需要使用自定義的小工具(day19_1)
    * */
    public void transferAccounts(String from,String to,double money) {
        //對事務的操作
        Connection con = null;
        try{
            con = JdbcUtils.getConnection();
            con.setAutoCommit(false);
            AccountDao dao = new AccountDao();
            dao.updateBalance(con,from,-money);//給from減去相應金額
            if (true){
                throw new RuntimeException("不好意思,轉賬失敗");
            }
            dao.updateBalance(con,to,+money);//給to加上相應金額
            //提交事務
            con.commit();

        } catch (Exception e) {
            try {
                con.rollback();
            } catch (SQLException e1) {
                e.printStackTrace();
            }
            throw new RuntimeException(e);
        }
    }
    @Test
    public void fun1() {
        transferAccounts("zs","ls",100);
    }
}

補充:

  JDBC事務優缺點:JDBC爲使用Java進行數據庫的事務操作提供了最基本的支持。通過JDBC事務,我們可以將多個SQL語句放到同一個事務中,保證其ACID特性。但是,一個 JDBC 事務不能跨越多個數據庫,不支持多數據庫的操作或分佈式場景。

2、JTA事務

  JTA(Java Transaction API)是一種高層的,與實現無關的,與協議無關的API,應用程序和應用服務器可以使用JTA來訪問事務。JTA提供了跨數據庫連接的事務管理能力。JTA事務管理則由JTA容器實現,J2ee框架中事務管理器與應用程序,資源管理器,以及應用服務器之間的事務通訊。一個分佈式事務包括一個事務管理器和一個或多個資源管理器。

1)JTA的構成

  a、高層應用事務界定接口,供事務客戶界定事務邊界的

  b、X/Open XA協議(資源之間的一種標準化的接口)的標準Java映射,它可以使事務性的資源管理器參與由外部事務管理器控制的事務中

  c、高層事務管理器接口,允許應用程序服務器爲其管理的應用程序界定事務的邊界

2)JTA的主要接口位於javax.transaction包中

  a、UserTransaction接口:讓應用程序得以控制事務的開始、掛起、提交、回滾等。由Java客戶端程序或EJB調用。

  b、TransactionManager 接口:用於應用服務器管理事務狀態

  c、Transaction接口:用於執行相關事務操作

  d、XAResource接口:用於在分佈式事務環境下,協調事務管理器和資源管理器的工作

  e、Xid接口:爲事務標識符的Java映射
補充:

  JTA提供了分佈式事務的解決方案,嚴格的ACID。但是JTA實現複雜,通常情況下,JTA UserTransaction需要從JNDI獲取,如果使用JTA,就需要同時使用JTA和JNDI。

3、容器事務:主要指的是J2EE應用服務器提供的事務管理,如在Spring、Hibernate等框架中都有各自的事務管理功能,表現形式不同,但都是在JAVA事務管理的基礎上實現的。

  Spring事務

  Spring並不直接管理事務,而是提供了多種事務管理器,他們將事務管理的職責委託給Hibernate或者JTA等持久化機制所提供的相關平臺框架的事務來實現。 
Spring事務管理器的接口是org.springframework.transaction.PlatformTransactionManager,通過這個接口,Spring爲各個平臺如JDBC、Hibernate等都提供了對應的事務管理器,但是具體的實現就是各個平臺自己的事情了。此接口的內容如下:

Public interface PlatformTransactionManager()...{  
    // 由TransactionDefinition得到TransactionStatus對象
    TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException; 
    // 提交
    Void commit(TransactionStatus status) throws TransactionException;  
    // 回滾
    Void rollback(TransactionStatus status) throws TransactionException;  
    } 

  

所以Spring事務管理的一個優點就是爲不同的事務API提供一致的編程模型,如JTA、JDBC、Hibernate、JPA。

    Spring事務抽象的核心類圖

  

   部分Spring包含的對PlatformTransactionManager的實現類如下圖所示:

  

 AbstractPlatformTransactionManager抽象類實現了Spring事務的標準流程,其子類DataSourceTransactionManager是我們使用較多的JDBC單數據源事務管理器,而JtaTransactionManager是JTA(Java Transaction API)規範的實現類,另外兩個則分別是JavaEE容器WebLogic和WebSphere的JTA事務管理器的具體實現。

  spring事務核心邏輯

  事務攔截器TransactionInterceptorinvoke方法中,通過調用父類TransactionAspectSupportinvokeWithinTransaction方法進行事務處理,該方法支持聲明式事務和編程式事務。

// TransactionInterceptor.class
@Override
public Object invoke(final MethodInvocation invocation) throws Throwable {
    // 獲取targetClass
    ...

    // Adapt to TransactionAspectSupport's invokeWithinTransaction...
    return invokeWithinTransaction(invocation.getMethod(), targetClass, new InvocationCallback() {
        @Override
        public Object proceedWithInvocation() throws Throwable {
            // 實際執行目標方法
            return invocation.proceed();
        }
    });
}

// TransactionInterceptor父類TransactionAspectSupport.class
protected Object invokeWithinTransaction(Method method, Class<?> targetClass, final InvocationCallback invocation)
        throws Throwable {

    // If the transaction attribute is null, the method is non-transactional.
    // 查詢目標方法事務屬性、確定事務管理器、構造連接點標識(用於確認事務名稱)
    final TransactionAttribute txAttr = getTransactionAttributeSource().getTransactionAttribute(method, targetClass);
    final PlatformTransactionManager tm = determineTransactionManager(txAttr);
    final String joinpointIdentification = methodIdentification(method, targetClass, txAttr);

    if (txAttr == null || !(tm instanceof CallbackPreferringPlatformTransactionManager)) {
        // 事務獲取
        TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);
        Object retVal = null;
        try {
            // 通過回調執行目標方法
            retVal = invocation.proceedWithInvocation();
        }
        catch (Throwable ex) {
            // 目標方法執行拋出異常,根據異常類型執行事務提交或者回滾操作
            completeTransactionAfterThrowing(txInfo, ex);
            throw ex;
        }
        finally {
            // 清理當前線程事務信息
            cleanupTransactionInfo(txInfo);
        }
        // 目標方法執行成功,提交事務
        commitTransactionAfterReturning(txInfo);
        return retVal;
    } else {
        // 帶回調的事務執行處理,一般用於編程式事務
        ...
    }
}

TransactionAspectSupport

//TransactionAspectSupport.class
protected TransactionInfo createTransactionIfNecessary(
        PlatformTransactionManager tm, TransactionAttribute txAttr, final String joinpointIdentification) {
    ...
    TransactionStatus status = null;
    if (txAttr != null) {
        if (tm != null) {
            // 獲取事務
            status = tm.getTransaction(txAttr);
            ...
}

protected void commitTransactionAfterReturning(TransactionInfo txInfo) {
    if (txInfo != null && txInfo.hasTransaction()) {
        ...
        // 提交事務
        txInfo.getTransactionManager().commit(txInfo.getTransactionStatus());
    }
}

protected void completeTransactionAfterThrowing(TransactionInfo txInfo, Throwable ex) {
    if (txInfo != null && txInfo.hasTransaction()) {
        ...
        if (txInfo.transactionAttribute.rollbackOn(ex)) {
            try {
                // 異常類型爲回滾異常,執行事務回滾
                txInfo.getTransactionManager().rollback(txInfo.getTransactionStatus());
            }
            ...
        } else {
            try {
                // 異常類型爲非回滾異常,仍然執行事務提交
                txInfo.getTransactionManager().commit(txInfo.getTransactionStatus());
            }
            ...
}

protected final class TransactionInfo {
    private final PlatformTransactionManager transactionManager;
    ...

    1)spring-jdbc

  如果應用程序中直接使用JDBC來進行持久化,DataSourceTransactionManager會處理事務邊界。爲了使用DataSourceTransactionManager,需要使用如下的XML將其裝配到應用程序的上下文定義中:

    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource" />
    </bean>

  實際上,DataSourceTransactionManager是通過調用java.sql.Connection來管理事務,而後者是通過DataSource獲取到的。通過調用連接的commit()方法來提交事務,同樣,事務失敗則通過調用rollback()方法進行回滾。

    2)Hibernate事務

  如果應用程序的持久化是通過Hibernate實現的,那麼需要使用HibernateTransactionManager。對於Hibernate3,需要在Spring上下文定義中添加如下的<bean>聲明:

    <bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
        <property name="sessionFactory" ref="sessionFactory" />
    </bean>

  sessionFactory屬性需要裝配一個Hibernate的session工廠,HibernateTransactionManager的實現細節是它將事務管理的職責委託給org.hibernate.Transaction對象,而後者是從Hibernate Session中獲取到的。當事務成功完成時,HibernateTransactionManager將會調用Transaction對象的commit()方法,反之,將會調用rollback()方法。

     3)Java持久化API事務(JPA)

  Hibernate多年來一直是事實上的Java持久化標準,但是現在Java持久化API作爲真正的Java持久化標準進入大家的視野。如果使用JPA的話,需要使用Spring的JpaTransactionManager來處理事務。需要在Spring中這樣配置JpaTransactionManager:

    <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
        <property name="sessionFactory" ref="sessionFactory" />
    </bean>

     4)Java原生API事務

  如果沒有使用以上所述的事務管理,或者是跨越了多個事務管理源(比如兩個或者是多個不同的數據源),就需要使用JtaTransactionManager:

    <bean id="transactionManager" class="org.springframework.transaction.jta.JtaTransactionManager">
        <property name="transactionManagerName" value="java:TransactionManager" />
    </bean>

  JpaTransactionManager只需要裝配一個JPA實體管理工廠(javax.persistence.EntityManagerFactory接口的任意實現)。JpaTransactionManager將與由工廠所產生的JPA EntityManager合作來構建事務。

三、Spring事務應用---編程式事務和聲明式事務

  區別:Spring提供了對編程式事務和聲明式事務的支持,編程式事務允許用戶在代碼中精確定義事務的邊界,而聲明式事務(基於AOP)有助於用戶將操作與事務規則進行解耦(對原有代碼無侵入)。 

(1)編程式事務

    Spring提供兩種方式的編程式事務管理,分別是:使用TransactionTemplate和直接使用PlatformTransactionManager。

 1.1)使用TransactionTemplate

  採用TransactionTemplate和採用其他Spring模板,如JdbcTempalte和HibernateTemplate是一樣的方法。它使用回調方法,把應用程序從處理取得和釋放資源中解脫出來。如同其他模板,TransactionTemplate是線程安全的。代碼片段:

    TransactionTemplate tt = new TransactionTemplate(); // 新建一個TransactionTemplate
    Object result = tt.execute(
        new TransactionCallback(){  
            public Object doTransaction(TransactionStatus status){  
                updateOperation();  
                return resultOfUpdateOperation();  
            }  
    }); // 執行execute方法進行事務管理

使用TransactionCallback()可以返回一個值。如果使用TransactionCallbackWithoutResult則沒有返回值。

    1.2)使用PlatformTransactionManager

    DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager(); //定義一個某個框架平臺的TransactionManager,如JDBC、Hibernate
    dataSourceTransactionManager.setDataSource(this.getJdbcTemplate().getDataSource()); // 設置數據源
    DefaultTransactionDefinition transDef = new DefaultTransactionDefinition(); // 定義事務屬性
    transDef.setPropagationBehavior(DefaultTransactionDefinition.PROPAGATION_REQUIRED); // 設置傳播行爲屬性
    TransactionStatus status = dataSourceTransactionManager.getTransaction(transDef); // 獲得事務狀態
    try {
        // 數據庫操作
        dataSourceTransactionManager.commit(status);// 提交
    } catch (Exception e) {
        dataSourceTransactionManager.rollback(status);// 回滾
    }

(2)聲明式事務

    ①聲明式事務原理

  聲明式事務的實現就是通過環繞增強的方式,在目標方法執行之前開啓事務,在目標方法執行之後提交或者回滾事務,事務攔截器的繼承關係圖可以體現這一點:

  

 ②用法:根據代理機制的不同,總結了五種Spring事務的配置方式,配置文件如下:

    1)每個Bean都有一個代理

<?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"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
           http://www.springframework.org/schema/context
           http://www.springframework.org/schema/context/spring-context-2.5.xsd
           http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd">

    <bean id="sessionFactory" 
            class="org.springframework.orm.hibernate3.LocalSessionFactoryBean"> 
        <property name="configLocation" value="classpath:hibernate.cfg.xml" /> 
        <property name="configurationClass" value="org.hibernate.cfg.AnnotationConfiguration" />
    </bean> 

    <!-- 定義事務管理器(聲明式的事務) --> 
    <bean id="transactionManager"
        class="org.springframework.orm.hibernate3.HibernateTransactionManager">
        <property name="sessionFactory" ref="sessionFactory" />
    </bean>

    <!-- 配置DAO -->
    <bean id="userDaoTarget" class="com.bluesky.spring.dao.UserDaoImpl">
        <property name="sessionFactory" ref="sessionFactory" />
    </bean>

    <bean id="userDao" 
        class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean"> 
           <!-- 配置事務管理器 --> 
           <property name="transactionManager" ref="transactionManager" />    
        <property name="target" ref="userDaoTarget" /> 
         <property name="proxyInterfaces" value="com.bluesky.spring.dao.GeneratorDao" />
        <!-- 配置事務屬性 --> 
        <property name="transactionAttributes"> 
            <props> 
                <prop key="*">PROPAGATION_REQUIRED</prop>
            </props> 
        </property> 
    </bean> 
</beans>

   2)所有Bean共享一個代理基類

   3)使用攔截器

<?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"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
           http://www.springframework.org/schema/context
           http://www.springframework.org/schema/context/spring-context-2.5.xsd
           http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd">

    <bean id="sessionFactory" 
            class="org.springframework.orm.hibernate3.LocalSessionFactoryBean"> 
        <property name="configLocation" value="classpath:hibernate.cfg.xml" /> 
        <property name="configurationClass" value="org.hibernate.cfg.AnnotationConfiguration" />
    </bean> 

    <!-- 定義事務管理器(聲明式的事務) --> 
    <bean id="transactionManager"
        class="org.springframework.orm.hibernate3.HibernateTransactionManager">
        <property name="sessionFactory" ref="sessionFactory" />
    </bean> 

    <bean id="transactionInterceptor" 
        class="org.springframework.transaction.interceptor.TransactionInterceptor"> 
        <property name="transactionManager" ref="transactionManager" /> 
        <!-- 配置事務屬性 --> 
        <property name="transactionAttributes"> 
            <props> 
                <prop key="*">PROPAGATION_REQUIRED</prop> 
            </props> 
        </property> 
    </bean>

    <bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator"> 
        <property name="beanNames"> 
            <list> 
                <value>*Dao</value>
            </list> 
        </property> 
        <property name="interceptorNames"> 
            <list> 
                <value>transactionInterceptor</value> 
            </list> 
        </property> 
    </bean> 

    <!-- 配置DAO -->
    <bean id="userDao" class="com.bluesky.spring.dao.UserDaoImpl">
        <property name="sessionFactory" ref="sessionFactory" />
    </bean>
</beans>

    4)使用tx標籤配置的攔截器

    5)全註解

<?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-2.5.xsd
           http://www.springframework.org/schema/context
           http://www.springframework.org/schema/context/spring-context-2.5.xsd
           http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
           http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd">

    <context:annotation-config />
    <context:component-scan base-package="com.bluesky" />

    <tx:annotation-driven transaction-manager="transactionManager"/>

    <bean id="sessionFactory" 
            class="org.springframework.orm.hibernate3.LocalSessionFactoryBean"> 
        <property name="configLocation" value="classpath:hibernate.cfg.xml" /> 
        <property name="configurationClass" value="org.hibernate.cfg.AnnotationConfiguration" />
    </bean> 

    <!-- 定義事務管理器(聲明式的事務) --> 
    <bean id="transactionManager"
        class="org.springframework.orm.hibernate3.HibernateTransactionManager">
        <property name="sessionFactory" ref="sessionFactory" />
    </bean>

</beans>

此時在DAO上需加上@Transactional註解,如下:

import java.util.List;
import org.hibernate.SessionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.orm.hibernate3.support.HibernateDaoSupport;
import org.springframework.stereotype.Component;

@Transactional
@Component("userDao")
public class UserDaoImpl extends HibernateDaoSupport implements UserDao {

    public List<User> listUsers() {
        return this.getSession().createQuery("from User").list();
    }  
}

eg:

數據庫表:

book(isbn, book_name, price) 
account(username, balance) 
book_stock(isbn, stock)

xml配置:

<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.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd">

    <import resource="applicationContext-db.xml" />

    <context:component-scan
        base-package="com.springinaction.transaction">
    </context:component-scan>

    <tx:annotation-driven transaction-manager="txManager"/>

    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource" />
    </bean>

</beans>

BookShopServiceImpl

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

@Service("bookShopService")
public class BookShopServiceImpl implements BookShopService {

    @Autowired
    private BookShopDao bookShopDao;

    /**
     * 1.添加事務註解
     * 使用propagation 指定事務的傳播行爲,即當前的事務方法被另外一個事務方法調用時如何使用事務。
     * 默認取值爲REQUIRED,即使用調用方法的事務
     * REQUIRES_NEW:使用自己的事務,調用的事務方法的事務被掛起。
     *
     * 2.使用isolation 指定事務的隔離級別,最常用的取值爲READ_COMMITTED
     * 3.默認情況下 Spring 的聲明式事務對所有的運行時異常進行回滾,也可以通過對應的屬性進行設置。通常情況下,默認值即可。
     * 4.使用readOnly 指定事務是否爲只讀。 表示這個事務只讀取數據但不更新數據,這樣可以幫助數據庫引擎優化事務。若真的是一個只讀取數據庫值得方法,應設置readOnly=true
     * 5.使用timeOut 指定強制回滾之前事務可以佔用的時間。
     */
    @Transactional(propagation=Propagation.REQUIRES_NEW,
            isolation=Isolation.READ_COMMITTED,
            noRollbackFor={UserAccountException.class},
            readOnly=true, timeout=3)
    @Override
    public void purchase(String username, String isbn) {
        //1.獲取書的單價
        int price = bookShopDao.findBookPriceByIsbn(isbn);
        //2.更新書的庫存
        bookShopDao.updateBookStock(isbn);
        //3.更新用戶餘額
        bookShopDao.updateUserAccount(username, price);
    }
}

  備註:和spring boot集成使用方式參考 https://zhuanlan.zhihu.com/p/227922586 

 四、分佈式事務

  1、5種分佈式事務解決方案原理 https://developer.51cto.com/art/201907/600249.htm

  2、分佈式事務框架-Seata

  (1)簡介

    Seata 是一款開源的分佈式事務解決方案,致力於提供高性能和簡單易用的分佈式事務服務。Seata 將爲用戶提供了 AT、TCC、SAGA 和 XA 事務模式,爲用戶打造一站式的分佈式解決方案。

  

  (2)ShardingSphere 集成了 SEATA作爲分佈式事務解決方案

    Apache ShardingSphere ,是一套開源的分佈式數據庫解決方案組成的生態圈,包括由 JDBC、Proxy 和 Sidecar(規劃中)。TCC 和 Saga 是兩種常見分佈式事務實現方案, 主張開發者自行實現對數據庫的反向操作,來達到數據在回滾時仍能夠保證最終一致性。 SEATA 實現了 SQL 反向操作的自動生成,可以使柔性事務不再必須由開發者介入才能使用。ShardingSphere 集成了 SEATA 作爲柔性事務的使用方案。

      3、分佈式事務框架-Atomikos

     採用微服務之間強一致性實現方式。執行原理如圖所示:

  

 

 

 

 

 感謝閱讀,借鑑了不少大佬資料,如需轉載,請註明出處,謝謝!https://www.cnblogs.com/huyangshu-fs/p/15600093.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章