事務(wù)

事務(wù)應(yīng)該具有4個(gè)屬性:原子性用僧、一致性、隔離性辰如、持久性普监。這四個(gè)屬性通常稱(chēng)為ACID特性。
原子性(atomicity)琉兜。一個(gè)事務(wù)是一個(gè)不可分割的工作單位凯正,事務(wù)中包括的諸操作要么都做,要么都不做豌蟋。
一致性(consistency)廊散。事務(wù)必須是使數(shù)據(jù)庫(kù)從一個(gè)一致性狀態(tài)變到另一個(gè)一致性狀態(tài)。一致性與原子性是密切相關(guān)的梧疲。
隔離性(isolation)允睹。一個(gè)事務(wù)的執(zhí)行不能被其他事務(wù)干擾。即一個(gè)事務(wù)內(nèi)部的操作及使用的數(shù)據(jù)對(duì)并發(fā)的其他事務(wù)是隔離的往声,并發(fā)執(zhí)行的各個(gè)事務(wù)之間不能互相干擾擂找。
持久性(durability)。持久性也稱(chēng)永久性(permanence)浩销,指一個(gè)事務(wù)一旦提交贯涎,它對(duì)數(shù)據(jù)庫(kù)中數(shù)據(jù)的改變就應(yīng)該是永久性的。接下來(lái)的其他操作或故障不應(yīng)該對(duì)其有任何影響慢洋。

傳播行為

事務(wù)的第一個(gè)方面是傳播行為(propagation behavior)塘雳。當(dāng)事務(wù)方法被另一個(gè)事務(wù)方法調(diào)用時(shí),必須指定事務(wù)應(yīng)該如何傳播普筹。例如:方法可能繼續(xù)在現(xiàn)有事務(wù)中運(yùn)行败明,也可能開(kāi)啟一個(gè)新事務(wù),并在自己的事務(wù)中運(yùn)行太防。Spring定義了七種傳播行為:

傳播行為 含義
PROPAGATION_REQUIRED 表示當(dāng)前方法必須運(yùn)行在事務(wù)中妻顶。如果當(dāng)前事務(wù)存在,方法將會(huì)在該事務(wù)中運(yùn)行蜒车。否則讳嘱,會(huì)啟動(dòng)一個(gè)新的事務(wù)
PROPAGATION_SUPPORTS 表示當(dāng)前方法不需要事務(wù)上下文,但是如果存在當(dāng)前事務(wù)的話酿愧,那么該方法會(huì)在這個(gè)事務(wù)中運(yùn)行
PROPAGATION_MANDATORY 表示該方法必須在事務(wù)中運(yùn)行沥潭,如果當(dāng)前事務(wù)不存在,則會(huì)拋出一個(gè)異常
PROPAGATION_REQUIRED_NEW 表示當(dāng)前方法必須運(yùn)行在它自己的事務(wù)中嬉挡。一個(gè)新的事務(wù)將被啟動(dòng)钝鸽。如果存在當(dāng)前事務(wù)汇恤,在該方法執(zhí)行期間,當(dāng)前事務(wù)會(huì)被掛起拔恰。如果使用JTATransactionManager的話因谎,則需要訪問(wèn)TransactionManager
PROPAGATION_NOT_SUPPORTED 表示該方法不應(yīng)該運(yùn)行在事務(wù)中。如果存在當(dāng)前事務(wù)仁连,在該方法運(yùn)行期間蓝角,當(dāng)前事務(wù)將被掛起。如果使用JTATransactionManager的話饭冬,則需要訪問(wèn)TransactionManager
PROPAGATION_NEVER 表示當(dāng)前方法不應(yīng)該運(yùn)行在事務(wù)上下文中。如果當(dāng)前正有一個(gè)事務(wù)在運(yùn)行揪阶,則會(huì)拋出異常
PROPAGATION_NESTED 表示如果當(dāng)前已經(jīng)存在一個(gè)事務(wù)昌抠,那么該方法將會(huì)在嵌套事務(wù)中運(yùn)行。嵌套的事務(wù)可以獨(dú)立于當(dāng)前事務(wù)進(jìn)行單獨(dú)地提交或回滾鲁僚。如果當(dāng)前事務(wù)不存在炊苫,那么其行為與PROPAGATION_REQUIRED一樣。注意各廠商對(duì)這種傳播行為的支持是有所差異的冰沙∏劝可以參考資源管理器的文檔來(lái)確認(rèn)它們是否支持嵌套事務(wù)

事務(wù)的第二個(gè)維度就是隔離級(jí)別(isolation level)。隔離級(jí)別定義了一個(gè)事務(wù)可能受其他并發(fā)事務(wù)影響的程度拓挥。
(1)并發(fā)事務(wù)引起的問(wèn)題
在典型的應(yīng)用程序中唠梨,多個(gè)事務(wù)并發(fā)運(yùn)行,經(jīng)常會(huì)操作相同的數(shù)據(jù)來(lái)完成各自的任務(wù)侥啤。并發(fā)雖然是必須的当叭,但可能會(huì)導(dǎo)致一下的問(wèn)題。

臟讀(Dirty reads)——臟讀發(fā)生在一個(gè)事務(wù)讀取了另一個(gè)事務(wù)改寫(xiě)但尚未提交的數(shù)據(jù)時(shí)盖灸。如果改寫(xiě)在稍后被回滾了蚁鳖,那么第一個(gè)事務(wù)獲取的數(shù)據(jù)就是無(wú)效的。
不可重復(fù)讀(Nonrepeatable read)——不可重復(fù)讀發(fā)生在一個(gè)事務(wù)執(zhí)行相同的查詢兩次或兩次以上赁炎,但是每次都得到不同的數(shù)據(jù)時(shí)醉箕。這通常是因?yàn)榱硪粋€(gè)并發(fā)事務(wù)在兩次查詢期間進(jìn)行了更新。
幻讀(Phantom read)——幻讀與不可重復(fù)讀類(lèi)似徙垫。它發(fā)生在一個(gè)事務(wù)(T1)讀取了幾行數(shù)據(jù)讥裤,接著另一個(gè)并發(fā)事務(wù)(T2)插入了一些數(shù)據(jù)時(shí)。在隨后的查詢中松邪,第一個(gè)事務(wù)(T1)就會(huì)發(fā)現(xiàn)多了一些原本不存在的記錄坞琴。
不可重復(fù)讀與幻讀的區(qū)別

隔離級(jí)別 含義

ISOLATION_DEFAULT 使用后端數(shù)據(jù)庫(kù)默認(rèn)的隔離級(jí)別
ISOLATION_READ_UNCOMMITTED 最低的隔離級(jí)別,允許讀取尚未提交的數(shù)據(jù)變更逗抑,可能會(huì)導(dǎo)致臟讀剧辐、幻讀或不可重復(fù)讀
ISOLATION_READ_COMMITTED 允許讀取并發(fā)事務(wù)已經(jīng)提交的數(shù)據(jù)寒亥,可以阻止臟讀,但是幻讀或不可重復(fù)讀仍有可能發(fā)生
ISOLATION_REPEATABLE_READ 對(duì)同一字段的多次讀取結(jié)果都是一致的荧关,除非數(shù)據(jù)是被本身事務(wù)自己所修改溉奕,可以阻止臟讀和不可重復(fù)讀,但幻讀仍有可能發(fā)生
ISOLATION_SERIALIZABLE 最高的隔離級(jí)別忍啤,完全服從ACID的隔離級(jí)別加勤,確保阻止臟讀、不可重復(fù)讀以及幻讀同波,也是最慢的事務(wù)隔離級(jí)別鳄梅,因?yàn)樗ǔJ峭ㄟ^(guò)完全鎖定事務(wù)相關(guān)的數(shù)據(jù)庫(kù)表來(lái)實(shí)現(xiàn)的

只讀

事務(wù)的第三個(gè)特性是它是否為只讀事務(wù)。如果事務(wù)只對(duì)后端的數(shù)據(jù)庫(kù)進(jìn)行該操作未檩,數(shù)據(jù)庫(kù)可以利用事務(wù)的只讀特性來(lái)進(jìn)行一些特定的優(yōu)化戴尸。通過(guò)將事務(wù)設(shè)置為只讀,你就可以給數(shù)據(jù)庫(kù)一個(gè)機(jī)會(huì)冤狡,讓它應(yīng)用它認(rèn)為合適的優(yōu)化措施孙蒙。

事務(wù)超時(shí)

為了使應(yīng)用程序很好地運(yùn)行,事務(wù)不能運(yùn)行太長(zhǎng)的時(shí)間悲雳。因?yàn)槭聞?wù)可能涉及對(duì)后端數(shù)據(jù)庫(kù)的鎖定挎峦,所以長(zhǎng)時(shí)間的事務(wù)會(huì)不必要的占用數(shù)據(jù)庫(kù)資源。事務(wù)超時(shí)就是事務(wù)的一個(gè)定時(shí)器合瓢,在特定時(shí)間內(nèi)事務(wù)如果沒(méi)有執(zhí)行完畢坦胶,那么就會(huì)自動(dòng)回滾,而不是一直等待其結(jié)束歪玲。

回滾規(guī)則

事務(wù)五邊形的最后一個(gè)方面是一組規(guī)則迁央,這些規(guī)則定義了哪些異常會(huì)導(dǎo)致事務(wù)回滾而哪些不會(huì)。默認(rèn)情況下滥崩,事務(wù)只有遇到運(yùn)行期異常時(shí)才會(huì)回滾岖圈,而在遇到檢查型異常時(shí)不會(huì)回滾(這一行為與EJB的回滾行為是一致的)
但是你可以聲明事務(wù)在遇到特定的檢查型異常時(shí)像遇到運(yùn)行期異常那樣回滾。同樣钙皮,你還可以聲明事務(wù)遇到特定的異常不回滾蜂科,即使這些異常是運(yùn)行期異常。

事務(wù)狀態(tài)

上面講到的調(diào)用PlatformTransactionManager接口的getTransaction()的方法得到的是TransactionStatus接口的一個(gè)實(shí)現(xiàn)短条,這個(gè)接口的內(nèi)容如下:

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

3 編程式事務(wù)

3.1 編程式和聲明式事務(wù)的區(qū)別

Spring提供了對(duì)編程式事務(wù)和聲明式事務(wù)的支持导匣,編程式事務(wù)允許用戶在代碼中精確定義事務(wù)的邊界,而聲明式事務(wù)(基于AOP)有助于用戶將操作與事務(wù)規(guī)則進(jìn)行解耦茸时。
簡(jiǎn)單地說(shuō)贡定,編程式事務(wù)侵入到了業(yè)務(wù)代碼里面,但是提供了更加詳細(xì)的事務(wù)管理可都;而聲明式事務(wù)由于基于AOP缓待,所以既能起到事務(wù)管理的作用蚓耽,又可以不影響業(yè)務(wù)代碼的具體實(shí)現(xiàn)。

3.2 如何實(shí)現(xiàn)編程式事務(wù)旋炒?

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

3.2.1 使用TransactionTemplate

采用TransactionTemplate和采用其他Spring模板瘫镇,如JdbcTempalte和HibernateTemplate是一樣的方法鼎兽。它使用回調(diào)方法,把應(yīng)用程序從處理取得和釋放資源中解脫出來(lái)铣除。如同其他模板谚咬,TransactionTemplate是線程安全的。代碼片段:

    TransactionTemplate tt = new TransactionTemplate(); // 新建一個(gè)TransactionTemplate
    Object result = tt.execute(
        new TransactionCallback(){  
            public Object doTransaction(TransactionStatus status){  
                updateOperation();  
                return resultOfUpdateOperation();  
            }  
    }); // 執(zhí)行execute方法進(jìn)行事務(wù)管理

使用TransactionCallback()可以返回一個(gè)值尚粘。如果使用TransactionCallbackWithoutResult則沒(méi)有返回值序宦。

3.2.2 使用PlatformTransactionManager

示例代碼如下:


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

4 聲明式事務(wù)

4.1 配置方式

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

根據(jù)代理機(jī)制的不同,總結(jié)了五種Spring事務(wù)的配置方式潘明,配置文件如下:

(1)每個(gè)Bean都有一個(gè)代理

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

    <!-- 定義事務(wù)管理器(聲明式的事務(wù)) --> 
    <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"> 
           <!-- 配置事務(wù)管理器 --> 
           <property name="transactionManager" ref="transactionManager" />    
        <property name="target" ref="userDaoTarget" /> 
         <property name="proxyInterfaces" value="com.bluesky.spring.dao.GeneratorDao" />
        <!-- 配置事務(wù)屬性 --> 
        <property name="transactionAttributes"> 
            <props> 
                <prop key="*">PROPAGATION_REQUIRED</prop>
            </props> 
        </property> 
    </bean> 
</beans>

(2)所有Bean共享一個(gè)代理基類(lèi)

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

    <!-- 定義事務(wù)管理器(聲明式的事務(wù)) --> 
    <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"> 
        <!-- 配置事務(wù)管理器 --> 
        <property name="transactionManager" ref="transactionManager" /> 
        <!-- 配置事務(wù)屬性 --> 
        <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>

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

    <!-- 定義事務(wù)管理器(聲明式的事務(wù)) --> 
    <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" /> 
        <!-- 配置事務(wù)屬性 --> 
        <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標(biāo)簽配置的攔截器

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

    <!-- 定義事務(wù)管理器(聲明式的事務(wù)) --> 
    <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>

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

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

</beans>

此時(shí)在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();
    }  
}

4.2 一個(gè)聲明式事務(wù)的實(shí)例

注:該實(shí)例參考自Spring中的事務(wù)管理實(shí)例詳解

首先是數(shù)據(jù)庫(kù)表
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>

使用的類(lèi)
BookShopDao

package com.springinaction.transaction;

public interface BookShopDao {
    // 根據(jù)書(shū)號(hào)獲取書(shū)的單價(jià)
    public int findBookPriceByIsbn(String isbn);
    // 更新書(shū)的庫(kù)存,使書(shū)號(hào)對(duì)應(yīng)的庫(kù)存-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) {
        //檢查書(shū)的庫(kù)存是否足夠钳降,若不夠厚宰,則拋出異常
        String sql2 = "SELECT stock FROM book_stock WHERE isbn = ?";
        int stock = JdbcTemplate.queryForObject(sql2, Integer.class, isbn);
        if (stock == 0) {
            throw new BookStockException("庫(kù)存不足!");
        }
        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.添加事務(wù)注解
     * 使用propagation 指定事務(wù)的傳播行為吓坚,即當(dāng)前的事務(wù)方法被另外一個(gè)事務(wù)方法調(diào)用時(shí)如何使用事務(wù)撵幽。
     * 默認(rèn)取值為REQUIRED,即使用調(diào)用方法的事務(wù)
     * REQUIRES_NEW:使用自己的事務(wù)礁击,調(diào)用的事務(wù)方法的事務(wù)被掛起盐杂。
     *
     * 2.使用isolation 指定事務(wù)的隔離級(jí)別,最常用的取值為READ_COMMITTED
     * 3.默認(rèn)情況下 Spring 的聲明式事務(wù)對(duì)所有的運(yùn)行時(shí)異常進(jìn)行回滾哆窿,也可以通過(guò)對(duì)應(yīng)的屬性進(jìn)行設(shè)置链烈。通常情況下,默認(rèn)值即可挚躯。
     * 4.使用readOnly 指定事務(wù)是否為只讀强衡。 表示這個(gè)事務(wù)只讀取數(shù)據(jù)但不更新數(shù)據(jù),這樣可以幫助數(shù)據(jù)庫(kù)引擎優(yōu)化事務(wù)码荔。若真的是一個(gè)只讀取數(shù)據(jù)庫(kù)值得方法漩勤,應(yīng)設(shè)置readOnly=true
     * 5.使用timeOut 指定強(qiáng)制回滾之前事務(wù)可以占用的時(shí)間感挥。
     */
    @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.獲取書(shū)的單價(jià)
        int price = bookShopDao.findBookPriceByIsbn(isbn);
        //2.更新書(shū)的庫(kù)存
        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聯(lián)合測(cè)試了事務(wù)的傳播行為

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

測(cè)試類(lèi)

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"));
    }
}

OVER

    <!-- 1.啟動(dòng)Spring注解 -->
    <context:annotation-config/>
    <!-- 2.掃描 -->
    <context:component-scan base-package="com.shxt"/>
    <!-- 3.讀取屬性文件 -->
    <context:property-placeholder location="classpath:jdbc.properties"/>
    <!-- 4.配置數(shù)據(jù)源 -->
    <bean id="druidDataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close">
        <property name="driverClassName" value="${jdbc.driverClassName}"/>
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>
    <!-- 5.實(shí)例化JdbcTemplate和注入數(shù)據(jù)源 -->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="druidDataSource"/>
    </bean>
    
    <!-- 6.配置事務(wù)管理器,保證數(shù)據(jù)的完整性和一致性 -->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="druidDataSource"/>
    </bean>

    <!-- 7.啟動(dòng)事務(wù)注解 -->
    <tx:annotation-driven transaction-manager="transactionManager" proxy-target-class="true"/>

    <!-- 8.配置告知哪些方法需要被事務(wù)管理器進(jìn)行管理,設(shè)置約定 -->
        <tx:advice  id="serviceAdvice" transaction-manager="transactionManager">
            <tx:attributes>
                <tx:method name="get*" read-only="true"/>
                <tx:method name="load*" read-only="true"/>
                <tx:method name="query*" read-only="true"/>
                <tx:method name="list*" read-only="true"/>
                <tx:method name="find*" read-only="true"/>
                <tx:method name="sel*" read-only="true"/>
                <tx:method name="login*" read-only="true"/>
                <tx:method name="check*" read-only="true"/>
                <tx:method name="valid*" read-only="true"/>
                <tx:method name="*" propagation="REQUIRED"/>
            </tx:attributes>
        </tx:advice>

package com.shxt.service.impl;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import com.shxt.dao.BookDao;
import com.shxt.service.OneBookService;

@Service
public class OneBookServiceImpl implements OneBookService {
    @Autowired
    private BookDao bookDao;

    //@Transactional//點(diǎn)石成金 將普通方法變成事務(wù)方法
    //@Transactional(readOnly=true)//重要屬性只讀 查詢操作
    //面試題:查詢操作需要事務(wù),保證數(shù)據(jù)的一致性
    //@Transactional(noRollbackFor=RuntimeException.class)
    //@Transactional(timeout=2)
    //@Transactional(propagation=Propagation.REQUIRED)
    @Transactional(propagation=Propagation.REQUIRES_NEW)
    @Override
    public void buyOneBook( String account , String isbn ) {
        int price = this.bookDao.getBookPrice(isbn);
        int balance = this.bookDao.getUserBalance(account);

        if(balance<price){
            throw new RuntimeException("賬號(hào)余額不足,請(qǐng)充值");
        }
        this.bookDao.updateUserBalance(account, price);


        int stock = this.bookDao.getBookStock(isbn);

        if(stock==0){
            throw new RuntimeException("庫(kù)存不足");
        }

        this.bookDao.updateBookStock(isbn);
    }

}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末锯七,一起剝皮案震驚了整個(gè)濱河市链快,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌眉尸,老刑警劉巖域蜗,帶你破解...
    沈念sama閱讀 211,123評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異噪猾,居然都是意外死亡霉祸,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,031評(píng)論 2 384
  • 文/潘曉璐 我一進(jìn)店門(mén)袱蜡,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)丝蹭,“玉大人,你說(shuō)我怎么就攤上這事坪蚁”即” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 156,723評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵敏晤,是天一觀的道長(zhǎng)贱田。 經(jīng)常有香客問(wèn)我,道長(zhǎng)嘴脾,這世上最難降的妖魔是什么男摧? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,357評(píng)論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮译打,結(jié)果婚禮上耗拓,老公的妹妹穿的比我還像新娘。我一直安慰自己奏司,他們只是感情好乔询,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,412評(píng)論 5 384
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著结澄,像睡著了一般哥谷。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上麻献,一...
    開(kāi)封第一講書(shū)人閱讀 49,760評(píng)論 1 289
  • 那天们妥,我揣著相機(jī)與錄音,去河邊找鬼勉吻。 笑死监婶,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播惑惶,決...
    沈念sama閱讀 38,904評(píng)論 3 405
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼煮盼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了带污?” 一聲冷哼從身側(cè)響起僵控,我...
    開(kāi)封第一講書(shū)人閱讀 37,672評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎鱼冀,沒(méi)想到半個(gè)月后报破,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,118評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡千绪,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,456評(píng)論 2 325
  • 正文 我和宋清朗相戀三年充易,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片荸型。...
    茶點(diǎn)故事閱讀 38,599評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡盹靴,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出瑞妇,到底是詐尸還是另有隱情稿静,我是刑警寧澤,帶...
    沈念sama閱讀 34,264評(píng)論 4 328
  • 正文 年R本政府宣布辕狰,位于F島的核電站自赔,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏柳琢。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,857評(píng)論 3 312
  • 文/蒙蒙 一润脸、第九天 我趴在偏房一處隱蔽的房頂上張望柬脸。 院中可真熱鬧,春花似錦毙驯、人聲如沸倒堕。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,731評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)垦巴。三九已至,卻和暖如春铭段,著一層夾襖步出監(jiān)牢的瞬間骤宣,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,956評(píng)論 1 264
  • 我被黑心中介騙來(lái)泰國(guó)打工序愚, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留憔披,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,286評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像芬膝,于是被迫代替她去往敵國(guó)和親望门。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,465評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容