JPA和事务管理
1 事務
1.1事務管理方式
spring支持編程式事務管理和聲明式事務管理兩種方式。
編程式事務管理使用TransactionTemplate或者直接使用底層的PlatformTransactionManager。對于編程式事務管理,spring推薦使用TransactionTemplate。
聲明式事務管理建立在AOP之上的。其本質是對方法前后進行攔截,然后在目標方法開始之前創建或者加入一個事務,在執行完目標方法之后根據執行情況提交或者回滾事務。聲明式事務最大的優點就是不需要通過編程的方式管理事務,這樣就不需要在業務邏輯代碼中摻雜事務管理的代碼,只需在配置文件中做相關的事務規則聲明(或通過基于@Transactional注解的方式),便可以將事務規則應用到業務邏輯中。
顯然聲明式事務管理要優于編程式事務管理,這正是spring倡導的非侵入式的開發方式。聲明式事務管理使業務代碼不受污染,一個普通的POJO對象,只要加上注解就可以獲得完全的事務支持。和編程式事務相比,聲明式事務唯一不足地方是,后者的最細粒度只能作用到方法級別,無法做到像編程式事務那樣可以作用到代碼塊級別。但是即便有這樣的需求,也存在很多變通的方法,比如,可以將需要進行事務管理的代碼塊獨立為方法等等。
聲明式事務管理也有兩種常用的方式,一種是基于tx和aop名字空間的xml配置文件,另一種就是基于@Transactional注解。顯然基于注解的方式更簡單易用,更清爽。
1.2自動提交(AutoCommit)與連接關閉時的是否自動提交
默認情況下,數據庫處于自動提交模式。每一條語句處于一個單獨的事務中,在這條語句執行完畢時,如果執行成功則隱式的提交事務,如果執行失敗則隱式的回滾事務。對于正常的事務管理,是一組相關的操作處于一個事務之中,因此必須關閉數據庫的自動提交模式。不過,spring會將底層連接的自動提交特性設置為false。
當一個連接關閉時,如果有未提交的事務應該如何處理?JDBC規范沒有提及,C3P0默認的策略是回滾任何未提交的事務。這是一個正確的策略,但JDBC驅動提供商之間對此問題并沒有達成一致。
1.3事務隔離級別
隔離級別是指若干個并發的事務之間的隔離程度。TransactionDefinition 接口中定義了五個表示隔離級別的常量:
1、TransactionDefinition.ISOLATION_DEFAULT:這是默認值,表示使用底層數據庫的默認隔離級別。對大部分數據庫而言,通常這值就是TransactionDefinition.ISOLATION_READ_COMMITTED
2、TransactionDefinition.ISOLATION_READ_UNCOMMITTED:該隔離級別表示一個事務可以讀取另一個事務修改但還沒有提交的數據。該級別不能防止臟讀,不可重復讀和幻讀,因此很少使用該隔離級別。比如PostgreSQL實際上并沒有此級別。
3、TransactionDefinition.ISOLATION_READ_COMMITTED:該隔離級別表示一個事務只能讀取另一個事務已經提交的數據。該級別可以防止臟讀,這也是大多數情況下的推薦值。
4、TransactionDefinition.ISOLATION_REPEATABLE_READ:該隔離級別表示一個事務在整個過程中可以多次重復執行某個查詢,并且每次返回的記錄都相同。該級別可以防止臟讀和不可重復讀。
5、TransactionDefinition.ISOLATION_SERIALIZABLE:所有的事務依次逐個執行,這樣事務之間就完全不可能產生干擾,也就是說,該級別可以防止臟讀、不可重復讀以及幻讀。但是這將嚴重影響程序的性能。通常情況下也不會用到該級別。
注:
MYSQL: 默認為REPEATABLE_READ級別
SQLSERVER: 默認為READ_COMMITTED
臟讀 :一個事務讀取到另一事務未提交的更新數據
不可重復讀 :在同一事務中, 多次讀取同一數據返回的結果有所不同, 換句話說,?
后續讀取可以讀到另一事務已提交的更新數據. 相反, "可重復讀"在同一事務中多次
讀取數據時, 能夠保證所讀數據一樣, 也就是后續讀取不能讀到另一事務已提交的更新數據
幻讀 :一個事務讀到另一個事務已提交的insert數據。
1.4事務傳播行為
所謂事務的傳播行為是指,如果在開始當前事務之前,一個事務上下文已經存在,此時有若干選項可以指定一個事務性方法的執行行為。在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
例如:@Transactional(propagation=Propagation.REQUIRED)?
1.5事務超時
所謂事務超時,就是指一個事務所允許執行的最長時間,如果超過該時間限制但事務還沒有完成,則自動回滾事務。在 TransactionDefinition 中以 int 的值來表示超時時間,其單位是秒。
默認設置為底層事務系統的超時值,如果底層數據庫事務系統沒有設置超時值,那么就是none,沒有超時限制。@Transactional(timeout=30) //默認是30秒
1.6事務只讀屬性
只讀事務用于客戶代碼只讀但不修改數據的情形,只讀事務用于特定情景下的優化,比如使用Hibernate的時候。默認為讀寫事務。
@Transactional(propagation=Propagation.NOT_SUPPORTED,readOnly=true)
只讀標志只在事務啟動時應用,否則即使配置也會被忽略。啟動事務會增加線程開銷,數據庫因共享讀取而鎖定(具體跟數據庫類型和事務隔離級別有關)。通常情況下,僅是讀取數據時,不必設置只讀事務而增加額外的系統開銷。
1.7 spring事務回滾規則
指示spring事務管理器回滾一個事務的推薦方法是在當前事務的上下文內拋出異常。spring事務管理器會捕捉任何未處理的異常,然后依據規則決定是否回滾拋出異常的事務。
默認配置下,spring只有在拋出的異常為運行時unchecked異常時才回滾該事務,也就是拋出的異常為RuntimeException的子類(Errors也會導致事務回滾),而拋出checked異常(編譯器會檢查到的異常叫受檢查異常)則不會導致事務回滾。
可以明確的配置在拋出那些異常時回滾事務,包括checked異常。也可以明確定義那些異常拋出時不回滾事務。如: @Transactional(notRollbackFor=RunTimeException.class)、@Transactional(rollbackFor=Exception.class)
還可以編程性的通過setRollbackOnly()方法來指示一個事務必須回滾,在調用setRollbackOnly()后你所能執行的唯一操作就是回滾。
1.8 @Transactional注解
@Transactional 可以作用于接口、接口方法、類以及類方法上。當作用于類上時,該類的所有 public 方法將都具有該類型的事務屬性,同時,我們也可以在方法級別使用該標注來覆蓋類級別的定義。
雖然 @Transactional 注解可以作用于接口、接口方法、類以及類方法上,但是 Spring 建議不要在接口或者接口方法上使用該注解,因為這只有在使用基于接口的代理時它才會生效,因為注解是不能繼承的,這就意味著如果你正在使用基于類的代理時,那么事務的設置將不能被基于類的代理所識別,而且對象也將不會被事務代理所包裝(將被確認為嚴重的)。另外, @Transactional 注解應該只被應用到 public 方法上,這是由 Spring AOP 的本質決定的。如果你在 protected、private 或者默認可見性的方法上使用 @Transactional 注解,這將被忽略,也不會拋出任何異常。
默認情況下,只有來自外部的方法調用才會被AOP代理捕獲,也就是,類內部方法調用本類內部的其他方法并不會引起事務行為,即使被調用方法使用@Transactional注解進行修飾。
2 ?關于UnexpectedRollbackException異常。
因為在被spring事務代理方法中, 被代理的方法(dosomething())內部有異常拋出,事務會標記為回滾,并在最外層調用那(service里的callB中)回滾。 這種情況下transaction會正常的rollback。
?
這個時候,我們再調用ServiceA的callB。程序會拋出org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only這樣一個異常信息.因為在ServiceA和ServiceB中的@Transactional propagation都采用的默認值:REQUREID。根據我們前面講過的REQUIRED特性,當ServiceA調用ServiceB的時候,他們是處于同一個transaction中。當ServiceB中拋出了一個異常以后,ServiceB會把當前的transaction標記為需要rollback(設置rollBackOnly狀態),繼續執行到serviceA, 可是被serviceA中的方法捕獲了并進行了處理,也就不回滾了,一直執行到最后commit。在commit時spring會判斷回滾標志(rollBackOnly狀態),若有回滾標記,回滾并拋出UnexpectedRollbackException異常(因為出現了前后不一致)。
此時,程序可以正常的退出了,也沒有拋出UnexpectedRollbackException。原因是因為當ServiceA調用ServiceB時,serviceB的doSomething是在一個新的transaction中執行的。當doSomething拋出異常以后,僅僅是把新創建的transaction rollback了,而不會影響到ServiceA的transaction。ServiceA就可以正常的進行commit。
3 ?訪問多個數據源或者多個數據庫
利用@Transactional 的value屬性定不同的事務管理器。主要用來滿足:在同一個系統中存在不同的事務管理器時,可以用此參數來根據需要指定特定的transactionManager(配置文件中qualifier元素定義的名稱)。比如:在同一個系統中,需要訪問多個數據源或者多個數據庫時,則必然會配置多個事務管理器。
類或方法上添加注解:@Transactional(value= "system"),或簡寫為@Transactional("system"),即顯式的要求spring用id="system"的事務管理器來管理事務。該屬性亦可省略(省略的話用容器中缺省的事務管理器),
注:spring容器缺省事務管理器:以加載順序,首先加載的作為缺省。例如如果
<tx:annotation-driven transaction-manager="transactionManagerX" /><tx:annotation-driven transaction-manager="transactionManagerY" />定義在同一個文件中,則第一個transactionManagerX作為缺省。定義在不同文件,則按文件的加載順序,首先加載的作為缺省。
4 ?transaction和EntityManager
(1)、關于persistence context(持久化上下文)和database transaction(事務)
@Transactional本身定義了單個事務的范圍。這個事務在persistence context的范圍內。JPA中的持久化上下文是EntityManager,內部實現使用了Hibernate Session(使用Hibernate作為持久化provider)。持久化上下文僅僅是一個同步對象,它記錄了有限集合的Java對象的狀態,并且保證這些對象的變化最終持久化到數據庫。這是與單個事務非常不同的概念。一個Entity Manager可以跨越多個事務使用,而且的確是這樣使用的。EntityManager何時跨越多個事務?最常見的情況是應用使用Open Session In View模式處理懶初始化異常時。這種情況下視圖層運行的多個查詢處于獨立的事務中,而不是單事務的業務邏輯,但這些查詢由相同的entity manager管理。另一種情況是開發人員將持久化上下文標記為PersistenceContextType.EXTENDED,這表示它能夠響應多個請求。
如何定義EntityManager和Transaction之間的關系?這由應用開發者來選擇,但是JPA Entity Manager最常用的方式是“Entity Manager per application transaction”(每個事務都有自己的實體管理器)模式。entity manager注入的常用方法是:
@PersistenceContext
private EntityManager em;
這里默認為“Entity Manager per transaction”模式。這種模式下如果在@Transactional方法內部使用該Entity Manager,那么該方法將在單一事務中運行。EntityManager是一個接口,注入到spring bean中的不是entity manager本身,而是在運行時代理具體entity manager的context aware proxy(上下文感知代理)。
(2)、事務的實現
實現了EntityManager接口的持久化上下文代理并不是聲明式事務管理的唯一部分,事實上包含三個組成部分:
EntityManager Proxy本身、事務的切面、事務管理器
這三部分以及它們之間的相互作用如下:。
1、事務的切面
事務的切面是一個“around(環繞)”切面,在注解的業務方法前后都可以被調用。實現切面的具體類是TransactionInterceptor。
事務的切面有兩個主要職責:
在’before’時,切面提供一個調用點,來決定被調用業務方法應該在正在進行事務的范圍內運行,還是開始一個新的獨立事務。
在’after’時,切面需要確定事務被提交,回滾或者繼續運行。
在’before’時,事務切面自身不包含任何決策邏輯,是否開始新事務的決策委派給事務管理器完成。
2、事務管理器
事務管理器需要解決下面兩個問題:
新的Entity Manager是否應該被創建?是否應該開始新的事務?這些需要事務切面’before’邏輯被調用時決定。事務管理器的決策基于以下兩點:
事務是否正在進行
事務方法的propagation屬性(比如REQUIRES_NEW總要開始新事務)
如果事務管理器確定要創建新事務,那么將創建一個新的entity manager,entity manager綁定到當前線程,從數據庫連接池中獲取連接,將連接綁定到當前線程,使用ThreadLocal變量將entity manager和數據庫連接都綁定到當前線程。事務運行時他們存儲在線程中,當它們不再被使用時,事務管理器決定是否將他們清除。程序的任何部分如果需要當前的entity manager和數據庫連接都可以從線程中獲取。
3、EntityManager proxy
當業務方法調用entityManager.persist()時,這不是由entity manager直接調用的。而是業務方法調用代理,代理從線程獲取當前的entity manager,事務管理器將entity manager綁定到線程。
?
總結
- 上一篇: HTTP 相应头相关
- 下一篇: 简评黑客利器——中国菜刀