Spring框架系列(六)--事務Transaction


  本文絕大部分內容為轉載,原文地址:https://blog.csdn.net/trigl/article/details/50968079

  除此之外,后面還有延伸內容

  事務在企業日常開發中幾乎是一定會遇到的,例如一個審核的流程可能涉及到查詢、修改、插入等操作,所以保證事務性是很有必要的。一般

就是開啟事務支持,然后@Transactional,但是事務不僅僅是這些,可以了解一下細節

事務:

  說白了就是一系列操作要么成功,要么失敗,最典型的場景就是轉賬

一、事務四大特性:ACID

1、原子性(Atomicity):

  事務是一個原子操作,由一系列動作組成。事務的原子性確保動作要么全部完成,要么完全失敗

2、一致性(Consistency):

  一旦事務完成(不管成功還是失敗),系統必須確保它處於一致的狀態,而不會是部分完成部分失敗。在現實中的數據不應該被破壞

例如:賬戶A有1200塊錢,賬戶B有900塊錢,一共是2100塊錢,A向B轉賬300塊錢,A有900,B有1200,A+B還是2100。這就是一致性

3、隔離性(Isolation):

  多個事務會同時處理相同的數據,因此每個事務都應該與其他事務隔離開來,防止數據損壞

4、持久性(Durability):

  一旦事務完成,無論發生什么系統錯誤,它的結果都不應該受到影響,這樣就能從任何系統崩潰中恢復過來。通常情況下,事務的結果被寫

到持久化存儲器中

二、事務三大要素

  1、DataSource:事務的真正處理者,如MySQL等

  2、TransactionManager:管理事務的處理過程,如打開、提交、回滾

  3、事務應用和屬性配置:作為一個標識符表明哪些方法要參與事務,一些配置:隔離級別、超時時間等

三、核心接口

Spring事務設計的接口如下:

四、事務管理器

  Spring並不直接管理事務,而是提供了多種事務管理器,他們將事務管理的職責委托給Hibernate或者JTA等持久化機制所提供的相關平台框架

的事務來實現

PlatformTransactionManager:

  Spring事務管理器的接口,通過這個接口,JDBC、Hibernate等提供了對應的任務管理器,進行具體的實現

具體代碼:

public interface PlatformTransactionManager {
    //由TransactionDefinition得到TransactionStatus對象
    TransactionStatus getTransaction(@Nullable TransactionDefinition definition) throws TransactionException;
    //提交事務
    void commit(TransactionStatus status) throws TransactionException;
    //回滾事務
    void rollback(TransactionStatus status) throws TransactionException;
}

1、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>

  JpaTransactionManager只需要裝配一個JPA實體管理工廠(javax.persistence.EntityManagerFactory接口的任意實現)。

JpaTransactionManager將與由工廠所產生的JPA EntityManager合作來構建事務。

4、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();       // 事務是否只讀,事務管理器能夠根據這個返回值進行優化,確保事務是只讀的
}

5.1).Spring定義了七種傳播行為:

具體內容可參考:https://www.open-open.com/lib/view/open1350865116821.html

5.2).隔離級別

  定義了一個事務可能受其他並發事務影響的程度

5.2.1).並發事務可能引起的問題

  多個事務並發運行,經常會操作相同的數據來完成各自的任務。並發雖然是必須的,但可能會導致一下的問題 

1).臟讀(Dirty reads):

  臟讀發生在一個事務讀取了另一個事務改寫但尚未提交的數據時。如果改寫在稍后被回滾了,那么第一個事務獲取的數據就是無效的。

2).不可重復讀(Nonrepeatable read):

  發生在一個事務執行相同的查詢兩次或兩次以上,但是每次都得到不同的數據時。通常是因為另一個並發事務在兩次查詢期間進行了更新。

例如:

  1、第一次:select score from score where stu_id = '1001',結果為78分

  2、這時候另一個事務:update score set score = 80 where stu_id = '1001'

  3、然后再次查詢的時候,就發現1001號同學分數變成80分了

3).幻讀(Phantom read):

  幻讀與不可重復讀類似。它發生在一個事務(T1)讀取了幾行數據,接着另一個並發事務(T2)插入了一些數據時。在隨后的查詢中,第一

事務(T1)就會發現多了一些原本不存在的記錄。

PS:不可重復讀的重點是修改,幻讀的重點在於新增或者刪除,對於前者, 只需要鎖住滿足條件的記錄。對於后者, 要鎖住滿足條件及其相近

的記錄。

寫讀是臟讀,讀寫讀是不可重復讀,where insert where是幻讀。

5.2.2).隔離級別

  1、DEFAULT:使用底層數據存儲的默認隔離級別。MySQL的默認隔離級別是REPEATABLE-READ。

  2、READ_UNCOMMITTED:最低的隔離級別,允許讀取尚未提交的數據變更,可能會導致臟讀、幻讀或不可重復讀

  3、READ_COMMITTED:允許讀取並發事務已經提交的數據,可以阻止臟讀,但是幻讀或不可重復讀仍有可能發生

  4、REPEATABLE_READ:對同一字段的多次讀取結果都是一致的,除非數據是被本身事務自己所修改,可以阻止臟讀和不可重復讀,但幻讀仍有

可能發生

  5、SERIALIZABLE:可串行化。最高的隔離級別,完全服從ACID的隔離級別,確保阻止臟讀、不可重復讀以及幻讀,也是最慢的事務隔離級別,

因為它通常是通過完全鎖定事務相關的數據庫表來實現的

5.3).只讀

  事務的第三個特性是它是否為只讀事務。如果事務只對后端的數據庫進行該操作,數據庫可以利用事務的只讀特性來進行一些特定的優化。通

過將事務設置為只讀,你就可以給數據庫一個機會,讓它應用它認為合適的優化措施。

5.4).事務超時

  為了使應用程序很好地運行,事務不能運行太長的時間。因為事務可能涉及對后端數據庫的鎖定,所以長時間的事務會不必要的占用數據庫資

源。事務超時就是事務的一個定時器,在特定時間內事務如果沒有執行完畢,那么就會自動回滾,而不是一直等待其結束。

5.5).回滾規則

  規則定義了哪些異常會導致事務回滾而哪些不會。默認情況下,事務只有遇到運行期異常時才會回滾,而在遇到檢查型異常時不會回滾

  但是你可以聲明事務在遇到特定的檢查型異常時像遇到運行期異常那樣回滾。同樣,你還可以聲明事務遇到特定的異常不回滾,即使這些異常

是運行期異常。

5.6).事務狀態

  上面講到的調用PlatformTransactionManager接口的getTransaction()的方法得到的是TransactionStatus接口的一個實現

這個接口的內容如下:

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

  在回滾或提交的時候需要應用對應的事務狀態。

六、編程式事務

6.1).編程式事務和聲明式事務的區別

  Spring提供了對編程式事務和聲明式事務的支持,編程式事務允許用戶在代碼中精確定義事務的邊界,而聲明式事務(基於AOP)有助於用戶

將操作與事務規則進行解耦。簡單地說,編程式事務侵入到了業務代碼里面,但是提供了更加詳細的事務管理;而聲明式事務由於基於AOP,所以

既能起到事務管理的作用,又可以不影響業務代碼的具體實現。

6.2).如何實現?

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

6.2.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則沒有返回值。

6.2.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);// 回滾
}

七、聲明式事務

7.1).配置方式

注:以下配置代碼參考自Spring事務配置的五種方式

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

7.1.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>

7.1.2).所有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>

    <bean id="transactionBase" 
            class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean" 
            lazy-init="true" abstract="true"> 
        <!-- 配置事務管理器 --> 
        <property name="transactionManager" ref="transactionManager" /> 
        <!-- 配置事務屬性 --> 
        <property name="transactionAttributes"> 
            <props> 
                <prop key="*">PROPAGATION_REQUIRED</prop> 
            </props> 
        </property> 
    </bean>   

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

    <bean id="userDao" parent="transactionBase" > 
        <property name="target" ref="userDaoTarget" />  
    </bean>
</beans>

7.1.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>

7.1.4).使用tx標簽配置的攔截器

<?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" />

    <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>

    <tx:advice id="txAdvice" transaction-manager="transactionManager">
        <tx:attributes>
            <tx:method name="*" propagation="REQUIRED" />
        </tx:attributes>
    </tx:advice>

    <aop:config>
        <aop:pointcut id="interceptorPointCuts"
            expression="execution(* com.bluesky.spring.dao.*.*(..))" />
        <aop:advisor advice-ref="txAdvice"
            pointcut-ref="interceptorPointCuts" />       
    </aop:config>     
</beans>

7.1.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注解,如下:

package com.bluesky.spring.dao;

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;

import com.bluesky.spring.domain.User;

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

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

7.2).一個聲明式事務的實例

注:該實例參考自Spring中的事務管理實例詳解

7.2.1).首先是數據庫表

book(isbn, book_name, price)

account(username, balance)

book_stock(isbn, stock)

7.2.2).然后是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>

使用的類BookShopDao

package com.springinaction.transaction;

public interface BookShopDao {
    // 根據書號獲取書的單價
    public int findBookPriceByIsbn(String isbn);
    // 更新書的庫存,使書號對應的庫存-1
    public void updateBookStock(String isbn);
    // 更新用戶的賬戶余額:account的balance-price
    public void updateUserAccount(String username, int price);
}

BookShopDaoImpl

package com.springinaction.transaction;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;

@Repository("bookShopDao")
public class BookShopDaoImpl implements BookShopDao {

    @Autowired
    private JdbcTemplate JdbcTemplate;

    @Override
    public int findBookPriceByIsbn(String isbn) {
        String sql = "SELECT price FROM book WHERE isbn = ?";

        return JdbcTemplate.queryForObject(sql, Integer.class, isbn);
    }

    @Override
    public void updateBookStock(String isbn) {
        //檢查書的庫存是否足夠,若不夠,則拋出異常
        String sql2 = "SELECT stock FROM book_stock WHERE isbn = ?";
        int stock = JdbcTemplate.queryForObject(sql2, Integer.class, isbn);
        if (stock == 0) {
            throw new BookStockException("庫存不足!");
        }
        String sql = "UPDATE book_stock SET stock = stock - 1 WHERE isbn = ?";
        JdbcTemplate.update(sql, isbn);
    }

    @Override
    public void updateUserAccount(String username, int price) {
        //檢查余額是否不足,若不足,則拋出異常
        String sql2 = "SELECT balance FROM account WHERE username = ?";
        int balance = JdbcTemplate.queryForObject(sql2, Integer.class, username);
        if (balance < price) {
            throw new UserAccountException("余額不足!");
        }       
        String sql = "UPDATE account SET balance = balance - ? WHERE username = ?";
        JdbcTemplate.update(sql, price, username);
    }

}

BookShopService

package com.springinaction.transaction;
public interface BookShopService {
     public void purchase(String username, String isbn);
}

BookShopServiceImpl

package com.springinaction.transaction;

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 的聲明式事務對所有的runtime,unchecked Exception進行回滾,通常情況下,默認值即可。Checked exceptions默認不導致回滾
   * rollbackFor用來精確配置導致回滾的異常類型
   * noRollbackFor用來配置不盡興回滾的異常類型 * 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); } }

Cashier

package com.springinaction.transaction;
import java.util.List;
public interface Cashier {
    public void checkout(String username, List<String>isbns);
}

CashierImpl:CashierImpl.checkout和bookShopService.purchase聯合測試了事務的傳播行為

package com.springinaction.transaction;

import java.util.List;

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

@Service("cashier")
public class CashierImpl implements Cashier {
    @Autowired
    private BookShopService bookShopService;

    @Transactional
    @Override
    public void checkout(String username, List<String> isbns) {
        for(String isbn : isbns) {
            bookShopService.purchase(username, isbn);
        }
    }
}

BookStockException

package com.springinaction.transaction;
public class BookStockException extends RuntimeException {

    private static final long serialVersionUID = 1L;

    public BookStockException() {
        super();
        // TODO Auto-generated constructor stub
    }

    public BookStockException(String arg0, Throwable arg1, boolean arg2,
            boolean arg3) {
        super(arg0, arg1, arg2, arg3);
        // TODO Auto-generated constructor stub
    }

    public BookStockException(String arg0, Throwable arg1) {
        super(arg0, arg1);
        // TODO Auto-generated constructor stub
    }

    public BookStockException(String arg0) {
        super(arg0);
        // TODO Auto-generated constructor stub
    }

    public BookStockException(Throwable arg0) {
        super(arg0);
        // TODO Auto-generated constructor stub
    }
}

UserAccountException

package com.springinaction.transaction;
public class UserAccountException extends RuntimeException {

    private static final long serialVersionUID = 1L;

    public UserAccountException() {
        super();
        // TODO Auto-generated constructor stub
    }

    public UserAccountException(String arg0, Throwable arg1, boolean arg2,
            boolean arg3) {
        super(arg0, arg1, arg2, arg3);
        // TODO Auto-generated constructor stub
    }

    public UserAccountException(String arg0, Throwable arg1) {
        super(arg0, arg1);
        // TODO Auto-generated constructor stub
    }

    public UserAccountException(String arg0) {
        super(arg0);
        // TODO Auto-generated constructor stub
    }

    public UserAccountException(Throwable arg0) {
        super(arg0);
        // TODO Auto-generated constructor stub
    }
}

測試類

package com.springinaction.transaction;

import java.util.Arrays;

import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class SpringTransitionTest {

    private ApplicationContext ctx = null;
    private BookShopDao bookShopDao = null;
    private BookShopService bookShopService = null;
    private Cashier cashier = null;
    {
        ctx = new ClassPathXmlApplicationContext("config/transaction.xml");
        bookShopDao = ctx.getBean(BookShopDao.class);
        bookShopService = ctx.getBean(BookShopService.class);
        cashier = ctx.getBean(Cashier.class);
    }

    @Test
    public void testBookShopDaoFindPriceByIsbn() {
        System.out.println(bookShopDao.findBookPriceByIsbn("1001"));
    }

    @Test
    public void testBookShopDaoUpdateBookStock(){
        bookShopDao.updateBookStock("1001");
    }

    @Test
    public void testBookShopDaoUpdateUserAccount(){
        bookShopDao.updateUserAccount("AA", 100);
    }
    @Test
    public void testBookShopService(){
        bookShopService.purchase("AA", "1001");
    }

    @Test
    public void testTransactionPropagation(){
        cashier.checkout("AA", Arrays.asList("1001", "1002"));
    }
}

以下內容參考自公眾號:java團長

事務注解在類/方法上

  @Transactional注解可以標注到類/method上面,如果同時標注,method上面的注解優先級更高

事務注解在類上的繼承性

  @Transactional注解的作用可以傳播到子類,即如果父類標了子類就不用標了。但倒過來就不行了。父類方法需要在子類中重新聲明而參與

到子類上的注解,這樣才會有事務

事務注解在接口/類上

  @Transactional注解可以用在接口上,也可以在類上。在接口上時,必須使用基於接口的代理才行,即JDK動態代理

  事實是Java的注解不能從接口繼承,如果你使用基於類的代理,即CGLIB,或基於織入方面,即AspectJ,事務設置不會被代理和織入基礎設施

認出來,目標對象不會被包裝到一個事務代理中。

PS:Spring團隊建議注解標注在類上而非接口上

只在public方法上生效?

  當采用代理來實現事務時,(注意是代理),@Transactional注解只能應用在public方法上。當標記在protected、private、package-visible

方法上時,不會產生錯誤,但也不會表現出為它指定的事務配置。可以認為它作為一個普通的方法參與到一個public方法的事務中。

  如果想在非public方法上生效,考慮使用AspectJ(織入方式)。

兩個不靠譜的例子

1、在Service層拋出Exception,在Controller層捕獲,那如果在Service中有異常,那會事務回滾嗎?

@Transactional
public Employee addEmployee() throws Exception {

    Employee employee = new Employee("3y", 23);
    employeeRepository.save(employee);
    // 假設這里出了Exception
    int i = 1 / 0;

    return employee;
}

// Controller調用
@RequestMapping("/add")
public Employee addEmployee() {
    Employee employee = null;
    try {
        employee = employeeService.addEmployee();
    } catch (Exception e) {
        e.printStackTrace();
    }
    return employee;

}

表面上看:

  因為Service層已經拋出了異常,由Controller捕獲。那是否回滾應該由Controller的catch代碼塊中邏輯來決定,如果catch代碼塊沒有回滾,

應該是不會回滾。

實際上:

  會回滾,只要是Runtime Exception及其子類,和Error都會導致回滾

2、當前類下使用一個沒有事務的方法去調用一個有事務的方法,那我們這次調用會怎么樣?是否會有事務呢?

// 沒有事務的方法去調用有事務的方法
public Employee addEmployee2Controller() throws Exception {

    return this.addEmployee();
}

@Transactional
public Employee addEmployee() throws Exception {

    employeeRepository.deleteAll();
    Employee employee = new Employee("3y", 23);

    // 模擬異常
    int i = 1 / 0;

    return employee;
}

表面上看:

  這跟Spring事務的傳播機制有關吧

實際上:

  沒有關系

解釋:

  聲明式事務采用的是AOP,而AOP底層通過代理實現。所以如果我們在類或者方法上標注注解@Transactional,那么會生成一個代理對象

  我們得到的是代理(Proxy)對象,調用addEmployee2Controller()方法,而addEmployee2Controller()方法的邏輯是target.addEmployee()

,調用回原始對象(target)的addEmployee()。所以這次的調壓根就沒有事務存在,更談不上說Spring事務傳播機制了。

2.1).延伸

  如果是在本類中沒有事務的方法來調用標注注解@Transactional方法,最后的結論是沒有事務的。那如果我將這個標注注解的方法移到別的

Service對象上,有沒有事務?

 

@Service
public class TestService {

    @Autowired
    private EmployeeRepository employeeRepository;

    @Transactional
    public Employee addEmployee() throws Exception {

        employeeRepository.deleteAll();
        Employee employee = new Employee("3y", 23);
        // 模擬異常
        int i = 1 / 0;
        return employee;
    }

}

@Service
public class EmployeeService {
@Autowired
private TestService testService; // 沒有事務的方法去調用別的類有事務的方法 public Employee addEmployee2Controller() throws Exception { return testService.addEmployee(); } }

解釋:

  因為我們用的是代理對象(Proxy)去調用addEmployee()方法,那就當然有事務了

上面的內容屬於Spring事務傳播機制

PS:

  只要是以代理方式實現的聲明式事務,無論是JDK動態代理,還是CGLIB直接寫字節碼生成代理,都只有public方法上的事務注解才起作用。

而且必須在代理類外部調用才行,如果直接在目標類里面調用,事務照樣不起作用。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM