在Spring學(xué)習(xí)的書中,AOP最常見的應(yīng)用場(chǎng)景就是事務(wù)管理了∏耄基于AOP的事務(wù)管理是聲明式事務(wù),原理就是在方法的啟動(dòng)前設(shè)置事務(wù)開啟氯析,在方法結(jié)束后提交事務(wù)亏较,如果當(dāng)中有異常拋出,則事務(wù)回滾掩缓。
Spring的事務(wù)一般放到service層進(jìn)行執(zhí)行雪情,所以該層的方法,如果是事務(wù)處理的你辣,不能手工try catch異常巡通,必須由AOP來管理這些異常,不然事務(wù)將不會(huì)起作用舍哄。
這個(gè)簡(jiǎn)單講一下基于Spring的事務(wù)控制宴凉,沒有很深入,不過應(yīng)付多數(shù)開發(fā)是綽綽有余了表悬。
Spring事務(wù)兩種方式
spring支持編程式事務(wù)管理和聲明式事務(wù)管理兩種方式弥锄。
編程式事務(wù)管理使用TransactionTemplate或者直接使用底層的PlatformTransactionManager。對(duì)于編程式事務(wù)管理蟆沫,spring推薦使用TransactionTemplate籽暇。
聲明式事務(wù)管理建立在AOP之上的。其本質(zhì)是對(duì)方法前后進(jìn)行攔截饭庞,然后在目標(biāo)方法開始之前創(chuàng)建或者加入一個(gè)事務(wù)戒悠,在執(zhí)行完目標(biāo)方法之后根據(jù)執(zhí)行情況提交或者回滾事務(wù)。聲明式事務(wù)最大的優(yōu)點(diǎn)就是不需要通過編程的方式管理事務(wù)舟山,這樣就不需要在業(yè)務(wù)邏輯代碼中摻雜事務(wù)管理的代碼绸狐,只需在配置文件中做相關(guān)的事務(wù)規(guī)則聲明(或通過基于@Transactional注解的方式),便可以將事務(wù)規(guī)則應(yīng)用到業(yè)務(wù)邏輯中累盗。
顯然聲明式事務(wù)管理要優(yōu)于編程式事務(wù)管理六孵,這正是spring倡導(dǎo)的非侵入式的開發(fā)方式。聲明式事務(wù)管理使業(yè)務(wù)代碼不受污染幅骄,一個(gè)普通的POJO對(duì)象劫窒,只要加上注解就可以獲得完全的事務(wù)支持。和編程式事務(wù)相比拆座,聲明式事務(wù)唯一不足地方是主巍,后者的最細(xì)粒度只能作用到方法級(jí)別冠息,無(wú)法做到像編程式事務(wù)那樣可以作用到代碼塊級(jí)別。但是即便有這樣的需求孕索,也存在很多變通的方法逛艰,比如,可以將需要進(jìn)行事務(wù)管理的代碼塊獨(dú)立為方法等等搞旭。
聲明式事務(wù)管理也有兩種常用的方式散怖,一種是基于tx和aop名字空間的xml配置文件,另一種就是基于@Transactional注解肄渗。顯然基于注解的方式更簡(jiǎn)單易用镇眷,更清爽。
事務(wù)隔離級(jí)別
隔離級(jí)別是指若干個(gè)并發(fā)的事務(wù)之間的隔離程度翎嫡。TransactionDefinition 接口中定義了五個(gè)表示隔離級(jí)別的常量:
- TransactionDefinition.ISOLATION_DEFAULT:這是默認(rèn)值欠动,表示使用底層數(shù)據(jù)庫(kù)的默認(rèn)隔離級(jí)別。對(duì)大部分?jǐn)?shù)據(jù)庫(kù)而言惑申,通常這值就是TransactionDefinition.ISOLATION_READ_COMMITTED具伍。
- TransactionDefinition.ISOLATION_READ_UNCOMMITTED:該隔離級(jí)別表示一個(gè)事務(wù)可以讀取另一個(gè)事務(wù)修改但還沒有提交的數(shù)據(jù)。該級(jí)別不能防止臟讀圈驼,不可重復(fù)讀和幻讀人芽,因此很少使用該隔離級(jí)別。比如PostgreSQL實(shí)際上并沒有此級(jí)別绩脆。
- TransactionDefinition.ISOLATION_READ_COMMITTED:該隔離級(jí)別表示一個(gè)事務(wù)只能讀取另一個(gè)事務(wù)已經(jīng)提交的數(shù)據(jù)萤厅。該級(jí)別可以防止臟讀,這也是大多數(shù)情況下的推薦值衙伶。
- TransactionDefinition.ISOLATION_REPEATABLE_READ:該隔離級(jí)別表示一個(gè)事務(wù)在整個(gè)過程中可以多次重復(fù)執(zhí)行某個(gè)查詢,并且每次返回的記錄都相同害碾。該級(jí)別可以防止臟讀和不可重復(fù)讀矢劲。
- TransactionDefinition.ISOLATION_SERIALIZABLE:所有的事務(wù)依次逐個(gè)執(zhí)行,這樣事務(wù)之間就完全不可能產(chǎn)生干擾慌随,也就是說芬沉,該級(jí)別可以防止臟讀、不可重復(fù)讀以及幻讀阁猜。但是這將嚴(yán)重影響程序的性能丸逸。通常情況下也不會(huì)用到該級(jí)別。
事務(wù)傳播行為
所謂事務(wù)的傳播行為是指剃袍,如果在開始當(dāng)前事務(wù)之前黄刚,一個(gè)事務(wù)上下文已經(jīng)存在,此時(shí)有若干選項(xiàng)可以指定一個(gè)事務(wù)性方法的執(zhí)行行為民效。在TransactionDefinition定義中包括了如下幾個(gè)表示傳播行為的常量:
- TransactionDefinition.PROPAGATION_REQUIRED:如果當(dāng)前存在事務(wù)憔维,則加入該事務(wù)涛救;如果當(dāng)前沒有事務(wù),則創(chuàng)建一個(gè)新的事務(wù)业扒。這是默認(rèn)值检吆。
- TransactionDefinition.PROPAGATION_REQUIRES_NEW:創(chuàng)建一個(gè)新的事務(wù),如果當(dāng)前存在事務(wù)程储,則把當(dāng)前事務(wù)掛起蹭沛。
- TransactionDefinition.PROPAGATION_SUPPORTS:如果當(dāng)前存在事務(wù),則加入該事務(wù)章鲤;如果當(dāng)前沒有事務(wù)摊灭,則以非事務(wù)的方式繼續(xù)運(yùn)行。
- TransactionDefinition.PROPAGATION_NOT_SUPPORTED:以非事務(wù)方式運(yùn)行咏窿,如果當(dāng)前存在事務(wù)斟或,則把當(dāng)前事務(wù)掛起。
- TransactionDefinition.PROPAGATION_NEVER:以非事務(wù)方式運(yùn)行集嵌,如果當(dāng)前存在事務(wù)萝挤,則拋出異常。
- TransactionDefinition.PROPAGATION_MANDATORY:如果當(dāng)前存在事務(wù)根欧,則加入該事務(wù)怜珍;如果當(dāng)前沒有事務(wù),則拋出異常凤粗。
- TransactionDefinition.PROPAGATION_NESTED:如果當(dāng)前存在事務(wù)酥泛,則創(chuàng)建一個(gè)事務(wù)作為當(dāng)前事務(wù)的嵌套事務(wù)來運(yùn)行;如果當(dāng)前沒有事務(wù)嫌拣,則該取值等價(jià)于TransactionDefinition.PROPAGATION_REQUIRED柔袁。
事務(wù)超時(shí)
所謂事務(wù)超時(shí),就是指一個(gè)事務(wù)所允許執(zhí)行的最長(zhǎng)時(shí)間异逐,如果超過該時(shí)間限制但事務(wù)還沒有完成捶索,則自動(dòng)回滾事務(wù)。在 TransactionDefinition 中以 int 的值來表示超時(shí)時(shí)間灰瞻,其單位是秒腥例。默認(rèn)設(shè)置為底層事務(wù)系統(tǒng)的超時(shí)值,如果底層數(shù)據(jù)庫(kù)事務(wù)系統(tǒng)沒有設(shè)置超時(shí)值酝润,那么就是none燎竖,沒有超時(shí)限制。
事務(wù)只讀屬性
只讀事務(wù)用于客戶代碼只讀但不修改數(shù)據(jù)的情形要销,只讀事務(wù)用于特定情景下的優(yōu)化构回,比如使用Hibernate的時(shí)候。
默認(rèn)為讀寫事務(wù)。
“只讀事務(wù)”并不是一個(gè)強(qiáng)制選項(xiàng)捐凭,它只是一個(gè)“暗示”拨扶,提示數(shù)據(jù)庫(kù)驅(qū)動(dòng)程序和數(shù)據(jù)庫(kù)系統(tǒng),這個(gè)事務(wù)并不包含更改數(shù)據(jù)的操作茁肠,那么JDBC驅(qū)動(dòng)程序和數(shù)據(jù)庫(kù)就有可能根據(jù)這種情況對(duì)該事務(wù)進(jìn)行一些特定的優(yōu)化患民,比方說不安排相應(yīng)的數(shù)據(jù)庫(kù)鎖,以減輕事務(wù)對(duì)數(shù)據(jù)庫(kù)的壓力垦梆,畢竟事務(wù)也是要消耗數(shù)據(jù)庫(kù)的資源的匹颤。
但是你非要在“只讀事務(wù)”里面修改數(shù)據(jù),也并非不可以托猩,只不過對(duì)于數(shù)據(jù)一致性的保護(hù)不像“讀寫事務(wù)”那樣保險(xiǎn)而已印蓖。
因此,“只讀事務(wù)”僅僅是一個(gè)性能優(yōu)化的推薦配置而已京腥,并非強(qiáng)制你要這樣做不可
spring事務(wù)回滾規(guī)則
spring事務(wù)管理器回滾一個(gè)事務(wù)的推薦方法是在當(dāng)前事務(wù)的上下文內(nèi)拋出異常赦肃。spring事務(wù)管理器會(huì)捕捉任何未處理的異常,然后依據(jù)規(guī)則決定是否回滾拋出異常的事務(wù)公浪。
默認(rèn)配置下他宛,spring只有在拋出的異常為運(yùn)行時(shí)unchecked異常時(shí)才回滾該事務(wù),也就是拋出的異常為RuntimeException的子類(Errors也會(huì)導(dǎo)致事務(wù)回滾)欠气,而拋出checked異常則不會(huì)導(dǎo)致事務(wù)回滾厅各。可以明確的配置在拋出那些異常時(shí)回滾事務(wù)预柒,包括checked異常队塘。也可以明確定義那些異常拋出時(shí)不回滾事務(wù)。還可以編程性的通過setRollbackOnly()方法來指示一個(gè)事務(wù)必須回滾宜鸯,在調(diào)用完setRollbackOnly()后你所能執(zhí)行的唯一操作就是回滾憔古。
上面講了這么多,都是概念性的淋袖,很多人開發(fā)中會(huì)很疑惑鸿市, 我也沒有寫事務(wù)的控制語(yǔ)句啊,怎么就實(shí)現(xiàn)事務(wù)了呢适贸,下面簡(jiǎn)單說一下灸芳。
Spring的基本配置參照8.Spring JdbcTemplate這一節(jié)
利用配置來完成事務(wù)控制
applicatonContext.xml配置事務(wù)
<!-- 利用AOP配置事務(wù)處理 -->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource">
</property>
</bean>
<aop:config>
<aop:pointcut id="bussinessService"
expression="execution(public * com.critc.service.*.*(..))" />
<aop:advisor pointcut-ref="bussinessService" advice-ref="txAdvice" />
</aop:config>
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="add*" propagation="REQUIRED" />
<tx:method name="update*" propagation="REQUIRED" />
<tx:method name="delete*" propagation="REQUIRED" />
<tx:method name="save*" propagation="REQUIRED" />
<tx:method name="import*" propagation="REQUIRED" />
<tx:method name="*" read-only="true" />
</tx:attributes>
</tx:advice>
這一段代碼是聲明式事務(wù)配置的核心涝桅,共包括三部分拜姿,第一部分是定義事務(wù)管理器transactionManager
,這個(gè)事務(wù)管理器有好多種冯遂,如果是跨數(shù)據(jù)庫(kù)的事務(wù)蕊肥,還會(huì)用到JTA。
第二段是利用AOP定義切入點(diǎn)execution(public * com.critc.service.*.*(..))
第三段是定義事務(wù)的傳播特性,這一塊如果深入講起來會(huì)非常非常復(fù)雜壁却,肯定會(huì)把多數(shù)人搞糊涂批狱。簡(jiǎn)單記住兩點(diǎn)就行,如果是增刪改展东,就是REQUIRED
赔硫,如果是查詢就是read-only
。其中有一個(gè)method name的開始方法盐肃,一般以add|update|delete|save|import
開頭的方法才啟用事務(wù)爪膊,可以對(duì)數(shù)據(jù)庫(kù)操作,其余只能是讀砸王。
StaffService.java
@Service
public class StaffService {
@Autowired
private StaffDao staffDao;
public void add() {
Staff staff = new Staff();
staff.setName("JDBCTemplate");
staffDao.add(staff);
Staff staff2 = new Staff();
staff2.setName("JDBCTemplate123456789");
staffDao.add(staff2);
}
}
TestService.java
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath*:applicationContext.xml"})
public class TestService {
@Autowired
private StaffService staffService;
@Test
public void testAdd() {
staffService.add();
}
}
簡(jiǎn)單測(cè)試一下推盛,比如往表里寫數(shù)據(jù),一共 寫兩條記錄谦铃,第一條沒問題耘成,第二條會(huì)超長(zhǎng)(字段長(zhǎng)度設(shè)置為20)。
當(dāng)點(diǎn)擊執(zhí)行時(shí)會(huì)報(bào)錯(cuò):
兩條記錄都沒有寫入數(shù)據(jù)庫(kù)驹闰,這就是一個(gè)事務(wù)瘪菌。只有所有操作都成功后才會(huì)統(tǒng)一提交。
只讀方法設(shè)置疮方。
比如我把StaffService里面的add
方法改為doadd
控嗜,然后執(zhí)行測(cè)試,會(huì)報(bào)以下錯(cuò)誤
提示該方法是只讀的骡显。開發(fā)中經(jīng)常會(huì)遇到類似錯(cuò)誤疆栏,一定要快速定位進(jìn)行改正。
利用注解來完成事務(wù)控制
StaffService2.java
@Service
public class StaffService2 {
@Autowired
private StaffDao staffDao;
@Transactional
public void add() {
Staff staff = new Staff();
staff.setName("JDBCTemplate");
staffDao.add(staff);
Staff staff2 = new Staff();
staff2.setName("JDBCTemplate123456789");
staffDao.add(staff2);
}
}
在需要啟用事務(wù)的方法上加上@Transactional
即可惫谤。
這種方式不推薦采用壁顶,主要是每個(gè)方法都需要單獨(dú)配置比較麻煩,一般開發(fā)都是采用xml全局配置溜歪。