天天看點

Spring筆記(四): spring的程式設計式事務與聲明式事務

一、Spring 事務屬性分析

事務管理對于企業應用而言至關重要。它保證了使用者的每一次操作都是可靠的,即便出現了異常的通路情況,也不至于破壞背景資料的完整性。就像銀行的自助取款機,通常都能正常為客戶服務,但是也難免遇到操作過程中機器突然出故障的情況,此時,事務就必須確定出故障前對賬戶的操作不生效,就像使用者剛才完全沒有使用過取款機一樣,以保證使用者和銀行的利益都不受損失。 在 Spring 中,事務是通過 TransactionDefinition 接口來定義的。該接口包含與事務屬性有關的方法。在 Spring 中,事務是通過 TransactionDefinition 接口來定義的。該接口包含與事務屬性有關的方法。

public interface TransactionDefinition{  
	int getIsolationLevel();  
	int getPropagationBehavior();  
	int getTimeout();  
	boolean isReadOnly();  
} 
           

  (一)事務隔離級别 隔離級别是指若幹個并發的事務之間的隔離程度。TransactionDefinition 接口中定義了五個表示隔離級别的常量:

1、TransactionDefinition.ISOLATION_DEFAULT:這是預設值,表示使用底層資料庫的預設隔離級别。對大部分資料庫而言,通常這值就是TransactionDefinition.ISOLATION_READ_COMMITTED。 2、TransactionDefinition.ISOLATION_READ_UNCOMMITTED:該隔離級别表示一個事務可以讀取另一個事務修改但還沒有送出的資料。該級别不能防止髒讀和不可重複讀,是以很少使用該隔離級别。 3、TransactionDefinition.ISOLATION_READ_COMMITTED:該隔離級别表示一個事務隻能讀取另一個事務已經送出的資料。該級别可以防止髒讀,這也是大多數情況下的推薦值。 4、TransactionDefinition.ISOLATION_REPEATABLE_READ:該隔離級别表示一個事務在整個過程中可以多次重複執行某個查詢,并且每次傳回的記錄都相同。即使在多次查詢之間有新增的資料滿足該查詢,這些新增的記錄也會被忽略。該級别可以防止髒讀和不可重複讀。

5、TransactionDefinition.ISOLATION_SERIALIZABLE:所有的事務依次逐個執行,這樣事務之間就完全不可能産生幹擾,也就是說,該級别可以防止髒讀、不可重複讀以及幻讀。但是這将嚴重影響程式的性能。通常情況下也不會用到該級别。 (二)事務傳播行為 所謂事務的傳播行為是指,如果在開始目前事務之前,一個事務上下文已經存在,此時有若幹選項可以指定一個事務性方法的執行行為。在TransactionDefinition定義中包括了如下幾個表示傳播行為的常量:

1、TransactionDefinition.PROPAGATION_REQUIRED:如果目前存在事務,則加入該事務;如果目前沒有事務,則建立一個新的事務。

2、TransactionDefinition.PROPAGATION_REQUIRES_NEW:建立一個新的事務,如果目前存在事務,則把目前事務挂起。

3、TransactionDefinition.PROPAGATION_SUPPORTS:如果目前存在事務,則加入該事務;如果目前沒有事務,則以非事務的方式繼續運作。

4、TransactionDefinition.PROPAGATION_NOT_SUPPORTED:以非事務方式運作,如果目前存在事務,則把目前事務挂起。

5、TransactionDefinition.PROPAGATION_NEVER:以非事務方式運作,如果目前存在事務,則抛出異常。

6、TransactionDefinition.PROPAGATION_MANDATORY:如果目前存在事務,則加入該事務;如果目前沒有事務,則抛出異常。

7、TransactionDefinition.PROPAGATION_NESTED:如果目前存在事務,則建立一個事務作為目前事務的嵌套事務來運作;如果目前沒有事務,則該取值等價于TransactionDefinition.PROPAGATION_REQUIRED。

這裡需要指出的是,前面的六種事務傳播行為是 Spring 從 EJB 中引入的,他們共享相同的概念。而 PROPAGATION_NESTED是 Spring 所特有的。以 PROPAGATION_NESTED 啟動的事務内嵌于外部事務中(如果存在外部事務的話),此時,内嵌事務并不是一個獨立的事務,它依賴于外部事務的存在,隻有通過外部的事務送出,才能引起内部事務的送出,嵌套的子事務不能單獨送出。如果熟悉 JDBC 中的儲存點(SavePoint)的概念,那嵌套事務就很容易了解了,其實嵌套的子事務就是儲存點的一個應用,一個事務中可以包括多個儲存點,每一個嵌套子事務。另外,外部事務的復原也會導緻嵌套子事務的復原。

  (三)事務逾時 所謂事務逾時,就是指一個事務所允許執行的最長時間,如果超過該時間限制但事務還沒有完成,則自動復原事務。在 TransactionDefinition 中以 int 的值來表示逾時時間,其機關是秒。 (四)事務的隻讀屬性 事務的隻讀屬性是指,對事務性資源進行隻讀操作或者是讀寫操作。所謂事務性資源就是指那些被事務管理的資源,比如資料源、 JMS 資源,以及自定義的事務性資源等等。如果确定隻對事務性資源進行隻讀操作,那麼我們可以将事務标志為隻讀的,以提高事務處理的性能。在 TransactionDefinition 中以 boolean 類型來表示該事務是否隻讀。 (五)事務的復原規則 通常情況下,如果在事務中抛出了未檢查異常(繼承自 RuntimeException 的異常),則預設将復原事務。如果沒有抛出任何異常,或者抛出了已檢查異常,則仍然送出事務。這通常也是大多數開發者希望的處理方式,也是 EJB 中的預設處理方式。但是,我們可以根據需要人為控制事務在抛出某些未檢查異常時任然送出事務,或者在抛出某些已檢查異常時復原事務。 二、Spring 事務管理 API 分析 1、Spring 架構中,涉及到事務管理的 API 大約有100個左右,其中最重要的有三個:TransactionDefinition、PlatformTransactionManager、TransactionStatus。 2、所謂事務管理,其實就是“按照給定的事務規則來執行送出或者復原操作”。“給定的事務規則”就是用 TransactionDefinition 表示的,“按照……來執行送出或者復原操作”便是用 PlatformTransactionManager 來表示,而 TransactionStatus 用于表示一個運作着的事務的狀态。打一個不恰當的比喻,TransactionDefinition 與 TransactionStatus 的關系就像程式和程序的關系。

  (一)TransactionDefinition 該接口在前面已經介紹過,它用于定義一個事務。它包含了事務的靜态屬性,比如:事務傳播行為、逾時時間等等。Spring 為我們提供了一個預設的實作類:DefaultTransactionDefinition,該類适用于大多數情況。如果該類不能滿足需求,可以通過實作 TransactionDefinition 接口來實作自己的事務定義。 (二)PlatformTransactionManager 1、PlatformTransactionManager 用于執行具體的事務操作。PlatformTransactionManager 接口中定義的主要方法:

Public interface PlatformTransactionManager{  
	TransactionStatus getTransaction(TransactionDefinition definition)  
	throws TransactionException;  
	void commit(TransactionStatus status)throws TransactionException;  
	void rollback(TransactionStatus status)throws TransactionException;  
} 
           

2、PlatformTransactionManager 具體化,根據底層所使用的不同的持久化 API 或架構,PlatformTransactionManager 的主要實作類大緻如下: 1)DataSourceTransactionManager:适用于使用JDBC和iBatis進行資料持久化操作的情況。 2)HibernateTransactionManager:适用于使用Hibernate進行資料持久化操作的情況。 3)JpaTransactionManager:适用于使用JPA進行資料持久化操作的情況。 4)另外還有JtaTransactionManager 、JdoTransactionManager、JmsTransactionManager等等。

如果我們使用JTA進行事務管理,我們可以通過 JNDI 和 Spring 的 JtaTransactionManager 來擷取一個容器管理的 DataSource。JtaTransactionManager 不需要知道 DataSource 和其他特定的資源,因為它将使用容器提供的全局事務管理。而對于其他事務管理器,比如DataSourceTransactionManager,在定義時需要提供底層的資料源作為其屬性,也就是 DataSource。與 HibernateTransactionManager 對應的是 SessionFactory,與 JpaTransactionManager 對應的是 EntityManagerFactory 等等。 (三)TransactionStatus PlatformTransactionManager.getTransaction(…) 方法傳回一個 TransactionStatus 對象。傳回的TransactionStatus 對象可能代表一個新的或已經存在的事務(如果在目前調用堆棧有一個符合條件的事務)。TransactionStatus 接口提供了一個簡單的控制事務執行和查詢事務狀态的方法。該接口如下所示:

public  interface TransactionStatus{  
   boolean isNewTransaction();  
   void setRollbackOnly();  
   boolean isRollbackOnly();  
} 
           

  三、

Spring 事務管理

(一)程式設計式事務管理 在 Spring 出現以前,程式設計式事務管理對基于 POJO 的應用來說是唯一選擇。用過 Hibernate 的人都知道,我們需要在代碼中顯式調用beginTransaction()、commit()、rollback()等事務管理相關的方法,這就是程式設計式事務管理。通過 Spring 提供的事務管理 API,我們可以在代碼中靈活控制事務的執行。在底層,Spring 仍然将事務操作委托給底層的持久化架構來執行。 (二)聲明式事務管理 1、概述 1)Spring 的聲明式事務管理在底層是建立在 AOP 的基礎之上的。其本質是對方法前後進行攔截,然後在目标方法開始之前建立或者加入一個事務,在執行完目标方法之後根據執行情況送出或者復原事務。

2)聲明式事務最大的優點就是不需要通過程式設計的方式管理事務,這樣就不需要在業務邏輯代碼中摻雜事務管理的代碼,隻需在配置檔案中做相關的事務規則聲明(或通過等價的基于标注的方式),便可以将事務規則應用到業務邏輯中。因為事務管理本身就是一個典型的橫切邏輯,正是 AOP 的用武之地。Spring 開發團隊也意識到了這一點,為聲明式事務提供了簡單而強大的支援。

3)聲明式事務管理曾經是 EJB 引以為傲的一個亮點, Spring 讓 POJO 在事務管理方面也擁有了和 EJB 一樣的待遇,讓開發人員在 EJB 容器之外也用上了強大的聲明式事務管理功能,這主要得益于 Spring 依賴注入容器和 Spring AOP 的支援。依賴注入容器為聲明式事務管理提供了基礎設施,使得 Bean 對于 Spring 架構而言是可管理的;而 Spring AOP 則是聲明式事務管理的直接實作者。

  4)建議在開發中使用聲明式事務,不僅因為其簡單,更主要是因為這樣使得純業務代碼不被污染,極大友善後期的代碼維護。和程式設計式事務相比,聲明式事務唯一不足地方是,後者的最細粒度隻能作用到方法級别,無法做到像程式設計式事務那樣可以作用到代碼塊級别。但是即便有這樣的需求,也存在很多變通的方法,比如,可以将需要進行事務管理的代碼塊獨立為方法等等。 2、基于 <tx> 命名空間的聲明式事務管理

1)Spring 2.x 引入了 <tx> 命名空間,結合使用 <aop> 命名空間,使得配置聲明式事務的配置變得更加簡單和靈活。另外,得益于 <aop> 命名空間的切點表達式支援,聲明式事務也變得更加強大。

<beans......>  
......  
	<bean id="bankService"   class="footmark.spring.core.tx.declare.namespace.BankServiceImpl">  
		<property name="bankDao" ref="bankDao"/>  
	</bean> 
	 
	<aop:config>  
		<aop:pointcut id="bankPointcut" expression="execution(* *.transfer(..))"/>  
		<aop:advisor advice-ref="bankAdvice" pointcut-ref="bankPointcut"/>  
	</aop:config>  

	<tx:advice id="bankAdvice" transaction-manager="transactionManager">  
		<tx:attributes>  
			<tx:method name="transfer" propagation="REQUIRED"/>  
		</tx:attributes>  
	</tx:advice>  
......  
</beans>  
           

2)如果預設的事務屬性就能滿足要求,那麼代碼簡化如下:

<beans......>  
......  
	<bean id="bankService"  class="footmark.spring.core.tx.declare.namespace.BankServiceImpl">  
		<property name="bankDao" ref="bankDao"/>  
	</bean>  
	<tx:advice id="bankAdvice" transaction-manager="transactionManager">  
	<aop:config>  
		<aop:pointcut id="bankPointcut" expression="execution(**.transfer(..))"/>  
		<aop:advisor advice-ref="bankAdvice" pointcut-ref="bankPointcut"/>  
	</aop:config>  
......  
</beans>  
           

  3、基于 @Transactional 的聲明式事務管理

除了基于命名空間的事務配置方式,還有基于 Annotation 的方式,具體主要涉及@Transactional 注解。 1)@Transactional 可以作用于接口、接口方法、類以及類方法上。當作用于類上時,該類的所有 public 方法将都具有該類型的事務屬性,同時,我們也可以在方法級别使用該标注來覆寫類級别的定義。

@Transactional(propagation = Propagation.REQUIRED)  
public boolean transfer(Long fromId, Long toId, double amount) {  
	return bankDao.transfer(fromId, toId, amount);  
}
           

2)Spring 使用 BeanPostProcessor 來處理 Bean 中的标注,是以我們需要在配置檔案中作如下聲明來激活該後處理 。

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

3)雖然 @Transactional 注解可以作用于接口、接口方法、類以及類方法上,但是 不建議在接口或者接口方法上使用該注解,因為這隻有在使用基于接口的代理時它才會生效。另外, @Transactional 注解應該隻被應用到 public 方法上,這是由 Spring AOP 的本質決定的。如果你在 protected、private 或者預設可見性的方法上使用 @Transactional 注解,這将被忽略,也不會抛出任何異常。 4、基于 <tx> 命名空間和基于 @Transactional 的事務聲明方式各有優缺點。 1)基于 <tx> 的方式,其優點是與切點表達式結合,功能強大。利用切點表達式,一個配置可以比對多個方法,而基于 @Transactional 的方式必須在每一個需要使用事務的方法或者類上用 @Transactional 标注,盡管可能大多數事務的規則是一緻的,但是對 @Transactional 而言,也無法重用,必須逐個指定。 2)另一方面,基于 @Transactional 的方式使用起來非常簡單明了,沒有學習成本。開發人員可以根據需要,任選其中一種使用,甚至也可以根據需要混合使用這兩種方式。 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:component-scan base-package="main.java.com.spring.jdbc" />
	<!-- 打開aop 注解 -->
	<aop:aspectj-autoproxy />


	<!-- 配置資料源 -->
	<context:property-placeholder location="classpath:jdbc.properties" />
	<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"
		destroy-method="close">
		<property name="driverClassName" value="${driverClassName}" />
		<property name="url" value="${url}" />
		<property name="username" value="${username}" />
		<property name="password" value="${password}" />
		<!-- 連接配接池啟動時的初始值 -->
		<property name="initialSize" value="${initialSize}" />
		<!-- 連接配接池的最大值 -->
		<property name="maxActive" value="${maxActive}" />
		<!-- 最大空閑值.當經過一個高峰時間後,連接配接池可以慢慢将已經用不到的連接配接慢慢釋放一部分,一直減少到maxIdle為止 -->
		<property name="maxIdle" value="${maxIdle}" />
		<!-- 最小空閑值.當空閑的連接配接數少于閥值時,連接配接池就會預申請去一些連接配接,以免洪峰來時來不及申請 -->
		<property name="minIdle" value="${minIdle}" />
	</bean>

	<!-- 需要把 dataSource 注入到jdbcTemplate-->
	<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
		<property name="dataSource" ref="dataSource" />
	</bean>

	<!-- 配置JDBC的事物管理器 -->
	<bean id="txManager"
		class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
		<property name="dataSource" ref="dataSource" />
	</bean>
	
	<!-- 注解事物 -->
	<!-- <tx:annotation-driven transaction-manager="txManager" /> -->
	
	<!-- XML事物 -->
	<aop:config>
		<aop:pointcut id="transationPointCut" expression="execution(* main.java.com.spring.jdbc.service..*.*(..))" />
		<aop:advisor advice-ref="txAdvice" pointcut-ref="transationPointCut"/>
	</aop:config>
	
	<tx:advice id="txAdvice" transaction-manager="txManager"> 
		<tx:attributes>
			<tx:method name="get*" read-only="true" propagation="NOT_SUPPORTED"/>
			<tx:method name="*"  propagation="REQUIRED" isolation="DEFAULT" />
		</tx:attributes>
	</tx:advice>

</beans>

           

繼續閱讀