一邓萨、簡介
事務(wù)管理是企業(yè)級應(yīng)用程序開發(fā)中必不可少的技術(shù)地梨,用來確保數(shù)據(jù)的完整性和一致性
菊卷。事務(wù)是一系列的動作,這些動作要么全部完成宝剖,要么全部不起作用洁闰。
比如去銀行取款,總共分兩個步驟万细,取出錢扑眉,卡里扣錢,如果你在取錢的時候赖钞,突然出現(xiàn)停電或者及其故障腰素,此時很有可能出現(xiàn)兩種情況,要么你取出錢雪营,但是卡里沒扣錢弓千,或者是,你沒取出錢献起,但是卡里白白扣了那么多錢洋访。這肯定是不允許的,因此征唬,使用事務(wù)捌显,可以通過回滾操作茁彭,將已經(jīng)進行的操作全部取消总寒,相當于返回到了你取出錢之前的狀態(tài)
事務(wù)的四個關(guān)鍵屬性(ACID):原子性、一致性理肺、隔離性摄闸、持久性
二、Spring中的事務(wù)管理器
Spring 從不同的事務(wù)管理 API 中抽象了一整套的事務(wù)機制妹萨,開發(fā)人員不必了解底層的事務(wù) API年枕,就可以利用這些事務(wù)機制。有了這些事務(wù)機制乎完,事務(wù)管理代碼就能獨立于特定的事務(wù)技術(shù)了
Spring事務(wù)管理器的接口是 org.springframework.transaction.PlatformTransactionManager
熏兄,通過這個接口,Spring為各個平臺如JDBC树姨、Hibernate等都提供了對應(yīng)的事務(wù)管理器
三摩桶、JDBC中的事務(wù)
如果應(yīng)用程序中直接使用 JDBC 來進行持久化,DataSourceTransactionManager
會為你處理事務(wù)邊界帽揪。為了使用 DataSourceTransactionManager
硝清,你需要使用如下的XML將其裝配到應(yīng)用程序的上下文定義中:
<!-- 配置事務(wù)管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
配置文件中需要引用 JDBC 的 Bean 來進行配置。實際上转晰,DataSourceTransactionManager
是通過調(diào)用 java.sql.Connection
來管理事務(wù)芦拿,而后者是通過 DataSource
獲取到的士飒。通過調(diào)用連接的 commit()
方法來提交事務(wù),同樣蔗崎,事務(wù)失敗則通過調(diào)用 rollback()
方法進行回滾酵幕。
四、使用兩種方式來管理事務(wù)
使用@Transactional注解聲明式地管理事務(wù)
- 在需要定義事務(wù)操作的方法上加上
@Transactional
注解缓苛,且只能標注共有方法 - 在 Bean 配置文件中只需使用
<tx:annotation-driven>
元素裙盾,并為之指定事務(wù)管理器就可以了 - 如果事務(wù)處理器的名稱是
transactionManager
,就可以在<tx:annotation-driven>
元素中省略transaction-manager
屬性他嫡,這個元素會自動檢測該名稱的事務(wù)管理器
<!-- 啟用事務(wù)注解 -->
<tx:annotation-driven transaction-manager="transactionManager"/>
使用事務(wù)通知來聲明式的管理事務(wù)
- 事務(wù)管理是一種橫切關(guān)注點
- 使用
<tx:advice>
元素聲明事務(wù)通知番官,同時在里面設(shè)置事務(wù)的各種屬性 - 通過
<aop:config>
配置事務(wù)切入點,由于事務(wù)通知在<aop:config>
元素外部聲明的钢属,所以無法直接與切入點產(chǎn)生關(guān)聯(lián)徘熔,必須在<aop:config>
元素里面聲明一個<aop:advisor>
(增強器)與切入點想聯(lián)系 - 由于 Spring AOP 是基于代理的方法,因此只能增強公共方法
<!-- 1. 配置事務(wù)管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 2. 配置事務(wù)屬性 -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<!-- 根據(jù)方法名指定事務(wù)的屬性 -->
<tx:method name="purchase" propagation="REQUIRES_NEW"/>
<!-- 指定 get 開頭的方法為只讀的 -->
<tx:method name="get*" read-only="true"/>
<tx:method name="*"/>
</tx:attributes>
</tx:advice>
<!-- 3. 配置事務(wù)切入點, 以及把事務(wù)切入點和事務(wù)屬性關(guān)聯(lián)起來 -->
<aop:config>
<!-- 配置切點表達式 -->
<aop:pointcut expression="execution(* edu.just.spring.jdbc.transaction.BookShopService.*(..))" id="txPointCut"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="txPointCut"/>
</aop:config>
五淆党、事務(wù)傳播屬性
當事務(wù)方法被另一個事務(wù)方法調(diào)用時酷师,必須指定事務(wù)應(yīng)該如何傳播,如:被調(diào)用的方法是在調(diào)用自己的方法所在事務(wù)中運行染乌,還是創(chuàng)建一個自己的新事務(wù)山孔,并在自己的新事務(wù)中運行。以下是就介紹幾種傳播行為:
-
REQUIRED
:如果有事務(wù)在運行荷憋,當前的方法就在這個事務(wù)內(nèi)運行台颠;否則就創(chuàng)建一個新的事務(wù),并在自己的事務(wù)內(nèi)運行 -
REQUIRED_NEW
:當前的方法必須啟動新事務(wù)勒庄,并在它自己的事務(wù)內(nèi)運行串前,如果運行自己事務(wù)同時還有其他事務(wù)在運行,應(yīng)該把其他事務(wù)暫時掛起 -
SUPPORTS
:如果有事務(wù)在運行实蔽,當前的方法就在這個事務(wù)內(nèi)運行荡碾,否則它可以不運行在事務(wù)內(nèi) -
NOT_SUPPORTED
:當前的方法不應(yīng)該運行在事務(wù)中,如果有運行的事務(wù)局装,將它掛起 -
MANDATORY
:當前的方法必須在事務(wù)內(nèi)部坛吁,如果沒有正在運行的事務(wù),就拋出異常 -
NESTED
:如果有事務(wù)在運行铐尚,當前的方法就應(yīng)該在這個事務(wù)的嵌套事務(wù)內(nèi)運行
如何設(shè)置拨脉?
- 使用 @Transactional 注解
@Transactional(propagation=Propagation.REQUIRES_NEW)
@Override
public void purchase(String username, String isbn) {
....
}
- 在事務(wù)通知中配置傳播屬性
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<!-- 根據(jù)方法名指定事務(wù)的屬性 -->
<tx:method name="purchase" propagation="REQUIRES_NEW"/>
</tx:attributes>
</tx:advice>
可以在 <tx:method>
元素中設(shè)置傳播事務(wù)屬性
五、并發(fā)事務(wù)所導(dǎo)致的問題
當同一個應(yīng)用程序或者不同應(yīng)用程序中的多個事務(wù)在同一個數(shù)據(jù)集上并發(fā)執(zhí)行時塑径,可能會出現(xiàn)許多意外的問題
- 臟讀:臟讀就是指當一個事務(wù)正在訪問數(shù)據(jù)女坑,并且對數(shù)據(jù)進行了修改,而這種修改還沒有提交到數(shù)據(jù)庫中统舀,這時匆骗,另外一個事務(wù)也訪問這個數(shù)據(jù)劳景,然后使用了這個數(shù)據(jù)。此時若這個數(shù)據(jù)進行回滾碉就,則事務(wù)讀取的數(shù)據(jù)就是臨時且無效的盟广。
- 不可重復(fù)讀:是指在一個事務(wù)內(nèi),多次讀同一數(shù)據(jù)瓮钥。在這個事務(wù)還沒有結(jié)束時筋量,對于兩個事務(wù),第一個事務(wù)先讀取了一個字段碉熄,然后第二個事務(wù)修改了該字段桨武,之后,第一個事務(wù)再次讀取了同一個字段锈津,那么兩次讀取到的值是不一樣的呀酸。這樣就發(fā)生了在一個事務(wù)內(nèi)兩次讀到的數(shù)據(jù)是不一樣的,因此稱為是不可重復(fù)讀琼梆。
-
幻讀:第一個事務(wù)從表中讀取了一個字段性誉,然后第二個事務(wù)在該表中插入了一些新的行,之后如果第一個事務(wù)再次讀取同一個表茎杂,會發(fā)現(xiàn)多出幾行错览,如同發(fā)僧侶幻覺一般
六、事務(wù)的隔離級別
理論上說煌往,事務(wù)應(yīng)該彼此完全隔離倾哺,以避免并發(fā)事務(wù)所導(dǎo)致的問題。然而携冤,那樣會對性能產(chǎn)生極大的影響悼粮,因此為事務(wù)必須按順序執(zhí)行闲勺。在實際開發(fā)中曾棕,為了提升性能,事務(wù)會以比較低的隔離級別來運行
-
DEFAULT
:使用底層數(shù)據(jù)庫的默認隔離級別菜循,對于大多數(shù)數(shù)據(jù)庫來說翘地,默認隔離級別為 READ_COMMITED,Mysql 的默認隔離級別是 REPEATABLE-READ -
READ_UNCOMMITEED
:允許事務(wù)讀取未被其他事務(wù)提交的變更癌幕,會出現(xiàn)臟讀衙耕、不可重復(fù)度和幻讀 -
READ_COMMITEED
:只允許事務(wù)讀取已經(jīng)被其他事務(wù)提交的變更,可以避免臟讀勺远,但不可重復(fù)讀和幻讀可能出現(xiàn) -
REPEATABLE_READ
:確保事務(wù)可以多次從一個字段中讀取相同的值橙喘,在這個事務(wù)持續(xù)期間,禁止其他事務(wù)對這個字段進行更新胶逢,可以避免臟讀和不可重復(fù)讀厅瞎,但是幻讀的問題依舊存在 -
SERIALIZABLE
:確保事務(wù)可以從一個表中讀取相同的行饰潜,在這個事務(wù)期間,禁止其他事務(wù)對改變執(zhí)行增和簸、刪彭雾、改操作,所有并發(fā)問題都可以避免锁保,但是性能低下
PS:Oracle 支持兩種事務(wù)隔離級別薯酝,READ_COMMITEED
和 SERIALIZABLE
。MySql 支持4種事務(wù)隔離級別
如何設(shè)置爽柒?
- 用 @Transactional 注解
@Transactional(propagation=Propagation.REQUIRED, isolation=Isolation.READ_COMMITTED)
@Override
public void purchase(String username, String isbn) {
...
}
可以在 @Transactional
地 isolation
屬性中設(shè)置隔離級別
- 可以在
<tx:method>
元素中指定隔離級別
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<!-- 根據(jù)方法名指定事務(wù)的屬性 -->
<tx:method name="purchase" propagation="REQUIRES_NEW" isolation="READ_COMMITTED"/>
</tx:attributes>
</tx:advice>
七吴菠、設(shè)置超時和只讀事務(wù)屬性
- 超時和只讀屬性可以在
@Transactional
注解中定義。超時屬性以秒為單位來計算
@Transactional(propagation=Propagation.REQUIRED, isolation=Isolation.READ_COMMITTED, readOnly=false, timeout=3)
@Override
public void purchase(String username, String isbn) {
...
}
- 也可以在
<tx:method>
中進行指定
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<!-- 根據(jù)方法名指定事務(wù)的屬性 -->
<tx:method name="purchase" propagation="REQUIRES_NEW" isolation="READ_COMMITTED"/>
<!-- 指定 get 開頭的方法為只讀的 -->
<tx:method name="get*" read-only="true"/>
<tx:method name="*"/>
</tx:attributes>
</tx:advice>