Spring原始碼學習--Spring事物
Spring配置檔案中關於事務配置總是由三個組成部分,分別是DataSource、TransactionManager和代理機制這三部分,無論哪種配置方式,一般變化的只是代理機制這部分。DataSource、TransactionManager這兩部分只是會根據資料訪問方式有所變化,比如使用hibernate進行資料訪問時,DataSource實際為SessionFactory,TransactionManager的實現為HibernateTransactionManager。
具體如下圖:
1、根據代理機制的不同,總結了五種Spring事務的配置方式
配置檔案如下:
第一種方式:每個Bean都有一個代理
-
<?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"
-
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">
-
<bean id="sessionFactory"
-
class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
-
<property name="configLocation" value="classpath:hibernate.cfg.xml" />
-
<property name="configurationClass" value="org.hibernate.cfg.AnnotationConfiguration" />
-
</bean>
-
<!-- 定義事務管理器(宣告式的事務) -->
-
<bean id="transactionManager"
-
class="org.springframework.orm.hibernate3.HibernateTransactionManager">
-
<property name="sessionFactory" ref="sessionFactory" />
-
</bean>
-
<!-- 配置DAO -->
-
<bean id="userDaoTarget" class="com.bluesky.spring.dao.UserDaoImpl">
-
<property name="sessionFactory" ref="sessionFactory" />
-
</bean>
-
<bean id="userDao"
-
class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
-
<!-- 配置事務管理器 -->
-
<property name="transactionManager" ref="transactionManager" />
-
<property name="target" ref="userDaoTarget" />
-
<property name="proxyInterfaces" value="com.bluesky.spring.dao.GeneratorDao" />
-
<!-- 配置事務屬性 -->
-
<property name="transactionAttributes">
-
<props>
-
<prop key="*">PROPAGATION_REQUIRED</prop>
-
</props>
-
</property>
-
</bean>
-
</beans>
第二種方式:所有Bean共享一個代理基類
-
<?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"
-
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">
-
<bean id="sessionFactory"
-
class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
-
<property name="configLocation" value="classpath:hibernate.cfg.xml" />
-
<property name="configurationClass" value="org.hibernate.cfg.AnnotationConfiguration" />
-
</bean>
-
<!-- 定義事務管理器(宣告式的事務) -->
-
<bean id="transactionManager"
-
class="org.springframework.orm.hibernate3.HibernateTransactionManager">
-
<property name="sessionFactory" ref="sessionFactory" />
-
</bean>
-
<bean id="transactionBase"
-
class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean"
-
lazy-init="true" abstract="true">
-
<!-- 配置事務管理器 -->
-
<property name="transactionManager" ref="transactionManager" />
-
<!-- 配置事務屬性 -->
-
<property name="transactionAttributes">
-
<props>
-
<prop key="*">PROPAGATION_REQUIRED</prop>
-
</props>
-
</property>
-
</bean>
-
<!-- 配置DAO -->
-
<bean id="userDaoTarget" class="com.bluesky.spring.dao.UserDaoImpl">
-
<property name="sessionFactory" ref="sessionFactory" />
-
</bean>
-
<bean id="userDao" parent="transactionBase" >
-
<property name="target" ref="userDaoTarget" />
-
</bean>
-
</beans>
第三種方式:使用攔截器
-
<?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"
-
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">
-
<bean id="sessionFactory"
-
class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
-
<property name="configLocation" value="classpath:hibernate.cfg.xml" />
-
<property name="configurationClass" value="org.hibernate.cfg.AnnotationConfiguration" />
-
</bean>
-
<!-- 定義事務管理器(宣告式的事務) -->
-
<bean id="transactionManager"
-
class="org.springframework.orm.hibernate3.HibernateTransactionManager">
-
<property name="sessionFactory" ref="sessionFactory" />
-
</bean>
-
<bean id="transactionInterceptor"
-
class="org.springframework.transaction.interceptor.TransactionInterceptor">
-
<property name="transactionManager" ref="transactionManager" />
-
<!-- 配置事務屬性 -->
-
<property name="transactionAttributes">
-
<props>
-
<prop key="*">PROPAGATION_REQUIRED</prop>
-
</props>
-
</property>
-
</bean>
-
<bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
-
<property name="beanNames">
-
<list>
-
<value>*Dao</value>
-
</list>
-
</property>
-
<property name="interceptorNames">
-
<list>
-
<value>transactionInterceptor</value>
-
</list>
-
</property>
-
</bean>
-
<!-- 配置DAO -->
-
<bean id="userDao" class="com.bluesky.spring.dao.UserDaoImpl">
-
<property name="sessionFactory" ref="sessionFactory" />
-
</bean>
-
</beans>
第四種方式:使用tx標籤配置的攔截器
-
<?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:annotation-config />
-
<context:component-scan base-package="com.bluesky" />
-
<bean id="sessionFactory"
-
class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
-
<property name="configLocation" value="classpath:hibernate.cfg.xml" />
-
<property name="configurationClass" value="org.hibernate.cfg.AnnotationConfiguration" />
-
</bean>
-
<!-- 定義事務管理器(宣告式的事務) -->
-
<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="*" propagation="REQUIRED" />
-
</tx:attributes>
-
</tx:advice>
-
<aop:config>
-
<aop:pointcut id="interceptorPointCuts"
-
expression="execution(* com.bluesky.spring.dao.*.*(..))" />
-
<aop:advisor advice-ref="txAdvice"
-
pointcut-ref="interceptorPointCuts" />
-
</aop:config>
-
</beans>
第五種方式:全註解
-
<?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:annotation-config />
-
<context:component-scan base-package="com.bluesky" />
-
<tx:annotation-driven transaction-manager="transactionManager"/>
-
<bean id="sessionFactory"
-
class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
-
<property name="configLocation" value="classpath:hibernate.cfg.xml" />
-
<property name="configurationClass" value="org.hibernate.cfg.AnnotationConfiguration" />
-
</bean>
-
<!-- 定義事務管理器(宣告式的事務) -->
-
<bean id="transactionManager"
-
class="org.springframework.orm.hibernate3.HibernateTransactionManager">
-
<property name="sessionFactory" ref="sessionFactory" />
-
</bean>
-
</beans>
此時在DAO上需加上@Transactional註解,如下:
-
@Transactional
-
@Component("userDao")
-
public class UserDaoImpl extends HibernateDaoSupport implements UserDao {
-
public List<User> listUsers() {
-
return this.getSession().createQuery("from User").list();
-
}
-
}
總結:以上5種方法是Spring配置事物常用到的配置方法。
2、spring裡面事務的傳播屬性和事務隔離級別
一、Propagation (事務的傳播屬性)
Propagation : key屬性確定代理應該給哪個方法增加事務行為。這樣的屬性最重要的部份是傳播行為。有以下選項可供使用:
PROPAGATION_REQUIRED--支援當前事務,如果當前沒有事務,就新建一個事務。這是最常見的選擇。
PROPAGATION_SUPPORTS--支援當前事務,如果當前沒有事務,就以非事務方式執行。
PROPAGATION_MANDATORY--支援當前事務,如果當前沒有事務,就丟擲異常。
PROPAGATION_REQUIRES_NEW--新建事務,如果當前存在事務,把當前事務掛起。
PROPAGATION_NOT_SUPPORTED--以非事務方式執行操作,如果當前存在事務,就把當前事務掛起。
PROPAGATION_NEVER--以非事務方式執行,如果當前存在事務,則丟擲異常。
1: PROPAGATION_REQUIRED
加入當前正要執行的事務不在另外一個事務裡,那麼就起一個新的事務
比如說,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也要回滾
2: PROPAGATION_SUPPORTS
如果當前在事務中,即以事務的形式執行,如果當前不再一個事務中,那麼就以非事務的形式執行
3: PROPAGATION_MANDATORY
必須在一個事務中執行。也就是說,他只能被一個父事務呼叫。否則,他就要丟擲異常
4: PROPAGATION_REQUIRES_NEW
這個就比較繞口了。 比如我們設計ServiceA.methodA的事務級別為PROPAGATION_REQUIRED,ServiceB.methodB的事務級別為PROPAGATION_REQUIRES_NEW,那麼當執行到ServiceB.methodB的時候,ServiceA.methodA所在的事務就會掛起,ServiceB.methodB會起一個新的事務,等待ServiceB.methodB的事務完成以後,他才繼續執行。他與PROPAGATION_REQUIRED 的事務區別在於事務的回滾程度了。因為ServiceB.methodB是新起一個事務,那麼就是存在兩個不同的事務。如果ServiceB.methodB已經提交,那麼ServiceA.methodA失敗回滾,ServiceB.methodB是不會回滾的。如果ServiceB.methodB失敗回滾,如果他丟擲的異常被ServiceA.methodA捕獲,ServiceA.methodA事務仍然可能提交。
5: PROPAGATION_NOT_SUPPORTED
當前不支援事務。比如ServiceA.methodA的事務級別是PROPAGATION_REQUIRED ,而ServiceB.methodB的事務級別是PROPAGATION_NOT_SUPPORTED ,那麼當執行到ServiceB.methodB時,ServiceA.methodA的事務掛起,而他以非事務的狀態執行完,再繼續ServiceA.methodA的事務。
6: PROPAGATION_NEVER
不能在事務中執行。假設ServiceA.methodA的事務級別是PROPAGATION_REQUIRED, 而ServiceB.methodB的事務級別是PROPAGATION_NEVER ,那麼ServiceB.methodB就要丟擲異常了。
7: PROPAGATION_NESTED
理解Nested的關鍵是savepoint。他與PROPAGATION_REQUIRES_NEW的區別是,PROPAGATION_REQUIRES_NEW另起一個事務,將會與他的父事務相互獨立,而Nested的事務和他的父事務是相依的,他的提交是要等和他的父事務一塊提交的。也就是說,如果父事務最後回滾,他也要回滾的。
二、Isolation Level(事務隔離等級):
1、Serializable:最嚴格的級別,事務序列執行,資源消耗最大;
2、REPEATABLE READ:保證了一個事務不會修改已經由另一個事務讀取但未提交(回滾)的資料。避免了“髒讀取”和“不可重複讀取”的情況,但是帶來了更多的效能損失。
3、READ COMMITTED:大多數主流資料庫的預設事務等級,保證了一個事務不會讀到另一個並行事務已修改但未提交的資料,避免了“髒讀取”。該級別適用於大多數系統。
4、Read Uncommitted:保證了讀取過程中不會讀取到非法資料。隔離級別在於處理多事務的併發問題。
我們知道並行可以提高資料庫的吞吐量和效率,但是並不是所有的併發事務都可以併發執行,這需要檢視資料庫教材的可序列化條件判斷了。這裡就不闡述。
我們首先說併發中可能發生的3中不討人喜歡的事情
1: Dirty reads--讀髒資料。也就是說,比如事務A的未提交(還依然快取)的資料被事務B讀走,如果事務A失敗回滾,會導致事務B所讀取的的資料是錯誤的。
2: non-repeatable reads--資料不可重複讀。比如事務A中兩處讀取資料-total-的值。在第一讀的時候,total是100,然後事務B就把total的資料改成 200,事務A再讀一次,結果就發現,total竟然就變成200了,造成事務A資料混亂。
3: phantom reads--幻象讀資料,這個和non-repeatable reads相似,也是同一個事務中多次讀不一致的問題。但是non-repeatable reads的不一致是因為他所要取的資料集被改變了(比如total的資料),但是phantom reads所要讀的資料的不一致卻不是他所要讀的資料集改變,而是他的條件資料集改變。比如Select account.id where account.name="ppgogo*",第一次讀去了6個符合條件的id,第二次讀取的時候,由於事務b把一個帳號的名字由"dd"改成"ppgogo1",結果取出來了7個數據。
三、readOnly
事務屬性中的readOnly標誌表示對應的事務應該被最優化為只讀事務。
這是一個最優化提示。在一些情況下,一些事務策略能夠起到顯著的最優化效果,例如在使用Object/Relational對映工具(如:Hibernate或TopLink)時避免dirty checking(試圖“重新整理”)。
四、Timeout
在事務屬性中還有定義“timeout”值的選項,指定事務超時為幾秒。在JTA中,這將被簡單地傳遞到J2EE伺服器的事務協調程式,並據此得到相應的解釋