天天看點

@Transactional@Transactional

@Transactional

概述

  • @Transactional 是聲明式事務管理 程式設計中使用的注解
  • 添加位置
    • 接口實作類或接口實作方法上,而不是接口類中
  • 通路權限:public 的方法才起作用
    • @Transactional 注解應該隻被應用到 public 方法上,這是由 Spring AOP 的本質決定的。如果你在 protected、private 或者預設可見性的方法上使用 @Transactional 注解
  • 系統設計:将标簽放置在需要進行事務管理的方法上,而不是不假思索的放置在接口實作類上( 接口中所有方法都需要進行事務管理,但其實并不需要,如隻讀的接口就不需要事務管理,但是 由于配置了@Transactional就需要AOP攔截及事務的處理,影響系統性能)
  • 方法上注解屬性會覆寫類注解上的相同屬性,當接口與接口中方法上同時帶有@Transactional注解時
  • 錯誤使用:
    • 接口中A、B兩個方法,A無@Transactional标簽,B有,上層通過A間接調用B,此時事務不生效
    • 接口中異常(運作時異常)被捕獲而沒有被抛出
      • 預設配置下,spring隻有在抛出的異常為運作時unchecked異常時才復原該事務,也就是抛出的異常為RuntimeException的子類(Errors也會導緻事務復原),而抛出checked異常則不會導緻事務復原
      • 可通過 @Transactional rollbackFor進行配置
  • 多線程下事務管理
    • 因為線程不屬于spring托管,故線程不能夠預設使用spring的事務,也不能擷取spring注入的bean
    • 在被spring聲明式事務管理的方法内開啟多線程,多線程内的方法不被事務控制
      • 一個使用了@Transactional 的方法,如果方法内包含多線程的使用,方法内部出現異常,不會復原線程中調用方法的事務

聲明式事務管理實作方式:

  • 基于tx和aop名字空間的xml配置檔案
// 基本配置
<?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:mvc="http://www.springframework.org/schema/mvc" xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:task="http://www.springframework.org/schema/task" xmlns:jms="http://www.springframework.org/schema/jms"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.1.xsd
						  http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.1.xsd
						  http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.1.xsd
						  http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.1.xsd
						  http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task-4.1.xsd
						  http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.1.xsd
						  http://www.springframework.org/schema/jms http://www.springframework.org/schema/jms/spring-jms-4.1.xsd">

<bean name="transactionManager"
		class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
		<property name="dataSource" ref="shardingDataSource"></property>
	</bean>

<tx:annotation-driven transaction-manager="transactionManager" proxy-target-class="true" />

// MyBatis自動參與到spring事務管理中,無需額外配置,隻要org.mybatis.spring.SqlSessionFactoryBean引用的資料源與DataSourceTransactionManager引用的資料源一緻即可,否則事務管理會不起作用
// <annotation-driven>标簽的聲明,則是在Spring内部啟用@Transactional來進行事務管理,使用 @Transactional 前需要配置
           
  • 基于@Transactional注解

    @Transactional實質是使用了JDBC的事務來進行事務控制的

    @Transactional基于Spring的動态代理的機制

  • @Transactional實作原理
  • 1)事務開始時,通過AOP機制,生成一個代理connection對象,并将其放入DataSource執行個體的某個與DataSourceTransactionManager相關的某處容器中。在接下來的整個事務中,客戶代碼都應該使用該connection連接配接資料庫,執行所有資料庫指令[不使用該connection連接配接資料庫執行的資料庫指令,在本事務復原的時候得不到復原](實體連接配接connection邏輯上建立一個會話session;DataSource與TransactionManager配置相同的資料源)
  • 2)事務結束時,復原在第1步驟中得到的代理connection對象上執行的資料庫指令,然後關閉該代理connection對象(事務結束後,復原操作不會對已執行完畢的SQL操作指令起作用)

聲明式事務的管理實作本質:

  • 事務的兩種開啟方式
    • 顯示開啟

      start transaction | begin

      ,通過

      commit | rollback

      結束事務
    • 關閉資料庫中自動送出 autocommit

      set autocommit = 0

      ;MySQL 預設開啟自動送出;通過手動送出或執行復原操作來結束事務
  • Spring 關閉資料庫中自動送出:在方法執行前關閉自動送出,方法執行完畢後再開啟自動送出
// org.springframework.jdbc.datasource.DataSourceTransactionManager.java 源碼實作
 // switch to manual commit if necessary. this is very expensive in some jdbc drivers,
 // so we don't want to do it unnecessarily (for example if we've explicitly
 // configured the connection pool to set it already).
 if (con.getautocommit()) {
     txobject.setmustrestoreautocommit(true);
     if (logger.isdebugenabled()) {
         logger.debug("switching jdbc connection [" + con + "] to manual commit");
     }
     con.setautocommit(false);
 }
 
           
  • 問題:
  • 關閉自動送出後,若事務一直未完成,即未手動執行 commit 或 rollback 時如何處理已經執行過的SQL操作?
    • C3P0預設的政策是復原任何未送出的事務
    • C3P0是一個開源的JDBC連接配接池,它實作了資料源和JNDI綁定,支援JDBC3規範和JDBC2的标準擴充。目前使用它的開源項目有Hibernate,Spring等
    • JNDI(Java Naming and Directory Interface,Java命名和目錄接口)是SUN公司提供的一種标準的Java命名系統接口,JNDI提供統一的用戶端API,通過不同的通路提供者接口JNDI服務供應接口(SPI)的實作,由管理者将JNDI API映射為特定的命名服務和目錄系統,使得Java應用程式可以和這些命名服務和目錄服務之間進行互動

spring事務特性

  • spring所有的事務管理政策類都繼承自

    org.springframework.transaction.PlatformTransactionManager

    接口
public interface PlatformTransactionManager {
  TransactionStatus getTransaction(TransactionDefinition definition)
  throws TransactionException;
  void commit(TransactionStatus status) throws TransactionException;
  void rollback(TransactionStatus status) throws TransactionException;
}
           
  • 事務的隔離級别:是指若幹個并發

    的事務之間的隔離程度

  • @Transactional(isolation = Isolation.READ_UNCOMMITTED):讀取未送出資料(會出現髒讀, 不可重複讀) 基本不使用
  • @Transactional(isolation = Isolation.READ_COMMITTED):讀取已送出資料(會出現不可重複讀和幻讀)
  • @Transactional(isolation = Isolation.REPEATABLE_READ):可重複讀(會出現幻讀)
  • @Transactional(isolation = Isolation.SERIALIZABLE):串行化
  • 事務傳播行為:如果在開始目前事務之前,一個事務上下文已經存在,此時有若幹選項可以指定一個事務性方法的執行行為
    • 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。

@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 不會導緻事務復原的異常類名字數組

說明:

  • value :主要用來指定不同的事務管理器;主要用來滿足在同一個系統中,存在不同的事務管理器。比如在Spring中,聲明了兩種事務管理器txManager1, txManager2.然後,使用者可以根據這個參數來根據需要指定特定的txManager.
  • value 适用場景:在一個系統中,需要通路多個資料源或者多個資料庫,則必然會配置多個事務管理器的
  • REQUIRED_NEW和NESTED兩種不同的傳播機制的差別
  • REQUIRED_NEW:内部的事務獨立運作,在各自的作用域中,可以獨立的復原或者送出;而外部的事務将不受内部事務的復原狀态影響
  • ESTED的事務,基于單一的事務來管理,提供了多個儲存點。這種多個儲存點的機制允許内部事務的變更觸發外部事務的復原。而外部事務在混滾之後,仍能繼續進行事務處理,即使部分操作已經被混滾。 由于這個設定基于JDBC的儲存點,是以隻能工作在JDBC的機制
  • rollbackFor : 讓受檢查異常復原;即讓本來不應該復原的進行復原操作
  • noRollbackFor :忽略非檢查異常;即讓本來應該復原的不進行復原操作

嵌套事務

  • 帶有事務的方法調用其他事務的方法,此時執行的情況取決配置的事務的傳播屬性
    • PROPAGATION_REQUIRES_NEW :
      • 啟動一個新的, 不依賴于環境的 “内部” 事務. 這個事務将被完全 commited 或 rolled back 而不依賴于外部事務, 它擁有自己的隔離範圍, 自己的鎖, 等等. 當内部事務開始執行時, 外部事務将被挂起, 内務事務結束時, 外部事務将繼續執行.
    • PROPAGATION_NESTED :
      • 如果外部事務 commit, 嵌套事務也會被 commit;如果外部事務 roll back, 嵌套事務也會被 roll back 。
      • 開始一個 “嵌套的” 事務, 它是已經存在事務的一個真正的子事務. 嵌套事務開始執行時, 它将取得一個 savepoint. 如果這個嵌套事務失敗, 我們将復原到此 savepoint. 嵌套事務是外部事務的一部分, 隻有外部事務結束後它才會被送出
  • 關于Spring的事務Transactional,鎖同步,并發線程

spring事務復原規則

  • 訓示spring事務管理器復原一個事務的推薦方法是在目前事務的上下文内抛出異常

    spring事務管理器會捕捉任何未處理的異常,然後依據規則決定是否復原抛出異常的事務

  • 預設配置下,spring隻有在抛出的異常為運作時unchecked異常時才復原該事務,也就是抛出的異常為RuntimeException的子類(Errors也會導緻事務復原),而抛出checked異常則不會導緻事務復原。
  • 用 spring 事務管理器,由spring來負責資料庫的打開,送出,復原.預設遇到運作期例外(throw new RuntimeException(“注釋”);)會復原,即遇到不受檢查(unchecked)的例外時復原;而遇到需要捕獲的例外(throw new Exception(“注釋”);)不會復原,即遇到受檢查的例外(就是非運作時抛出的異常,編譯器會檢查到的異常叫受檢查例外或說受檢查異常)時,需我們指定方式來讓事務復原要想所有異常都復原,要加上 @Transactional( rollbackFor={Exception.class,其它異常}) .如果讓unchecked例外不復原: @Transactional(notRollbackFor=RunTimeException.class)

注意事項:

  • @Transactional 使用位置 類上方、方法上方

    Spring 建議不要在接口或者接口方法上使用該注解,因為這隻有在使用基于接口的代理時它才會生效

    當作用于類上時,該類的所有 public 方法将都具有該類型的事務屬性,同時,我們也可以在方法級别使用該标注來覆寫類級别的定義。

  • 方法的通路權限為 public

    @Transactional 注解應該隻被應用到 public 方法上,這是由 Spring AOP 的本質決定的。在 protected、private 或者預設可見性的方法上使用 @Transactional 注解,這将被忽略,也不會抛出任何異常

  • 預設情況下,隻有來自外部的方法調用才會被AOP代理捕獲,也就是,類内部方法調用本類内部的其他方法并不會引起事務行為,即使被調用方法使用@Transactional注解進行修飾

    例如一:同一個類中方法,A方法未使用此标簽,B使用了,C未使用,A 調用 B , B 調用 C ;則外部調用A之後,B的事務是不會起作用的

    例如二:若是有上層(按照 Controller層、Service層、DAO層的順序)由Action 調用 Service 直接調用,發生異常會發生復原;若間接調用,Action 調用 Service 中 的A 方法,A無@Transactional 注解,B有,A調用B,B的注解無效

其他

  • 事務方法的嵌套調用會産生事務傳播
  • spring 的事務管理是線程安全的
  • 父類的聲明的@Transactional會對子類的所有方法進行事務增強;子類覆寫重寫父類方式可覆寫其@Transactional中的聲明配置
  • 類名上方使用@Transactional,類中方法可通過屬性配置覆寫類上的@Transactional配置;比如:類上配置全局是可讀寫,可在某個方法上改為隻讀

源碼閱讀

  • 如果程式代碼中由于曆史原因遺留了接口内部方法調用的錯誤實作,可以用AspectJ 取代 Spring AOP 代理
  • 透徹的掌握 Spring 中@transactional 的使用
  • Spring @Transactional工作原理詳解

參考資料

  • spring的@Transactional注解詳細用法
  • Spring中@Transactional用法深度分析之一
  • @Transactional事務幾點注意
  • spring @Transactional注解參數詳解
  • 深入分析@Transactional的用法
  • Spring @Transactional (一)
  • Spring @Transactional工作原理
  • @Transactional注解工作原理
  • DBMS
  • c3p0
  • JNDI

多線程事務管理

  • 描述
    • 因為線程不屬于spring托管,故線程不能夠預設使用spring的事務,也不能擷取spring注入的bean
    • 在被spring聲明式事務管理的方法内開啟多線程,多線程内的方法不被事務控制
  • 解決
    • 如果方法中調用多線程
      • 方法主題的事務不會傳遞到線程中
      • 線程中可以單獨調用Service接口,接口的實作方法使用@Transactional,保證線程内部的事務
    • 多線程實作的方法
      • 使用異步注解@Async的方法上再加上注解@Transactional,保證新線程調用的方法是有事務管理的
  • 原理
    • Spring中事務資訊存儲在ThreadLocal變量中,變量是某個線程上進行的事務所特有的(這些變量對于其他線程中發生的事務來講是不可見的,無關的)
    • 單線程的情況下,一個事務會在層級式調用的Spring元件之間傳播
    • 在@Transactional注解的服務方法會産生一個新的線程的情況下,事務是不會從調用者線程傳播到建立線程的
  • 參考資料
    • Spring和線程:事務
    • spring 多線程事務的問題