Spring事務管理

Spring事務管理

參考:http://www.mamicode.com/info-detail-1248286.htmlhtml

基本概念

  事務是一系列的動做,它們綜合在一塊兒纔是一個完整的工做單元,這些動做必須所有完成,若是有一個失敗的話,那麼事務就會回滾到最開始的狀態,彷彿什麼都沒發生過同樣。就像銀行的自助取款機,一般都能正常爲客戶服務,可是也不免遇到操做過程當中機器忽然出故障的狀況,此時,事務就必須確保出故障前對帳戶的操做不生效,就像用戶剛纔徹底沒有使用過取款機同樣,以保證用戶和銀行的利益都不受損失。
  通常來講,事務是必須知足4個條件(ACID): Atomicity(原子性)、Consistency(一致性)、Isolation(隔離性)、Durability(持久性)
一、原子性:事務是一個原子操做,由一系列動做組成。事務的原子性確保動做要麼所有完成,要麼徹底不起做用。
二、一致性 :知足模式鎖指定的約束,好比銀行轉帳先後總金額應該不變。事務結束時,全部的內部數據結構(如B樹索引)也都必須是正確的。
三、隔離性:事務獨立運行。一個事務所作的修改在最終提交以前,對其它事務是不可見的。事務的100%隔離,須要犧牲速度。
四、持久性:一旦事務完成,不管發生什麼系統錯誤,它的結果都不該該受到影響,這樣就能從任何系統崩潰中恢復過來。一般狀況下,事務的結果被寫到持久化存儲器中。java

核心接口

  Spring事務管理涉及的接口的聯繫以下:
spring

事務管理器

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

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

  從這裏可知具體的具體的事務管理機制對Spring來講是透明的,它並不關心那些,那些是對應各個平臺須要關心的,因此Spring事務管理的一個優勢就是爲不一樣的事務API提供一致的編程模型,如JTA、JDBC、Hibernate、JPA。下面分別介紹各個平臺框架實現事務管理的機制。數據庫

JDBC事務

  若是應用程序中直接使用JDBC、iBatis和mybatis來進行持久化,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()方法進行回滾。
  MyBatis自動參與到spring事務管理中,無需額外配置,即便用DataSourceTransactionManager便可。api

Hibernate事務

  若是應用程序的持久化是經過Hibernate實習的,那麼你須要使用HibernateTransactionManager。對於Hibernate3,須要在Spring上下文定義中添加以下的 聲明: 數組

<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()方法。安全

Java持久化API事務(JPA)

  Hibernate多年來一直是事實上的Java持久化標準,可是如今Java持久化API做爲真正的Java持久化標準進入你們的視野。若是你計劃使用JPA的話,那你須要使用Spring的JpaTransactionManager來處理事務。你須要在Spring中這樣配置JpaTransactionManager:session

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

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

Java原生API事務

  若是你沒有使用以上所述的事務管理,或者是跨越了多個事務管理源(好比兩個或者是多個不一樣的數據源),你就須要使用JtaTransactionManager:

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

  JtaTransactionManager將事務管理的責任委託給javax.transaction.UserTransaction和javax.transaction.TransactionManager對象,其中事務成功完成經過UserTransaction.commit()方法提交,事務失敗經過UserTransaction.rollback()方法回滾。

基本事務屬性的定義

  上面講到的事務管理器接口PlatformTransactionManager經過getTransaction(TransactionDefinition definition)方法來獲得事務,這個方法裏面的參數是TransactionDefinition類,這個類就定義了一些基本的事務屬性。 那麼什麼是事務屬性呢?事務屬性能夠理解成事務的一些基本配置,描述了事務策略如何應用到方法上。事務屬性包含了5個方面,如圖所示:

  而TransactionDefinition接口內容以下:

public interface TransactionDefinition {
    int getPropagationBehavior(); // 返回事務的傳播行爲
    int getIsolationLevel(); // 返回事務的隔離級別,事務管理器根據它來控制另一個事務能夠看到本事務內的哪些數據
    int getTimeout();  // 返回事務必須在多少秒內完成
    boolean isReadOnly(); // 事務是否只讀,事務管理器可以根據這個返回值進行優化,確保事務是隻讀的
}

  咱們能夠發現TransactionDefinition正好用來定義事務屬性,下面詳細介紹一下各個事務屬性。

傳播行爲

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

  • TransactionDefinition.PROPAGATION_REQUIRED:若是當前存在事務,則加入該事務;若是當前沒有事務,則建立一個新的事務。
  • TransactionDefinition.PROPAGATION_REQUIRES_NEW:建立一個新的事務,若是當前存在事務,則把當前事務掛起。
  • TransactionDefinition.PROPAGATION_SUPPORTS:若是當前存在事務,則加入該事務;若是當前沒有事務,則以非事務的方式繼續運行。
  • TransactionDefinition.PROPAGATION_NOT_SUPPORTED:以非事務方式運行,若是當前存在事務,則把當前事務掛起。
  • TransactionDefinition.PROPAGATION_NEVER:以非事務方式運行,若是當前存在事務,則拋出異常。
  • TransactionDefinition.PROPAGATION_MANDATORY:若是當前存在事務,則加入該事務;若是當前沒有事務,則拋出異常。
  • TransactionDefinition.PROPAGATION_NESTED:若是當前存在事務,則建立一個事務做爲當前事務的嵌套事務來運行;若是當前沒有事務,則該取值等價於TransactionDefinition.PROPAGATION_REQUIRED。嵌套事務一個很是重要的概念就是內層事務依賴於外層事務。外層事務失敗時,會回滾內層事務所作的動做。而內層事務操做失敗並不會引發外層事務的回滾。

隔離級別

  隔離級別定義了一個事務可能受其餘併發事務影響的程度。TransactionDefinition 接口中定義了五個表示隔離級別的常量:

  • TransactionDefinition.ISOLATION_DEFAULT:這是默認值,表示使用底層數據庫的默認隔離級別。對大部分數據庫而言,一般這值就是TransactionDefinition.ISOLATION_READ_COMMITTED。Mysql默認repeatable read。
  • TransactionDefinition.ISOLATION_READ_UNCOMMITTED:該隔離級別表示一個事務能夠讀取另外一個事務修改但尚未提交的數據。該級別不能防止髒讀和不可重複讀或幻讀,所以不多使用該隔離級別。
  • TransactionDefinition.ISOLATION_READ_COMMITTED:該隔離級別表示一個事務只能讀取另外一個事務已經提交的數據。該級別能夠防止髒讀,這也是大多數狀況下的推薦值。
  • TransactionDefinition.ISOLATION_REPEATABLE_READ:該隔離級別表示一個事務在整個過程當中能夠屢次重複執行某個查詢,而且每次返回的記錄都相同,由於他鎖定了操做的行。該級別能夠防止髒讀和不可重複讀,但幻讀仍有可能發生。
  • TransactionDefinition.ISOLATION_SERIALIZABLE:全部的事務依次逐個執行,這樣事務之間就徹底不可能產生干擾,也就是說,該級別能夠防止髒讀、不可重複讀以及幻讀。可是這將嚴重影響程序的性能。一般狀況下也不會用到該級別。

只讀屬性

  事務的只讀屬性是指,對事務性資源進行只讀操做或者是讀寫操做。所謂事務性資源就是指那些被事務管理的資源,好比數據源、 JMS 資源,以及自定義的事務性資源等等。若是肯定只對事務性資源進行只讀操做,那麼咱們能夠將事務標誌爲只讀的,以提升事務處理的性能。在 TransactionDefinition 中以 boolean 類型來表示該事務是否只讀。

事務超時

  所謂事務超時,就是指一個事務所容許執行的最長時間,若是超過該時間限制但事務尚未完成,則自動回滾事務。在 TransactionDefinition 中以 int 的值來表示超時時間,其單位是秒。

事務的回滾規則

  一般狀況下,若是在事務中拋出了未檢查異常(繼承自Error類和 RuntimeException類的異常,其實只須要考慮 RuntimeException類異常),則默認將回滾事務。若是沒有拋出任何異常,或者拋出了已檢查異常,則仍然提交事務。這一般也是大多數開發者但願的處理方式,也是 EJB 中的默認處理方式。可是,咱們能夠根據須要人爲控制事務在拋出某些未檢查異常時仍然提交事務,或者在拋出某些已檢查異常時回滾事務。

事務狀態

  上面講到的調用PlatformTransactionManager接口的getTransaction()的方法獲得的是TransactionStatus接口的一個實現,這個接口的內容以下:

public interface TransactionStatus{
    boolean isNewTransaction(); // 是不是新的事物
    boolean hasSavepoint(); // 是否有恢復點
    void setRollbackOnly();  // 設置爲只回滾
    boolean isRollbackOnly(); // 是否爲只回滾
    boolean isCompleted; // 是否已完成
}

  能夠發現這個接口描述的是一些處理事務提供簡單的控制事務執行和查詢事務狀態的方法,在回滾或提交的時候須要應用對應的事務狀態。

編程式事務

編程式和聲明式事務的區別

  Spring提供了對編程式事務和聲明式事務的支持,編程式事務容許用戶在代碼中精肯定義事務的邊界,而聲明式事務(基於AOP)有助於用戶將操做與事務規則進行解耦。 簡單地說,編程式事務侵入到了業務代碼裏面,可是提供了更加詳細的事務管理;而聲明式事務因爲基於AOP,因此既能起到事務管理的做用,又能夠不影響業務代碼的具體實現。

如何實現編程式事務

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

使用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則沒有返回值。

使用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);// 回滾
        }

聲明式事務

  Spring 的聲明式事務管理在底層是創建在 AOP 的基礎之上的。其本質是對方法先後進行攔截,而後在目標方法開始以前建立或者加入一個事務,在執行完目標方法以後根據執行狀況提交或者回滾事務。
  聲明式事務最大的優勢就是不須要經過編程的方式管理事務,這樣就不須要在業務邏輯代碼中摻瑣事務管理的代碼,只需在配置文件中作相關的事務規則聲明(或經過等價的基於註解的方式),即可以將事務規則應用到業務邏輯中。由於事務管理自己就是一個典型的橫切邏輯,正是 AOP 的用武之地。Spring 開發團隊也意識到了這一點,爲聲明式事務提供了簡單而強大的支持。
  一般狀況下,強烈建議在開發中使用聲明式事務,不只由於其簡單,更主要是由於這樣使得純業務代碼不被污染,極大方便後期的代碼維護,很是符合 非侵入式輕量級容器的理念
  和編程式事務相比,聲明式事務惟一不足地方是,後者的最細粒度只能做用到方法級別,沒法作到像編程式事務那樣能夠做用到代碼塊級別。可是即使有這樣的需求,也存在不少變通的方法,好比,能夠將須要進行事務管理的代碼塊獨立爲方法等等。
  聲明式事務管理也有兩種經常使用的方式,一種是基於tx和aop名字空間的xml配置文件,另外一種就是基於@Transactional註解。顯然基於註解的方式更簡單易用,更清爽。推薦使用全註解的配置方式:
spring-dao.xml:

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

    <!--配置整合mybatis過程
    1.配置數據庫相關參數-->
    <context:property-placeholder location="classpath:jdbc.properties"/>

    <!--2.數據庫鏈接池-->
    <bean id="dataSource" class="com.zaxxer.hikari.HikariDataSource">
        <!--配置鏈接池屬性-->
        <property name="driverClassName" value="${jdbc.driverClassName}" />

        <!-- 基本屬性 url、user、password -->
        <property name="jdbcUrl" value="${jdbc.url}" />
        <property name="username" value="${jdbc.username}" />
        <property name="password" value="${jdbc.password}" />

        <property name="maximumPoolSize" value="50"/>
        <property name="minimumIdle" value="10"/>
        <!--關閉鏈接後不自動commit-->
        <property name="autoCommit" value="false"/>

        <!-- 等待鏈接池分配鏈接的最大時長(毫秒),超過這個時長還沒可用的鏈接則發生SQLException, 缺省:30秒 -->
        <property name="connectionTimeout" value="1000"/>
        <!-- 一個鏈接idle狀態的最大時長(毫秒),超時則被釋放(retired),缺省:10分鐘 -->
        <property name="idleTimeout" value="1000" />
    </bean>

    <!--約定大於配置-->
    <!--3.配置SqlSessionFactory對象-->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <!--往下才是mybatis和spring真正整合的配置-->
        <!--注入數據庫鏈接池-->
        <property name="dataSource" ref="dataSource"/>
        <!--配置mybatis全局配置文件:mybatis-config.xml-->
        <property name="configLocation" value="classpath:mybatis-config.xml"/>
        <!--掃描entity包,使用別名,多個用;隔開-->
        <property name="typeAliasesPackage" value="com.seckill.model"/>
        <!--掃描sql配置文件:mapper須要的xml文件-->
        <property name="mapperLocations" value="classpath:mapper/*.xml"/>
    </bean>

    <!--4:配置掃描Dao接口包,動態實現DAO接口,注入到spring容器-->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <!--注入SqlSessionFactory-->
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
        <!-- 給出須要掃描的Dao接口-->
        <property name="basePackage" value="com.seckill.dao"/>
    </bean>
</beans>

spring-service.xml:

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

    <!--掃描service包下全部使用註解的類型-->
    <context:component-scan base-package="com.seckill.service"/>

    <!--配置事務管理器-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <!--注入數據庫鏈接池-->
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <!--配置基於註解的聲明式事務
    默認使用註解來管理事務行爲-->
    <tx:annotation-driven transaction-manager="transactionManager"/>
</beans>

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

/**
     * 1.添加事務註解
     * 使用propagation 指定事務的傳播行爲,即當前的事務方法被另一個事務方法調用時如何使用事務,默認取值爲REQUIRED,即便用調用方法的事務
     * REQUIRES_NEW:使用本身的事務,調用的事務方法的事務被掛起。
     *
     * 2.使用isolation 指定事務的隔離級別,最經常使用的取值爲READ_COMMITTED,但Mysql默認是REPEATABLE_READ。
     * 3.默認狀況下 Spring 的聲明式事務對全部的運行時異常進行回滾,也能夠經過對應的屬性進行設置。一般狀況下,默認值便可。
     * 4.使用readOnly 指定事務是否爲只讀。 表示這個事務只讀取數據但不更新數據,這樣能夠幫助數據庫引擎優化事務。若真的是一個只讀取數據庫值得方法,應設置readOnly=true
     * 5.使用timeOut 指定強制回滾以前事務能夠佔用的時間。
     */
    @Transactional(propagation=Propagation.REQUIRES_NEW,
            isolation=Isolation.READ_COMMITTED,
            noRollbackFor={UserAccountException.class},
            readOnly=true, timeout=3)
@Component("userDao")
public class UserDaoImpl extends HibernateDaoSupport implements UserDao {

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

@Transactional註解

@Transactional屬性

屬性 類型 描述
value String 可選的限定描述符,指定使用的事務管理器
propagation enum: Propagation 可選的事務傳播行爲設置
isolation enum: Isolation 可選的事務隔離級別設置
readOnly boolean 讀寫或只讀事務,默認讀寫
timeout int (in seconds granularity) 事務超時時間設置
rollbackFor Class對象數組,必須繼承自Throwable 致使事務回滾的異常類數組
rollbackForClassName 類名數組,必須繼承自Throwable 致使事務回滾的異常類名字數組
noRollbackFor Class對象數組,必須繼承自Throwable 不會致使事務回滾的異常類數組
noRollbackForClassName 類名數組,必須繼承自Throwable 不會致使事務回滾的異常類名字數組

用法

  @Transactional能夠做用於接口、接口方法、類以及類方法上。看成用於類上時,該類的全部 public 方法將都具備該類型的事務屬性,同時,咱們也能夠在方法級別使用該標註來覆蓋類級別的定義。
  雖然 @Transactional 註解能夠做用於接口、接口方法、類以及類方法上,可是 Spring 建議不要在接口或者接口方法上使用該註解,由於這隻有在使用基於接口的代理時它纔會生效。另外, @Transactional註解應該只被應用到 public 方法上,這是由 Spring AOP 的本質決定的。若是你在 protected、private 或者默承認見性的方法上使用 @Transactional註解,這將被忽略,也不會拋出任何異常。
  默認狀況下,只有來自外部的方法調用纔會被AOP代理捕獲,也就是,類內部方法調用本類內部的其餘方法並不會引發事務行爲,即便被調用方法使用@Transactional註解進行修飾。

相關文章
相關標籤/搜索