spring的@Transactional注解詳細用法


概述

事務管理對於企業應用來說是至關重要的,即使出現異常情況,它也可以保證數據的一致性。
Spring Framework對事務管理提供了一致的抽象,其特點如下:

  • 為不同的事務API提供一致的編程模型,比如JTA(Java Transaction API), JDBC, Hibernate, JPA(Java Persistence API和JDO(Java Data Objects)
  • 支持聲明式事務管理,特別是基於注解的聲明式事務管理,簡單易用
  • 提供比其他事務API如JTA更簡單的編程式事務管理API
  • 與spring數據訪問抽象的完美集成

事務管理方式

spring支持編程式事務管理和聲明式事務管理兩種方式。

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

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

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

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

自動提交(AutoCommit)與連接關閉時的是否自動提交

自動提交

默認情況下,數據庫處於自動提交模式。每一條語句處於一個單獨的事務中,在這條語句執行完畢時,如果執行成功則隱式的提交事務,如果
執行失敗則隱式的回滾事務。

有些數據連接池提供了關閉事務自動提交的設置,最好在設置連接池時就將其關閉。但C3P0沒有提供這一特性,只能依靠spring來設置。
因為JDBC規范規定,當連接對象建立時應該處於自動提交模式,這是跨DBMS的缺省值,如果需要,必須顯式的關閉自動提交。C3P0遵守這一規范,讓客戶代碼來顯式的設置需要的提交模式。

連接關閉時的是否自動提交

當一個連接關閉時,如果有未提交的事務應該如何處理?JDBC規范沒有提及,C3P0默認的策略是回滾任何未提交的事務。這是一個正確的策略,但JDBC驅動提供商之間對此問題並沒有達成一致。
C3P0的autoCommitOnClose屬性默認是false,沒有十分必要不要動它。或者可以顯式的設置此屬性為false,這樣會更明確。

基於注解的聲明式事務管理配置
spring-servlet.xml

復制代碼
1 <!-- transaction support-->
2 <!-- PlatformTransactionMnager -->
3 <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
4     <property name="dataSource" ref="dataSource" />
5 </bean>
6 <!-- enable transaction annotation support -->
7 <tx:annotation-driven transaction-manager="txManager" />
復制代碼

還要在spring-servlet.xml中添加tx名字空間

復制代碼
 1 ...
 2     xmlns:tx="http://www.springframework.org/schema/tx"
 3     xmlns:aop="http://www.springframework.org/schema/aop"
 4     xsi:schemaLocation="
 5     ...
 6  
 7 http://www.springframework.org/schema/tx
 8  
 9  
10 http://www.springframework.org/schema/tx/spring-tx.xsd
11  
12     ...
復制代碼

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

另外需要下載依賴包aopalliance.jar放置到WEB-INF/lib目錄下。否則spring初始化時會報異常
java.lang.NoClassDefFoundError: org/aopalliance/intercept/MethodInterceptor

spring事務特性

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的時候。
默認為讀寫事務。

spring事務回滾規則

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

默認配置下,spring只有在拋出的異常為運行時unchecked異常時才回滾該事務,也就是拋出的異常為RuntimeException的子類(Errors也會導致事務回滾),而拋出checked異常則不會導致事務回滾。
可以明確的配置在拋出那些異常時回滾事務,包括checked異常。也可以明確定義那些異常拋出時不回滾事務。

還可以編程性的通過setRollbackOnly()方法來指示一個事務必須回滾,在調用完setRollbackOnly()后你所能執行的唯一操作就是回滾。

@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 不會導致事務回滾的異常類名字數組

 

用法

1. 在需要事務管理的地方加@Transactional 注解。@Transactional 注解可以被應用於接口定義和接口方法、類定義和類的 public 方法上。

2. @Transactional 注解只能應用到 public 可見度的方法上。 如果你在 protected、private 或者 package-visible 的方法上使用 @Transactional 注解,它也不會報錯, 但是這個被注解的方法將不會展示已配置的事務設置。

3. 注意僅僅 @Transactional 注解的出現不足於開啟事務行為,它僅僅 是一種元數據。必須在配置文件中使用配置元素,才真正開啟了事務行為。

4. 通過 元素的 "proxy-target-class" 屬性值來控制是基於接口的還是基於類的代理被創建。如果 "proxy-target-class" 屬值被設置為 "true",那么基於類的代理將起作用(這時需要CGLIB庫cglib.jar在CLASSPATH中)。如果 "proxy-target-class" 屬值被設置為 "false" 或者這個屬性被省略,那么標准的JDK基於接口的代理將起作用。


標准的JDK基於接口的代理將起作用-->
proxy-target-class="false"/>

基於類的代理將起作用 ,同時 cglib.jar必須在CLASSPATH中
proxy-target-class="true"/>
-->


非JTA事務(即非分布式事務), 事務配置的時候 ,需要指定dataSource屬性(非分布式事務,事務是在數據庫創建的鏈接上開啟。)-->


JTA事務(非分布式事務), 事務配置的時候 ,不能指定dataSource屬性(分布式事務,是有全局事務來管理數據庫鏈接的)-->

注解@Transactional cglib與java動態代理最大區別是代理目標對象不用實現接口,那么注解要是寫到接口方法上,要是使用cglib代理,這是注解事物就失效了,為了保持兼容注解最好都寫到實現類方法上。

5. Spring團隊建議在具體的類(或類的方法)上使用 @Transactional 注解,而不要使用在類所要實現的任何接口上。在接口上使用 @Transactional 注解,只能當你設置了基於接口的代理時它才生效。因為注解是 不能繼承 的,這就意味着如果正在使用基於類的代理時,那么事務的設置將不能被基於類的代理所識別,而且對象也將不會被事務代理所包裝。

6. @Transactional 的事務開啟 ,或者是基於接口的 或者是基於類的代理被創建。所以在同一個類中一個方法調用另一個方法有事務的方法,事務是不會起作用的。

public interface PersonService {
//刪除指定id的person
public void delete(Integer personid) ;

//刪除指定id的person,flag
public void delete(Integer personid,boolean flag) ;
}

public class PersonServiceBean implements PersonService {
private JdbcTemplate jdbcTemplate;

public void delete(Integer personid){
try{
this.delete(personid,true)
System.out.println("delete success");
}catch(Exception e){
System.out.println("delete failed");
}
}

@Transactional
//此時,事務根本就沒有開啟, 即數據庫會默認提交該操作,即記錄別刪除掉 public void delete(Integer personid,boolean flag){
if(flag == ture){
jdbcTemplate.update("delete from person where id=?", new Object[]{personid},
new int[]{java.sql.Types.INTEGER});
throw new RuntimeException("運行期例外");
}
}
}

public class PersonServiceBeanTest{
PersonService ps = new PersonServiceBean ();
ps.delete(5);
}

7. Spring使用聲明式事務處理,默認情況下,如果被注解的數據庫操作方法中發生了unchecked異常,所有的數據庫操作將rollback;如果發生的異常是checked異常,默認情況下數據庫操作還是會提交的。

-----------------------------------------------------------------------------------------------------------------------------------------------
public interface PersonService {
//刪除指定id的person
public void delete(Integer personid) ;

//獲取person
public Person getPerson(Integer personid);
}

//PersonServiceBean 實現了PersonService 接口,則基於接口的還是基於類的代理 都可以實現事務
@Transactional public class PersonServiceBean implements PersonService {
private JdbcTemplate jdbcTemplate;

//發生了unchecked異常,事務回滾, @Transactional
public void delete(Integer personid){
jdbcTemplate.update("delete from person where id=?", new Object[]{personid},
new int[]{java.sql.Types.INTEGER});
throw new RuntimeException("運行期例外");
}
}

---------------------------------------------------------------------------------------------------------------------------------------------------
public interface PersonService {
//刪除指定id的person
public void delete(Integer personid) throws Exception;

//獲取person
public Person getPerson(Integer personid);
}

@Transactional
public class PersonServiceBean implements PersonService {

//發生了checked異常,事務不回滾,即數據庫記錄仍能被刪除,
//checked的例外,需要我們在外部用try/catch語法對調用該方法的地方進行包含 @Transactional
public void delete(Integer personid) throws Exception{
jdbcTemplate.update("delete from person where id=?", new Object[]{personid},
new int[]{java.sql.Types.INTEGER});
throw new Exception("運行期例外");
}

}
---------------------------------------------------------------------------------------------------------------------------------------------------
但是,對於checked這種例外,默認情況下它是不會進行事務回滾的,但是如果我們需要它進行事務回滾,這時候可以在delete方法上通過@Transaction這個注解來修改它的行為。

@Transactional
public class PersonServiceBean implements PersonService {

@Transactional(rollbackFor=Exception.class)
//rollbackFor這屬性指定了,既使你出現了checked這種例外,那么它也會對事務進行回滾
public void delete(Integer personid) throws Exception{
jdbcTemplate.update("delete from person where id=?", new Object[]{personid},
new int[]{java.sql.Types.INTEGER});
throw new Exception("運行期例外");
}
}
---------------------------------------------------------------------------------------------------------------------------------------------------

在PersonServiceBean這個業務bean里面,有一些事務是不需要事務管理的,好比說獲取數據的getPersons方法,getPerson方法。因為@Transactional 放在了類的上面。


此時,可以采用propagation這個事務屬性@Transactional(propagation=Propagation.NOT_SUPPORTED),propagation這個屬性指定了事務傳播行為,我們可以指定它不支持事務,當我們這么寫了之后,Spring容器在getPersons方法執行前就不會開啟事務.

@Transactional
public class PersonServiceBean implements PersonService {

@Transactional(propagation=Propagation.NOT_SUPPORTED)
//則此方法 就不會開啟事務了
public Person getPerson(Integer personid)
{
}
}

 


免責聲明!

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



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