事務,是爲了保障邏輯處理的原子性、一致性、隔離性、永久性。html
經過事務控制,能夠避免由於邏輯處理失敗而致使產生髒數據等等一系列的問題。java
事務有兩個重要特性:spring
傳播行爲級別,定義的是事務的控制範圍。通俗點說,執行到某段代碼時,對已存在事務的不一樣處理方式。數據庫
Spring 對 JDBC 的事務隔離級別進行了補充和擴展,並提出了 7 種事務傳播行爲。express
PROPAGATION_REQUIRED,須要事務處理。有則使用,無則新建。這是 Spring 默認的事務傳播行爲。該級別的特性是,若是 Context 中已經存在事務,那麼就將當前須要使用事務的代碼加入到 Context 的事務中執行,若是當前 Context 中不存在事務,則新建一個事務執行代碼。這個級別一般能知足大多數的業務場景。編程
PROPAGATION_SUPPORTS,支持事務處理。該級別的特性是,若是 Context 存在事務,則將代碼加入到 Context 的事務中執行,若是 Context 中沒有事務,則使用 非事務 的方式執行。session
PROPAGATION_MANDATORY,強制性要求事務。該級別的特性是,當要以事務的方式執行代碼時,要求 Context 中必須已經存在事務,不然就會拋出異常!使用 MANDATORY 強制事務,能夠有效地控制 「必須以事務執行的代碼,卻忘記給它加上事務控制」 這種狀況的發生。舉個簡單的例子:有一個方法,對這個方法的要求是一旦被調用,該方法就必須包含在事務中才能正常執行,那麼這個方法就適合設置爲 PROPAGATION_MANDATORY 強制事務傳播行爲,從而在代碼層面加以控制。併發
PROPAGATION_REQUIRES_NEW,每次都新建一個事務。該級別的特色是,當執行到一段須要事務的代碼時,先判斷 Context 中是否已經有事務存在,若是不存在,就新建一個事務;若是已經存在,就 suspend 掛起當前事務,而後建立一個新事務去執行,直到新事務執行完畢,纔會恢復先前掛起的 Context 事務。分佈式
PROPAGATION_NOT_SUPPORTED,不支持事務。該級別的特色是,若是發現當前 Context 中有事務存在,則掛起該事務,而後執行邏輯代碼,執行完畢後,恢復先前掛起的 Context 事務。這個傳播行爲的事務,能夠縮小事務處理過程的範圍。舉個簡單例子,在一個事務中,須要調用一段非核心業務的邏輯操做 1000 次,若是將這段邏輯放在事務中,會致使該事務的範圍變大、生命週期變長,爲了不因事務範圍擴大、週期變長而引起一些的事先沒有考慮到的異常狀況發生,能夠將這段邏輯設置爲 NOT_SUPPORTED 不支持事務傳播行爲。高併發
PROPAGATION_NEVER,對事務要求更嚴格,不能出現事務!該級別的特色是,設置了該級別的代碼,在執行前一旦發現 Context 中有事務存在,就會拋出 Runtime 異常,強制中止執行,有我無他!
PROPAGATION_NESTED,嵌套事務。該級別的特色是,若是 Context 中存在事務 A,就將當前代碼對應的事務 B 加入到 事務 A 內部,嵌套執行;若是 Context 中不存在事務,則新建事務執行代碼。換句話說,事務 A 與事務 B 之間是父子關係,A 是父,B 是子。理解嵌套事務的關鍵點是:save point。
父、子事務嵌套、save point 的說明:
我在網上看到有一篇文章,採用代碼的方式來解釋事務傳播行爲級別,代碼方式很清晰,一看就明白了。
首先準備以下兩個 Service:
class ServiceA { void methodA() { ServiceB.methodB(); } } class ServiceB { void methodB() { } }
ServiceB.methodB()
的傳播行爲定義爲 PROPAGATION_REQUIRED
, 那麼在執行 ServiceA.methodA()
的時候,若 ServiceA.methodA()
已經開啓了事務,這時調用 ServiceB.methodB()
,ServiceB.methodB()
將會運行在 ServiceA.methodA()
的事務內部,而再也不開啓新的事務。而假如 ServiceA.methodA()
運行的時候發現本身沒有在事務中,就會爲它分配一個新事務。這樣,在 ServiceA.methodA()
或者在 ServiceB.methodB()
內的任何地方出現異常,事務都會被回滾。即便 ServiceB.methodB()
的事務已經被ServiceA.methodA()
在接下來的過程當中 fail 要回滾,ServiceB.methodB()
也會跟着一塊兒回滾。ServiceA.methodA()
的傳播行爲設置爲 PROPAGATION_REQUIRED
,ServiceB.methodB()
的傳播行爲爲 PROPAGATION_REQUIRES_NEW
,那麼當執行到 ServiceB.methodB()
的時候,ServiceA.methodA()
所在的事務就會掛起,而 ServiceB.methodB()
會起一個新的事務,等待 ServiceB.methodB()
的事務完成之後,A的事務纔會繼續執行。PROPAGATION_REQUIRED
與 PROPAGATION_REQUIRES_NEW
的事務區別在於事務的回滾程度。由於 ServiceB.methodB
是新起一個事務,那麼就是存在兩個不一樣的事務。若是 ServiceB.methodB
已經提交,那麼 ServiceA.methodA
失敗回滾,ServiceB.methodB
是不會回滾的。若是 ServiceB.methodB
失敗回滾,若是它拋出的異常被 ServiceA.methodA
捕獲,ServiceA.methodA
事務仍然可能會提交。ServiceA.methodA
的事務傳播行爲是 PROPAGATION_REQUIRED
,而 ServiceB.methodB
的事務傳播行爲是 PROPAGATION_NOT_SUPPORTED
,那麼當執行到 ServiceB.methodB
時,ServiceA.methodA
的事務掛起,而ServiceB.methodB
以非事務的狀態運行完以後,再繼續 ServiceA.methodA
的事務。ServiceA.methodA
的事務傳播行爲是 PROPAGATION_REQUIRED
, 而 ServiceB.methodB
的事務級別是 PROPAGATION_NEVER
,那麼 ServiceB.methodB
執行時就會拋出異常。在讀取數據庫的過程當中,若是兩個事務併發執行,那麼多個事務彼此之間,會對數據產生什麼樣的影響呢?
這裏就引出了事務的第二個特性:數據隔離級別。
數據隔離級別,定義的是事務在數據庫端讀寫方面的控制範圍。
數據隔離級別分爲 4 種:
第 1 種數據準確性最高,但相應地性能最差。第 4 種性能高,可是相應地讀取數據的準確性低。
髒讀、幻讀、不可重複讀都是併發事務的狀況下,由於不一樣的數據隔離級別而讀取到不一樣的內容。
髒讀,即一個事務讀到了另外一個事務還未提交的數據。若是髒讀讀取到的數據最終仍是提交了倒還好,但若是這條數據最終回滾了,那麼這條數據對於剛剛讀取到它的事務而言,就是一條髒數據。
不可重複讀,不一樣的事務讀取同一條數據,讀取到的內容是不一樣的。也就是說,對某一條數據而言,不一樣的事務以一樣的重複操做讀取,卻產生了不一樣的結果。
幻讀,一個事務按照某種查詢條件,第一次讀取的數據量和第二次讀取的數據量不同,就像幻覺同樣,明明剛纔查的是 N 條數據,再查一次就變成了 M 條(M <> N)。
假設一個邏輯操做須要檢查的條件有 20 個,可否爲了減少事務而將檢查性的內容放到事務以外呢?
不少系統都是在 DAO 的內部開始啓動事務,而後進行操做,最後提交或者回滾。這其中涉及到代碼設計的問題。
小一些的系統能夠採用這種方式來作,可是在一些比較大的系統,邏輯較爲複雜的系統中,勢必會將過多的業務邏輯嵌入到 DAO 中,致使 DAO 的複用性降低。因此這不是一個好的實踐。
來回答這個問題,可否爲了縮小事務,而將一些業務邏輯檢查放到事務外面?
答案是:對於核心的業務檢查邏輯,不能放到事務以外,並且必需要做爲分佈式下的併發控制!一旦在事務以外作檢查,那麼勢必會形成事務A已經檢查過的數據被事務B所修改,致使事務A徒勞無功並且出現併發問題,直接致使業務控制失敗。
因此,在分佈式的高併發環境下,對於核心業務邏輯的檢查,要採用加鎖機制。
好比事務開啓須要讀取一條數據進行驗證,而後邏輯操做中須要對這條數據進行修改,最後提交。
這樣的一個過程,若是讀取並驗證的代碼放到事務以外,那麼讀取的數據極有可能已經被其餘的事務修改,當前事務一旦提交,又會從新覆蓋掉其餘事務的數據,致使數據異常。
因此在進入當前事務的時候,必需要將這條數據鎖住,例如使用 Oracle 的 for update
就是一個在分佈式環境下頗有效的控制手段。
另外一種好的實踐方式是使用編程式事務而非聲明式事務,尤爲是在較大規模的項目中。對於大量事務的聲明配置,在代碼量很是大的狀況下,將是一種折磨。
將 DAO 保持針對一張表的最基本操做,而後業務邏輯的處理放入 manager 或 service 中進行,同時使用編程式事務能夠更精確地控制事務範圍。
特別注意的,對於事務內部一些可能拋出異常的狀況,捕獲異常時要謹慎,不能隨便的 catch Exception,否則會致使事務的異常被吃掉而不能正常回滾。
Spring配置聲明式事務:
編寫業務邏輯方法:
(1)使用 xml 配置方式:
<!-- 配置SessionFactory --> <bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean"> <property name="configLocation"> <value>classpath:hibernate.cfg.xml</value> </property> </bean> <!-- 配置 Hibernate 事務管理器 --> <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="add*" propagation="REQUIRED"/> <tx:method name="del*" propagation="REQUIRED"/> <tx:method name="modify*" propagation="REQUIRED"/> <tx:method name="*" propagation="REQUIRED" read-only="true"/> </tx:attributes> </tx:advice> <!-- 聲明哪些類哪些方法須要使用事務 --> <aop:config> <aop:pointcut id="transactionPC" expression="execution(* com.service.*.*(..))"/> <aop:advisor advice-ref="txAdvice" pointcut-ref="transactionPC"/> </aop:config> <!-- 普通 IOC 注入 --> <bean id="userManager" class="com.service.UserManagerImpl"> <property name="logManager" ref="logManager"/> <property name="sessionFactory" ref="sessionFactory"/> </bean> <bean id="logManager" class="com.service.LogManagerImpl"> <property name="sessionFactory" ref="sessionFactory"/> </bean>
關於 spring 配置中 read-only
的說明:
read-only
配置爲 true,會告訴 spring 對應的事務應該被最優化爲只讀事務。
這是一個最優化提示。在一些狀況下,一些事務策略可以起到顯著的最優化效果,例如在使用 Object/Relational
映射工具(如:Hibernate 或 TopLink)時避免 dirty checking
(試圖「刷新」)。
(2)使用 JPA 註解方式
@Service public class UserServiceImpl implements IUserService { @Resource IUserDAO userDAO; //啓動 REQUIRED 默認事務傳播行爲的方法 @Transactional public void funNone() throws Exception { save(new UserEntity("aaa")); } //啓動 REQUIRED 默認事務傳播行爲的方法 @Transactional(propagation = Propagation.REQUIRED) public void funRequire() throws Exception { save(new UserEntity("bbb")); } //啓動 Nested 嵌套事務的方法 @Transactional(propagation = Propagation.NESTED) public void funNest() throws Exception { save(new UserEntity("ccc")); } //REQUIRES_NEW 事務的方法 @Transactional(propagation = Propagation.REQUIRES_NEW) public void funRequireNew() throws Exception { save(new UserEntity("ddd")); } }
參考文章:
(Spring事務傳播性與隔離級別)[http://blog.csdn.net/edward0830ly/article/details/7569954]
http://blog.sina.com.cn/s/blog_4b5bc0110100z7jr.html