Spring 框架之九陰真經

Chapter One . Spring 事務配置


1.事務(Transaction)是併發控制的單位,是用戶定義的一個操作序列。

   這些操作要麼都做,要麼都不做,是一個不可分割的工作單位。通過事務,SQL Server能將邏輯相關的一組操作綁定在一起,以便服務器保持數據的完整性。

事務通常是以Begin,Transaction 開始,以Commit或Rollback結束。

Commit:表示提交,即提交事務的所有操作。具體地說就是將事務中所有對數據庫的更新寫回到磁盤上的物理數據庫中去,事務正常結束。

Rollback:表示回滾,即在事務運行的過程中發生了某種故障,事務不能繼續進行,系統將事務中對數據庫的所有以完成的操作全部撤消,滾回到事務開始的狀態。


2.事務的特性(ACID特性)

A:原子性(Atomicity) 事務是數據庫的邏輯工作單位,事務中包括的諸操作要麼全做,要麼全不做。

C:一致性(Consistency) 事務執行的結果必須是使數據庫從一個一致性狀態變到另一個一致性狀態。一致性與原子性是密切相關的。

I:隔離性(Isolation) 一個事務的執行不能被其他事務干擾。

D:持續性/永久性(Durability) 一個事務一旦提交,它對數據庫中數據的改變就應該是永久性的。


3. 分爲編程式事務管理和聲明式事務管理兩種方式。

編程式事務管理使用TransactionTemplate或者直接使用底層的PlatformTransactionManager。對於編程式事務管理,spring推薦使用TransactionTemplate。

聲明式事務管理建立在AOP之上的。其本質是對方法前後進行攔截,然後在目標方法開始之前創建或者加入一個事務,在執行完目標方法之後根據執行情況提交或者回滾事務。聲明式事務最大的優點就是不需要通過編程的方式管理事務,這樣就不需要在業務邏輯代碼中摻雜事務管理的代碼,只需在配置文件中做相關的事務規則聲明(或通過基於@Transactional註解的方式),便可以將事務規則應用到業務邏輯中。

       顯然聲明式事務管理要優於編程式事務管理,這正是spring倡導的非侵入式的開發方式。聲明式事務管理使業務代碼不受污染,一個普通的POJO對象,只要加上註解就可以獲得完全的事務支持。和編程式事務相比,聲明式事務唯一不足地方是,後者的最細粒度只能作用到方法級別,無法做到像編程式事務那樣可以作用到代碼塊級別。但是即便有這樣的需求,也存在很多變通的方法,比如,可以將需要進行事務管理的代碼塊獨立爲方法等等。

         聲明式事務管理也有兩種常用的方式,一種是基於tx和aop名字空間的xml配置文件,另一種就是基於@Transactional註解。顯然基於註解的方式更簡單易用,更清爽。

spring事務特性

spring所有的事務管理策略類都繼承自org.springframework.transaction.PlatformTransactionManager接口


其中TransactionDefinition接口定義以下特性:

事務隔離級別

  隔離級別是指若干個併發的事務之間的隔離程度。

  TransactionDefinition 接口中定義了五個表示隔離級別的常量:

  • TransactionDefinition.ISOLATION_DEFAULT:這是默認值,表示使用底層數據庫的默認隔離級別。對大部分數據庫而言,通常這值就是TransactionDefinition.ISOLATION_READ_COMMITTED。

  • TransactionDefinition.ISOLATION_READ_UNCOMMITTED:該隔離級別表示一個事務可以讀取另一個事務修改但還沒有提交的數據。該級別不能防止髒讀,不可重複讀和幻讀,因此很少使用該隔離級別。比如PostgreSQL實際上並沒有此級別。

  • TransactionDefinition.ISOLATION_READ_COMMITTED:該隔離級別表示一個事務只能讀取另一個事務已經提交的數據。該級別可以防止髒讀,這也是大多數情況下的推薦值。

  • TransactionDefinition.ISOLATION_REPEATABLE_READ:該隔離級別表示一個事務在整個過程中可以多次重複執行某個查詢,並且每次返回的記錄都相同。該級別可以防止髒讀和不可重複讀。

  • TransactionDefinition.ISOLATION_SERIALIZABLE:所有的事務依次逐個執行,這樣事務之間就完全不可能產生干擾,也就是說,該級別可以防止髒讀、不可重複讀以及幻讀。但是這將嚴重影響程序的性能。通常情況下也不會用到該級別。

事務傳播行爲

      所謂事務的傳播行爲是指,如果在開始當前事務之前,一個事務上下文已經存在,此時有若干選項可以指定一個事務性方法的執行行爲。在TransactionDefinition定義中包括瞭如下幾個表示傳播行爲的常量:

  • 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 中以 int 的值來表示超時時間,其單位是秒。

  默認設置爲底層事務系統的超時值,如果底層數據庫事務系統沒有設置超時值,那麼就是none,沒有超時限制。

事務只讀屬性:只讀事務用於客戶代碼只讀但不修改數據的情形,只讀事務用於特定情景下的優化,比如使用Hibernate的時候。

默認爲讀寫事務。



        “只讀事務”並不是一個強制選項,它只是一個“暗示”,提示數據庫驅動程序和數據庫系統,這個事務並不包含更改數據的操作,那麼JDBC驅動程序和數據庫就有可能根據這種情況對該事務進行一些特定的優化,比方說不安排相應的數據庫鎖,以減輕事務對數據庫的壓力,畢竟事務也是要消耗數據庫的資源的。 

但是你非要在“只讀事務”裏面修改數據,也並非不可以,只不過對於數據一致性的保護不像“讀寫事務”那樣保險而已。 

因此,“只讀事務”僅僅是一個性能優化的推薦配置而已,並非強制你要這樣做不可


spring事務回滾規則


     指示spring事務管理器回滾一個事務的推薦方法是在當前事務的上下文內拋出異常。spring事務管理器會捕捉任何未處理的異常,然後依據規則決定是否回滾拋出異常的事務。

        默認配置下,spring只有在拋出的異常爲運行時unchecked異常時纔回滾該事務,也就是拋出的異常爲RuntimeException的子類(Errors也會導致事務回滾),而拋出checked異常則不會導致事務回滾。可以明確的配置在拋出那些異常時回滾事務,包括checked異常。也可以明確定義那些異常拋出時不回滾事務。還可以編程性的通過setRollbackOnly()方法來指示一個事務必須回滾,在調用完setRollbackOnly()後你所能執行的唯一操作就是回滾。



  • myBatis爲例   基於註解的聲明式事務管理配置@Transactional

spring.xml

   

<!-- mybatis config -->  
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">  
        <property name="dataSource" ref="dataSource" />  
        <property name="configLocation">  
            <value>classpath:mybatis-config.xml</value>  
        </property>  
    </bean>  
      
    <!-- mybatis mappers, scanned automatically -->  
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">  
        <property name="basePackage">  
            <value>  
                com.baobao.persistence.test  
            </value>  
        </property>  
        <property name="sqlSessionFactory" ref="sqlSessionFactory" />  
    </bean>  
      
    <!-- 配置spring的PlatformTransactionManager,名字爲默認值 -->  
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">  
        <property name="dataSource" ref="dataSource" />  
    </bean>  
      
    <!-- 開啓事務控制的註解支持 -->  
    <tx:annotation-driven transaction-manager="transactionManager"/>

添加tx名字空間

xmlns:aop="http://www.springframework.org/schema/aop" 
xmlns:tx="http://www.springframework.org/schema/tx"  
xsi:schemaLocation="http://www.springframework.org/schema/aop 
                    http://www.springframework.org/schema/aop/spring-aop-3.0.xsd  
                http://www.springframework.org/schema/tx 
                http://www.springframework.org/schema/tx/spring-tx-3.0.xsd"


        MyBatis自動參與到spring事務管理中,無需額外配置,只要org.mybatis.spring.SqlSessionFactoryBean引用的數據源與DataSourceTransactionManager引用的數據源一致即可,否則事務管理會不起作用。


@Transactional註解


@Transactional屬性 

屬性類型描述
valueString可選的限定描述符,指定使用的事務管理器
propagationenum: Propagation可選的事務傳播行爲設置
isolationenum: Isolation可選的事務隔離級別設置
readOnlyboolean讀寫或只讀事務,默認讀寫
timeoutint (in seconds granularity)事務超時時間設置
rollbackForClass對象數組,必須繼承自Throwable導致事務回滾的異常類數組
rollbackForClassName類名數組,必須繼承自Throwable導致事務回滾的異常類名字數組
noRollbackForClass對象數組,必須繼承自Throwable不會導致事務回滾的異常類數組
noRollbackForClassName類名數組,必須繼承自Throwable不會導致事務回滾的異常類名字數組

用法

       @Transactional 可以作用於接口、接口方法、類以及類方法上。當作用於類上時,該類的所有 public 方法將都具有該類型的事務屬性,同時,我們也可以在方法級別使用該標註來覆蓋類級別的定義。

         雖然 @Transactional 註解可以作用於接口、接口方法、類以及類方法上,但是 Spring 建議不要在接口或者接口方法上使用該註解,因爲這只有在使用基於接口的代理時它纔會生效。另外, @Transactional 註解應該只被應用到 public 方法上,這是由 Spring AOP 的本質決定的。如果你在 protected、private 或者默認可見性的方法上使用 @Transactional 註解,這將被忽略,也不會拋出任何異常。

        默認情況下,只有來自外部的方法調用纔會被AOP代理捕獲,也就是,類內部方法調用本類內部的其他方法並不會引起事務行爲,即使被調用方法使用@Transactional註解進行修飾。

@Autowired  
private MyBatisDao dao;  
  
@Transactional  
@Override  
public void insert(Test test) {  
    dao.insert(test);  
    throw new RuntimeException("test");//拋出unchecked異常,觸發事物,回滾  
}


noRollbackFor類,當作用於類上時,該類的所有 public 方法將都具有該類型的事務屬性

@Transactional(noRollbackFor=RuntimeException.class)  
    @Override  
    public void insert(Test test) {  
        dao.insert(test);  
        //拋出unchecked異常,觸發事物,noRollbackFor=RuntimeException.class,不回滾  
        throw new RuntimeException("test");  
    } 
    
    
    @Transactional  
public class MyBatisServiceImpl implements MyBatisService {  
  
    @Autowired  
    private MyBatisDao dao;  
      
      
    @Override  
    public void insert(Test test) {  
        dao.insert(test);  
        //拋出unchecked異常,觸發事物,回滾  
        throw new RuntimeException("test");  
    }


propagation=Propagation.NOT_SUPPORTED

@Transactional(propagation=Propagation.NOT_SUPPORTED)  
@Override  
public void insert(Test test) {  
    //事物傳播行爲是PROPAGATION_NOT_SUPPORTED,以非事務方式運行,不會存入數據庫  
    dao.insert(test);  
}
  • myBatis爲例   基於註解的聲明式事務管理配置,xml配置

主要爲aop切面配置,只看xml就可以了

<!-- 事物切面配置 -->  
<tx:advice id="advice" transaction-manager="transactionManager">  
    <tx:attributes>  
        <tx:method name="update*" propagation="REQUIRED" read-only="false" rollback-for="java.lang.Exception"/>  
        <tx:method name="insert" propagation="REQUIRED" read-only="false"/>  
    </tx:attributes>  
</tx:advice>  
  
<aop:config>  
    <aop:pointcut id="testService" expression="execution (* com.baobao.service.MyBatisService.*(..))"/>  
    <aop:advisor advice-ref="advice" pointcut-ref="testService"/>  
</aop:config>

 


Chapter Two  Spring事務配置的方式



spacer.gif


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

    第一種方式:每個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>

    第二種方式:所有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>

第三種方式:使用攔截器

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

第四種方式:使用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>
第五種方式:全註解
<?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();
    }
    
    
}





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