什么是事務(wù)
- 事務(wù)就是一組操作數(shù)據(jù)庫的動作集合可霎。
- 動作集合被完整地執(zhí)行魄鸦,我們稱該事務(wù)被提交。動作集合中的某一部分執(zhí)行失敗癣朗,整個(gè)動作集合提交失敗拾因,回到最初的狀態(tài),稱為事務(wù)回滾旷余。
- 事務(wù)是一系列的動作绢记,它們綜合在一起才是一個(gè)完整的工作單元,這些動作必須全部完成正卧,如果有一個(gè)失敗的話蠢熄,那么事務(wù)就會回滾到最開始的狀態(tài),仿佛什么都沒發(fā)生過一樣穗酥。
事務(wù)的特性
- 原子性(Atomicity):事務(wù)是一個(gè)原子操作护赊,由一系列動作組成惠遏。事務(wù)的原子性確保動作要么全部完成砾跃,要么完全不起作用。(操作)
- 一致性(Consistency):一旦事務(wù)完成(不管成功還是失斀谒薄)抽高,系統(tǒng)必須確保它所建模的業(yè)務(wù)處于一致的狀態(tài),而不會是部分完成部分失敗透绩。在現(xiàn)實(shí)中的數(shù)據(jù)不應(yīng)該被破壞翘骂。(數(shù)據(jù))
- 隔離性(Isolation):可能有許多事務(wù)會同時(shí)處理相同的數(shù)據(jù)壁熄,因此每個(gè)事務(wù)都應(yīng)該與其他事務(wù)隔離開來,防止數(shù)據(jù)損壞碳竟。(數(shù)據(jù))
- 持久性(Durability):一旦事務(wù)完成草丧,無論發(fā)生什么系統(tǒng)錯(cuò)誤,它的結(jié)果都不應(yīng)該受到影響莹桅,這樣就能從任何系統(tǒng)崩潰中恢復(fù)過來昌执。通常情況下,事務(wù)的結(jié)果被寫到持久化存儲器中诈泼。(數(shù)據(jù))
spring事務(wù)管理核心接口
Spring事務(wù)管理的實(shí)現(xiàn)有許多細(xì)節(jié)懂拾,如果對整個(gè)接口框架有個(gè)大體了解會非常有利于我們理解事務(wù),下面通過講解Spring的事務(wù)接口來了解Spring實(shí)現(xiàn)事務(wù)的具體策略铐达。
事務(wù)管理器
Spring并不直接管理事務(wù)岖赋,而是提供了多種事務(wù)管理器,他們將事務(wù)管理的職責(zé)委托給Hibernate或者JTA等持久化機(jī)制所提供的相關(guān)平臺框架的事務(wù)來實(shí)現(xiàn)瓮孙。
Spring事務(wù)管理器的接口是
org.springframework.transaction.PlatformTransactionManager唐断,通過這個(gè)接口,Spring為各個(gè)平臺如JDBC杭抠、Hibernate等都提供了對應(yīng)的事務(wù)管理器栗涂,但是具體的實(shí)現(xiàn)就是各個(gè)平臺自己的事情了。
Public interface PlatformTransactionManager()...{
// 由TransactionDefinition得到TransactionStatus對象
TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException;
// 提交
Void commit(TransactionStatus status) throws TransactionException;
// 回滾
Void rollback(TransactionStatus status) throws TransactionException;
}
從這里可知具體的具體的事務(wù)管理機(jī)制對Spring來說是透明的祈争,它并不關(guān)心那些斤程,那些是對應(yīng)各個(gè)平臺需要關(guān)心的,所以Spring事務(wù)管理的一個(gè)優(yōu)點(diǎn)就是為不同的事務(wù)API提供一致的編程模型菩混,如JTA忿墅、JDBC、Hibernate沮峡、JPA疚脐。
1.JDBC事務(wù)
如果應(yīng)用程序中直接使用JDBC來進(jìn)行持久化,DataSourceTransactionManager會為你處理事務(wù)邊界邢疙。為了使用DataSourceTransactionManager棍弄,你需要使用如下的XML將其裝配到應(yīng)用程序的上下文定義中
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
實(shí)際上,DataSourceTransactionManager是通過調(diào)用java.sql.Connection來管理事務(wù)疟游,而后者是通過DataSource獲取到的呼畸。通過調(diào)用連接的commit()方法來提交事務(wù),同樣颁虐,事務(wù)失敗則通過調(diào)用rollback()方法進(jìn)行回滾蛮原。
2.Hibernate事務(wù)
如果應(yīng)用程序的持久化是通過Hibernate實(shí)習(xí)的,那么你需要使用HibernateTransactionManager另绩。對于Hibernate3儒陨,需要在Spring上下文定義中添加如下的<bean>聲明:
<bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory" />
</bean>
sessionFactory屬性需要裝配一個(gè)Hibernate的session工廠花嘶,HibernateTransactionManager的實(shí)現(xiàn)細(xì)節(jié)是它將事務(wù)管理的職責(zé)委托給org.hibernate.Transaction對象,而后者是從Hibernate Session中獲取到的蹦漠。當(dāng)事務(wù)成功完成時(shí)椭员,HibernateTransactionManager將會調(diào)用Transaction對象的commit()方法,反之笛园,將會調(diào)用rollback()方法拆撼。
基本事務(wù)屬性的定義
上面講到的事務(wù)管理器接口PlatformTransactionManager通過getTransaction(TransactionDefinition definition)方法來得到事務(wù),這個(gè)方法里面的參數(shù)是TransactionDefinition類喘沿,這個(gè)類就定義了一些基本的事務(wù)屬性闸度。
那么什么是事務(wù)屬性呢?事務(wù)屬性可以理解成事務(wù)的一些基本配置蚜印,描述了事務(wù)策略如何應(yīng)用到方法上莺禁。事務(wù)屬性包含了5個(gè)方面:
- 傳播行為
- 隔離規(guī)則
- 回滾規(guī)則
- 事務(wù)超時(shí)
- 是否只讀
TransactionDefinition接口內(nèi)容如下:
public interface TransactionDefinition {
int getPropagationBehavior(); // 返回事務(wù)的傳播行為
int getIsolationLevel(); // 返回事務(wù)的隔離級別,事務(wù)管理器根據(jù)它來控制另外一個(gè)事務(wù)可以看到本事務(wù)內(nèi)的哪些數(shù)據(jù)
int getTimeout(); // 返回事務(wù)必須在多少秒內(nèi)完成
boolean isReadOnly(); // 事務(wù)是否只讀窄赋,事務(wù)管理器能夠根據(jù)這個(gè)返回值進(jìn)行優(yōu)化哟冬,確保事務(wù)是只讀的
}
事務(wù)傳播行為(重點(diǎn))
什么是事務(wù)傳播?
事務(wù)傳播行為用來描述由某一個(gè)事務(wù)傳播行為修飾的方法被嵌套進(jìn)另一個(gè)方法的時(shí)事務(wù)如何傳播忆绰。
public void methodA(){
methodB();
//doSomething
}
@Transaction(Propagation=XXX)
public void methodB(){
//doSomething
}
代碼中methodA()方法嵌套調(diào)用了methodB()方法浩峡,methodB()的事務(wù)傳播行為由@Transaction(Propagation=XXX)設(shè)置決定。這里需要注意的是methodA()并沒有開啟事務(wù)错敢,某一個(gè)事務(wù)傳播行為修飾的方法并不是必須要在開啟事務(wù)的外圍方法中調(diào)用翰灾。
spring事務(wù)傳播行為有哪些?(重點(diǎn)掌握)
傳播行為 | 含義 |
---|---|
PROPAGATION_REQUIRED | 如果當(dāng)前沒有事務(wù)稚茅,就新建一個(gè)事務(wù)纸淮,如果已經(jīng)存在一個(gè)事務(wù)中,加入到這個(gè)事務(wù)中亚享,這是最常見的選擇咽块,也是默認(rèn)的事務(wù)傳播行為。 |
PROPAGATION_REQUIRED_NEW | 新建事務(wù)欺税,如果當(dāng)前存在事務(wù)侈沪,把當(dāng)前事務(wù)掛起。 |
PROPAGATION_SUPPORTS | 支持當(dāng)前事務(wù)晚凿,如果當(dāng)前沒有事務(wù)亭罪,就以非事務(wù)方式執(zhí)行。 |
PROPAGATION_NOT_SUPPORTED | 以非事務(wù)方式執(zhí)行操作晃虫,如果當(dāng)前存在事務(wù)皆撩,就把當(dāng)前事務(wù)掛起扣墩。 |
PROPAGATION_NESTED | 如果當(dāng)前存在事務(wù)哲银,則在嵌套事務(wù)內(nèi)執(zhí)行扛吞。如果當(dāng)前沒有事務(wù),則執(zhí)行與PROPAGATION_REQUIRED類似的操作荆责。 |
PROPAGATION_NEVER | 以非事務(wù)方式執(zhí)行滥比,如果當(dāng)前存在事務(wù),則拋出異常做院。 |
PROPAGATION_MANDATORY | 使用當(dāng)前的事務(wù)盲泛,如果當(dāng)前沒有事務(wù),就拋出異常键耕。 |
當(dāng)使用PROPAGATION_NESTED時(shí)寺滚,底層的數(shù)據(jù)源必須基于JDBC 3.0,并且實(shí)現(xiàn)者需要支持保存點(diǎn)事務(wù)機(jī)制屈雄。
什么是事務(wù)掛起村视?
例如方法A支持事務(wù),方法B不支持事務(wù)酒奶,方法A調(diào)用方法B蚁孔。
在方法A開始運(yùn)行時(shí),系統(tǒng)為它建立Transaction惋嚎,方法A中對于數(shù)據(jù)庫的操作杠氢,會在該Transaction的控制之下。
這時(shí)另伍,方法A調(diào)用方法B鼻百,方法A打開的Transation將掛起,方法B中任何數(shù)據(jù)庫操作摆尝,都不在該Transaction的管理之下愕宋。
當(dāng)方法B返回,方法A繼續(xù)運(yùn)行结榄,之前的Transaction恢復(fù)中贝,后面的數(shù)據(jù)庫操作繼續(xù)在該Transaction的控制之下提交或回滾。
測試案例
用戶實(shí)現(xiàn)類中包含注冊和注冊送積分兩個(gè)業(yè)務(wù)方法:
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserMapper userMapper;
@Autowired
private BSService bsService;
//用戶注冊
@Override
public void registe(User user) {
userMapper.insert(user);
}
//用戶注冊并送積分
@Override
public void registeAndCredit(User user){
registe(user);
Credit credit = new Credit();
credit.setUsername(user.getName());
credit.setScore(20);
bsService.addCredit(credit);
}
}
業(yè)務(wù)實(shí)現(xiàn)類中有送積分的業(yè)務(wù)方法:
@Service
public class BSServiceImpl implements BSService {
@Autowired
private CreditMapper creditMapper;
//送積分
@Override
public void addCredit(Credit credit) {
creditMapper.insert(credit);
throw new RuntimeException();
}
}
registeAndCredit方法中包含了用戶注冊和送積分兩個(gè)數(shù)據(jù)庫操作臼朗。
1.PROPAGATION_REQUIRED
如果當(dāng)前沒有事務(wù)邻寿,就新建一個(gè)事務(wù),如果已經(jīng)存在一個(gè)事務(wù)中视哑,加入到這個(gè)事務(wù)中绣否,這是最常見的選擇,也是默認(rèn)的事務(wù)傳播行為挡毅。
場景一:registeAndCredit方法不添加事務(wù)蒜撮,registe方法和addCredit方法均添加PROPAGATION_REQUIRED事務(wù),addCredit方法拋出異常。
//送積分
@Override
@Transactional
public void addCredit(Credit credit) {
creditMapper.insert(credit);
throw new RuntimeException();
}
//用戶注冊
@Override
@Transactional
public void registe(User user) {
userMapper.insert(user);
}
@Test
public void test03(){
User user = new User();
user.setName("小紅");
user.setAge(26);
userService.registeAndCredit(user);
}
測試結(jié)果:用戶注冊成功段磨,送積分失敗取逾。
測試分析:因?yàn)閞egisteAndCredit沒有添加事務(wù),對于registe和addCredit來說苹支,屬于當(dāng)前沒有事務(wù)砾隅,所以各自新建事務(wù),兩個(gè)事務(wù)是獨(dú)立的债蜜,互不影響晴埂。
場景二:registeAndCredit方法添加PROPAGATION_REQUIRED事務(wù)。對于registe和addCredit來說寻定,屬于存在事務(wù)儒洛,是否添加PROPAGATION_REQUIRED均屬于同一個(gè)事務(wù)。
如果事務(wù)中異常被try catch處理后狼速,事務(wù)正常提交晶丘。(嵌套調(diào)用方法除外)
使用此事務(wù),在事務(wù)A中又開了一個(gè)事務(wù)B唐含,其實(shí)AB是同一種事務(wù)浅浮,當(dāng)事務(wù)回滾B時(shí)已經(jīng)將事務(wù)標(biāo)記為回滾,如果在A中try catch之后捷枯,事務(wù)A再次執(zhí)行commit會報(bào)異常:org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only(事務(wù)已經(jīng)被標(biāo)記為回滾)
代碼如下:
//用戶注冊并送積分
@Transactional(propagation = Propagation.REQUIRED)
@Override
public void registeAndCredit(User user){
registe(user);
Credit credit = new Credit();
credit.setUsername(user.getName());
credit.setScore(20);
try {
bsService.addCredit(credit);
}catch (Exception e){
System.out.println("%%%%%%%%%%%%%%%%%%%jiaixnxiao%%%%%%%%%%");
}
}
//送積分
@Override
@Transactional(propagation = Propagation.REQUIRED)
public void addCredit(Credit credit) {
creditMapper.insert(credit);
throw new RuntimeException();
}
執(zhí)行registeAndCredit拋出事務(wù)已經(jīng)被標(biāo)記為回滾的異常滚秩。
2.PROPAGATION_REQUIRED_NEW
新建事務(wù),如果當(dāng)前存在事務(wù)淮捆,把當(dāng)前事務(wù)掛起郁油。
PROPAGATION_REQUIRES_NEW 啟動一個(gè)新的、和外層事務(wù)無關(guān)的“內(nèi)部”事務(wù)攀痊。該事務(wù)擁有自己的獨(dú)立隔離級別和鎖桐腌,不依賴于外部事務(wù),獨(dú)立地提交和回滾苟径。當(dāng)內(nèi)部事務(wù)開始執(zhí)行時(shí)案站,外部事務(wù) 將被掛起,內(nèi)務(wù)事務(wù)結(jié)束時(shí)棘街,外部事務(wù)才繼續(xù)執(zhí)行蟆盐。
場景一:registeAndCredit添加PROPAGATION_REQUIRED事務(wù),addCredit添加PROPAGATION_REQUIRES_NEW事務(wù)遭殉,并且拋出異常
- 如果不在registeAndCredit中對addCredit進(jìn)行try catch的話石挂,兩個(gè)事務(wù)均回滾,因?yàn)閍ddCredit拋出異常到registeAndCredit险污。(可以理解為addCredit開啟新事物拋出異常并回滾痹愚,異常被當(dāng)前事務(wù)registeAndCredit接收,因此也回滾)。
- registeAndCredit對addCredit進(jìn)行try catch拯腮,新事物回滾窖式,當(dāng)前事務(wù)正常commit,所以用戶注冊成功疾瓮,送積分操作回滾脖镀。
場景二:registeAndCredit添加PROPAGATION_REQUIRES_NEW事務(wù)飒箭,addCredit添加PROPAGATION_REQUIRES_NEW事務(wù)狼电,并且拋出異常,與場景一相同弦蹂。
場景三:registeAndCredit添加PROPAGATION_REQUIRED事務(wù)肩碟,addCredit添加PROPAGATION_REQUIRES_NEW事務(wù),在registeAndCredit中拋出異常凸椿,因?yàn)閍ddCredit創(chuàng)建了新事務(wù)削祈,是兩個(gè)不同的事務(wù),所以用戶注冊回滾脑漫,送積分提交成功髓抑。
3.PROPAGATION_NESTED
如果當(dāng)前存在事務(wù),則在嵌套事務(wù)內(nèi)執(zhí)行优幸。如果當(dāng)前沒有事務(wù)吨拍,則執(zhí)行與PROPAGATION_REQUIRED類似的操作。
場景一:與PROPAGATION_REQUIRED_NEW場景一相同网杆。
場景三:registeAndCredit添加PROPAGATION_REQUIRED事務(wù)羹饰,addCredit添加PROPAGATION_NESTED事務(wù),在registeAndCredit中拋出異常碳却。addCredit創(chuàng)建了registeAndCredit事務(wù)的嵌套事務(wù)队秩,外圍事務(wù)拋出異常回滾昼浦,嵌套事務(wù)也回滾馍资。所以用戶注冊回滾,送積分回滾关噪。
新事務(wù)與嵌套事務(wù)
PROPAGATION_REQUIRES_NEW 和 PROPAGATION_NESTED 的最大區(qū)別在于:PROPAGATION_REQUIRES_NEW 將創(chuàng)建一個(gè)全新的事務(wù)迷帜,它和外層事務(wù)沒有任何關(guān)系,而 PROPAGATION_NESTED 將創(chuàng)建一個(gè)依賴于外層事務(wù)的子事務(wù)色洞,當(dāng)外層事務(wù)提交或回滾時(shí)戏锹,子事務(wù)也會連帶提交和回滾。
注意問題
- 當(dāng)業(yè)務(wù)方法被設(shè)置為PROPAGATION_MANDATORY時(shí)火诸,它就不能被非事務(wù)的業(yè)務(wù)方法調(diào)用锦针。如將addCredit設(shè)置為PROPAGATION_MANDATORY,如果controller直接調(diào)用addCredit方法,將引發(fā)一個(gè)異常奈搜。正確的情況是:addCredit方法必須被另一個(gè)帶事務(wù)的業(yè)務(wù)方法調(diào)用悉盆。所以 PROPAGATION_MANDATORY的方法一般都是被其它業(yè)務(wù)方法間接調(diào)用的。
- 當(dāng)業(yè)務(wù)方法被設(shè)置為PROPAGATION_NEVER時(shí)馋吗,它將不能被擁有事務(wù)的其它業(yè)務(wù)方法調(diào)用焕盟。假設(shè)addCredit設(shè)置為PROPAGATION_NEVER,當(dāng)registeAndCredit擁有一個(gè)事務(wù)時(shí)宏粤,addCredit方法將拋出異常脚翘。所以PROPAGATION_NEVER方法一般是被直接調(diào)用的。
- 當(dāng)方法被設(shè)置為PROPAGATION_NOT_SUPPORTED時(shí)绍哎,外層業(yè)務(wù)方法的事務(wù)會被掛起来农,當(dāng)內(nèi)部方法運(yùn)行完成后,外層方法的事務(wù)重新運(yùn)行崇堰。如果外層方法沒有事務(wù)沃于,直接運(yùn)行,不需要做任何其它的事海诲。