事務(wù)的嵌套概念
所謂事務(wù)的嵌套就是兩個(gè)事務(wù)方法之間相互調(diào)用初厚。spring事務(wù)開啟 ,或者是基于接口的或者是基于類的代理被創(chuàng)建(注意一定要是代理,不能手動(dòng)new 一個(gè)對(duì)象产禾,并且此類(有無接口都行)一定要被代理——spring中的bean只要納入了IOC管理都是被代理的)排作。所以在同一個(gè)類中一個(gè)方法調(diào)用另一個(gè)方法有事務(wù)的方法,事務(wù)是不會(huì)起作用的亚情。
事務(wù)異惩荆回滾
Spring默認(rèn)情況下會(huì)對(duì)運(yùn)行期例外(RunTimeException),即uncheck異常楞件,進(jìn)行事務(wù)回滾衫生。
如果遇到checked異常就不回滾。
如何改變默認(rèn)規(guī)則
- 讓checked例外也回滾:在整個(gè)方法前加上 @Transactional(rollbackFor=Exception.class)
- 讓unchecked例外不回滾: @Transactional(notRollbackFor=RunTimeException.class)
- 不需要事務(wù)管理的(只查詢的)方法:@Transactional(propagation=Propagation.NOT_SUPPORTED)
上面三種方式也可在xml配置
spring事務(wù)傳播屬性
在 spring的 TransactionDefinition接口中一共定義了六種事務(wù)傳播屬性:
PROPAGATION_REQUIRED -- 支持當(dāng)前事務(wù)土浸,如果當(dāng)前沒有事務(wù)罪针,就新建一個(gè)事務(wù)。這是最常見的選擇黄伊。
PROPAGATION_SUPPORTS -- 支持當(dāng)前事務(wù)泪酱,如果當(dāng)前沒有事務(wù),就以非事務(wù)方式執(zhí)行还最。
PROPAGATION_MANDATORY -- 支持當(dāng)前事務(wù)墓阀,如果當(dāng)前沒有事務(wù),就拋出異常拓轻。
PROPAGATION_REQUIRES_NEW -- 新建事務(wù)斯撮,如果當(dāng)前存在事務(wù),把當(dāng)前事務(wù)掛起扶叉。
PROPAGATION_NOT_SUPPORTED -- 以非事務(wù)方式執(zhí)行操作吮成,如果當(dāng)前存在事務(wù),就把當(dāng)前事務(wù)掛起辜梳。
PROPAGATION_NEVER -- 以非事務(wù)方式執(zhí)行,如果當(dāng)前存在事務(wù)泳叠,則拋出異常作瞄。
PROPAGATION_NESTED
-- 如果當(dāng)前存在事務(wù),則在嵌套事務(wù)內(nèi)執(zhí)行危纫。如果當(dāng)前沒有事務(wù)宗挥,則進(jìn)行與PROPAGATION_REQUIRED類似的操作。
前六個(gè)策略類似于EJB CMT种蝶,第七個(gè)(PROPAGATION_NESTED)是Spring所提供的一個(gè)特殊變量契耿。
它要求事務(wù)管理器或者使用JDBC 3.0 Savepoint API提供嵌套事務(wù)行為(如Spring的DataSourceTransactionManager)
舉例淺析Spring嵌套事務(wù)
ServiceA {
void methodA() {
ServiceB.methodB();
}
}
ServiceB {
void methodB() {
}
}
1、PROPAGATION_REQUIRED
假如當(dāng)前正要執(zhí)行的事務(wù)不在另外一個(gè)事務(wù)里螃征,那么就起一個(gè)新的事務(wù)
比如說搪桂,ServiceB.methodB的事務(wù)級(jí)別定義為PROPAGATION_REQUIRED, 那么由于執(zhí)行ServiceA.methodA的時(shí)候
(1)、如果ServiceA.methodA已經(jīng)起了事務(wù),這時(shí)調(diào)用ServiceB.methodB踢械,ServiceB.methodB看到自己已經(jīng)運(yùn)行在ServiceA.methodA的事務(wù)內(nèi)部酗电,就不再起新的事務(wù)。這時(shí)只有外部事務(wù)并且他們是共用的内列,所以這時(shí)ServiceA.methodA或者ServiceB.methodB無論哪個(gè)發(fā)生異常methodA和methodB作為一個(gè)整體都將一起回滾
撵术。
(2)、如果ServiceA.methodA沒有事務(wù)话瞧,ServiceB.methodB就會(huì)為自己分配一個(gè)事務(wù)嫩与。這樣,在ServiceA.methodA中是沒有事務(wù)控制的交排。只是在ServiceB.methodB內(nèi)的任何地方出現(xiàn)異常划滋,ServiceB.methodB將會(huì)被回滾,不會(huì)引起ServiceA.methodA的回滾
2个粱、PROPAGATION_SUPPORTS
如果當(dāng)前在事務(wù)中古毛,即以事務(wù)的形式運(yùn)行,如果當(dāng)前不再一個(gè)事務(wù)中都许,那么就以非事務(wù)的形式運(yùn)行 稻薇。
3、PROPAGATION_MANDATORY
必須在一個(gè)事務(wù)中運(yùn)行胶征。也就是說塞椎,他只能被一個(gè)父事務(wù)調(diào)用。否則睛低,他就要拋出異常
4案狠、PROPAGATION_REQUIRES_NEW
啟動(dòng)一個(gè)新的, 不依賴于環(huán)境的 "內(nèi)部" 事務(wù). 這個(gè)事務(wù)將被完全 commited 或 rolled back 而不依賴于外部事務(wù), 它擁有自己的隔離范圍, 自己的鎖, 等等. 當(dāng)內(nèi)部事務(wù)開始執(zhí)行時(shí), 外部事務(wù)將被掛起, 內(nèi)務(wù)事務(wù)結(jié)束時(shí), 外部事務(wù)將繼續(xù)執(zhí)行.
比如我們?cè)O(shè)計(jì)ServiceA.methodA的事務(wù)級(jí)別為PROPAGATION_REQUIRED,ServiceB.methodB的事務(wù)級(jí)別為PROPAGATION_REQUIRES_NEW钱雷,那么當(dāng)執(zhí)行到ServiceB.methodB的時(shí)候骂铁,ServiceA.methodA所在的事務(wù)就會(huì)掛起,ServiceB.methodB會(huì)起一個(gè)新的事務(wù)罩抗,等待ServiceB.methodB的事務(wù)完成以后拉庵,他才繼續(xù)執(zhí)行。他與PROPAGATION_REQUIRED 的事務(wù)區(qū)別在于事務(wù)的回滾程度了套蒂。因?yàn)镾erviceB.methodB是新起一個(gè)事務(wù)钞支,那么就是存在兩個(gè)不同的事務(wù)。
(1)操刀、如果ServiceB.methodB已經(jīng)提交烁挟,那么ServiceA.methodA失敗回滾,ServiceB.methodB是不會(huì)回滾的骨坑。
(2)撼嗓、如果ServiceB.methodB失敗回滾,如果他拋出的異常被ServiceA.methodA的try..catch捕獲并處理,ServiceA.methodA事務(wù)仍然可能提交静稻;如果他拋出的異常未被ServiceA.methodA捕獲處理警没,ServiceA.methodA事務(wù)將回滾。
場(chǎng)景: 不管業(yè)務(wù)邏輯的service是否有異常振湾,Log Service都應(yīng)該能夠記錄成功杀迹,所以Log Service的傳播屬性可以配為此屬性。最下面將會(huì)貼出配置代碼押搪。
5树酪、PROPAGATION_NOT_SUPPORTED
當(dāng)前不支持事務(wù)。比如ServiceA.methodA的事務(wù)級(jí)別是PROPAGATION_REQUIRED 大州,而ServiceB.methodB的事務(wù)級(jí)別是PROPAGATION_NOT_SUPPORTED 续语,那么當(dāng)執(zhí)行到ServiceB.methodB時(shí),ServiceA.methodA的事務(wù)掛起厦画,而他以非事務(wù)的狀態(tài)運(yùn)行完疮茄,再繼續(xù)ServiceA.methodA的事務(wù)。
6根暑、PROPAGATION_NEVER
不能在事務(wù)中運(yùn)行力试。假設(shè)ServiceA.methodA的事務(wù)級(jí)別是PROPAGATION_REQUIRED, 而ServiceB.methodB的事務(wù)級(jí)別是PROPAGATION_NEVER 排嫌,那么ServiceB.methodB就要拋出異常了畸裳。
7、PROPAGATION_NESTED (特殊)
開始一個(gè) "嵌套的" 事務(wù), 它是已經(jīng)存在事務(wù)的一個(gè)真正的子事務(wù). 潛套事務(wù)開始執(zhí)行時(shí), 它將取得一個(gè) savepoint. 如果這個(gè)嵌套事務(wù)失敗, 我們將回滾到此 savepoint. 潛套事務(wù)是外部事務(wù)的一部分, 只有外部事務(wù)結(jié)束后它才會(huì)被提交.
比如我們?cè)O(shè)計(jì)ServiceA.methodA的事務(wù)級(jí)別為PROPAGATION_REQUIRED淳地,ServiceB.methodB的事務(wù)級(jí)別為PROPAGATION_NESTED怖糊,那么當(dāng)執(zhí)行到ServiceB.methodB的時(shí)候,ServiceA.methodA所在的事務(wù)就會(huì)掛起颇象,ServiceB.methodB會(huì)起一個(gè)新的子事務(wù)并設(shè)置savepoint伍伤,等待ServiceB.methodB的事務(wù)完成以后,他才繼續(xù)執(zhí)行遣钳。嚷缭。因?yàn)镾erviceB.methodB是外部事務(wù)的子事務(wù),那么
1耍贾、如果ServiceB.methodB已經(jīng)提交,那么ServiceA.methodA失敗回滾路幸,ServiceB.methodB也將回滾荐开。
2、如果ServiceB.methodB失敗回滾简肴,如果他拋出的異常被ServiceA.methodA的try..catch捕獲并處理晃听,ServiceA.methodA事務(wù)仍然可能提交;如果他拋出的異常未被ServiceA.methodA捕獲處理,ServiceA.methodA事務(wù)將回滾能扒。
理解Nested的關(guān)鍵是savepoint佣渴。他與PROPAGATION_REQUIRES_NEW的區(qū)別是:
PROPAGATION_REQUIRES_NEW 完全是一個(gè)新的事務(wù),它與外部事務(wù)相互獨(dú)立; 而 PROPAGATION_NESTED 則是外部事務(wù)的子事務(wù), 如果外部事務(wù) commit, 嵌套事務(wù)也會(huì)被 commit, 這個(gè)規(guī)則同樣適用于 roll back.
在 spring 中使用 PROPAGATION_NESTED的前提:
1. 我們要設(shè)置 transactionManager 的 nestedTransactionAllowed 屬性為 true, 注意, 此屬性默認(rèn)為 false!!!
2. java.sql.Savepoint 必須存在, 即 jdk 版本要 1.4+
3. Connection.getMetaData().supportsSavepoints() 必須為 true, 即 jdbc drive 必須支持 JDBC 3.0
確保以上條件都滿足后, 你就可以嘗試使用 PROPAGATION_NESTED 了.
隔離級(jí)別(Isolation Level)
1初斑、Serializable:最嚴(yán)格的級(jí)別辛润,事務(wù)串行執(zhí)行,資源消耗最大见秤;
2砂竖、REPEATABLE READ:保證了一個(gè)事務(wù)不會(huì)修改已經(jīng)由另一個(gè)事務(wù)讀取但未提交(回滾)的數(shù)據(jù)。避免了“臟讀取”和“不可重復(fù)讀取”的情況鹃答,但是帶來了更多的性能損失乎澄。
3、READ COMMITTED:大多數(shù)主流數(shù)據(jù)庫的默認(rèn)事務(wù)等級(jí)测摔,保證了一個(gè)事務(wù)不會(huì)讀到另一個(gè)并行事務(wù)已修改但未提交的數(shù)據(jù)置济,避免了“臟讀取”。該級(jí)別適用于大多數(shù)系統(tǒng)锋八。
4浙于、Read Uncommitted:保證了讀取過程中不會(huì)讀取到非法數(shù)據(jù)。隔離級(jí)別在于處理多事務(wù)的并發(fā)問題查库。
我們知道并行可以提高數(shù)據(jù)庫的吞吐量和效率路媚,但是并不是所有的并發(fā)事務(wù)都可以并發(fā)運(yùn)行,這需要查看數(shù)據(jù)庫教材的可串行化條件判斷了樊销。
我們首先說并發(fā)中可能發(fā)生的3中不討人喜歡的事情
1: Dirty reads--讀臟數(shù)據(jù)整慎。也就是說,比如事務(wù)A的未提交(還依然緩存)的數(shù)據(jù)被事務(wù)B讀走围苫,如果事務(wù)A失敗回滾裤园,會(huì)導(dǎo)致事務(wù)B所讀取的的數(shù)據(jù)是錯(cuò)誤的。
2: non-repeatable reads--數(shù)據(jù)不可重復(fù)讀剂府。比如事務(wù)A中兩處讀取數(shù)據(jù)-total-的值拧揽。在第一讀的時(shí)候,total是100腺占,然后事務(wù)B就把total的數(shù)據(jù)改成 200淤袜,事務(wù)A再讀一次,結(jié)果就發(fā)現(xiàn)衰伯,total竟然就變成200了铡羡,造成事務(wù)A數(shù)據(jù)混亂。
3: phantom reads--幻象讀數(shù)據(jù)意鲸,這個(gè)和non-repeatable reads相似烦周,也是同一個(gè)事務(wù)中多次讀不一致的問題尽爆。但是non-repeatable reads的不一致是因?yàn)樗〉臄?shù)據(jù)集被改變了(比如total的數(shù)據(jù)),但是phantom reads所要讀的數(shù)據(jù)的不一致卻不是他所要讀的數(shù)據(jù)集改變读慎,而是他的條件數(shù)據(jù)集改變漱贱。比如Select account.id where account.name="ppgogo*",第一次讀去了6個(gè)符合條件的id,第二次讀取的時(shí)候夭委,由于事務(wù)b把一個(gè)帳號(hào)的名字由"dd"改成"ppgogo1"幅狮,結(jié)果取出來了7個(gè)數(shù)據(jù)。
數(shù)據(jù)庫提供了四種事務(wù)隔離級(jí)別, 不同的隔離級(jí)別采用不同的鎖類開來實(shí)現(xiàn).
在四種隔離級(jí)別中, Serializable的級(jí)別最高, Read Uncommited級(jí)別最低.
大多數(shù)數(shù)據(jù)庫的默認(rèn)隔離級(jí)別為: Read Commited,如Sql Server , Oracle.
少數(shù)數(shù)據(jù)庫默認(rèn)的隔離級(jí)別為Repeatable Read, 如MySQL InnoDB存儲(chǔ)引擎
3闰靴、Transactional注意點(diǎn)
Transactional特性
-
service實(shí)現(xiàn)類標(biāo)簽
在service實(shí)現(xiàn)類 類頭(一般不建議在接口上)上添加@Transactional彪笼,可以將整個(gè)類納入spring事務(wù)管理,在每個(gè)業(yè)務(wù)方法執(zhí)行時(shí)都會(huì)開啟一個(gè)事務(wù)蚂且,不過這些事務(wù)采用相同的管理方式配猫。 -
可見度
@Transactional 注解只能應(yīng)用到 public 可見度的方法上。 如果應(yīng)用在protected杏死、private或者 package可見度的方法上泵肄,也不會(huì)報(bào)錯(cuò),不過事務(wù)設(shè)置不會(huì)起作用淑翼。 -
回滾
默認(rèn)情況下腐巢,spring會(huì)對(duì)unchecked異常進(jìn)行事務(wù)回滾;如果是checked異常則不回滾玄括。
通俗一點(diǎn):你寫代碼出現(xiàn)的空指針等異常冯丙,會(huì)被回滾,文件讀寫遭京,網(wǎng)絡(luò)出問題胃惜,spring就沒法回滾了。
java里面將派生于Error或者RuntimeException(比如空指針哪雕,1/0)的異常稱為unchecked異常船殉,其他繼承自java.lang.Exception得異常統(tǒng)稱為Checked Exception,如IOException斯嚎、TimeoutException
-
只讀事務(wù)
只讀標(biāo)志只在事務(wù)啟動(dòng)時(shí)應(yīng)用利虫,否則即使配置也會(huì)被忽略。
啟動(dòng)事務(wù)會(huì)增加線程開銷堡僻,數(shù)據(jù)庫因共享讀取而鎖定(具體跟數(shù)據(jù)庫類型和事務(wù)隔離級(jí)別有關(guān))糠惫。通常情況下,僅是讀取數(shù)據(jù)時(shí)钉疫,不必設(shè)置只讀事務(wù)而增加額外的系統(tǒng)開銷硼讽。
解決Transactional不回滾
1、檢查方法是否是public的陌选。
2理郑、異常類型是否是unchecked異常
如果想check異常也想回滾怎么辦,注解上面寫明異常類型即可
@Transactional(rollbackFor=Exception.class)
3咨油、spring是否開啟對(duì)注解的解析
- @EnableTransactionManagement
- 還有例如SpringDataJPA 事務(wù)容器聲明:
transactionManager(JpaTransactionManager) -> entityManagerFactory(EntityManagerFactory) -> dataSource
4您炉、spring是否掃描到包
5、數(shù)據(jù)庫引擎是否支持事務(wù)
事務(wù)綁定事件@TransactionalEventListener
1役电、 使用DEMO
@Service
public class TransactionEventTestService {
@Resource
private TestMapper mapper;
@Resource
private ApplicationEventPublisher publisher;
@Transactional
public void addTestModel() {
TestModel model = new TestModel();
model.setName("haogrgr");
mapper.insert(model);
//如果model沒有繼承ApplicationEvent, 則內(nèi)部會(huì)包裝為PayloadApplicationEvent
//對(duì)于@TransactionalEventListener, 會(huì)在事務(wù)提交后才執(zhí)行Listener處理邏輯.
//發(fā)布事件, 事務(wù)提交后, 記錄日志, 或發(fā)送消息等操作
publisher.publishEvent(model);
}
//當(dāng)事務(wù)提交后, 才會(huì)真正的執(zhí)行@TransactionalEventListener配置的Listener, 如果Listener拋異常, 方法返回失敗, 但事務(wù)不會(huì)回滾.
}
@Component
public class TransactionEventListener {
@TransactionalEventListener
public void handle(PayloadApplicationEvent<TestModel> event) {
System.out.println(event.getPayload().getName());
//這里可以記錄日志, 發(fā)送消息等操作.
//這里拋出異常, 會(huì)導(dǎo)致addTestModel方法異常, 但不會(huì)回滾事務(wù).
//注意, ApplicationEventPublisher不能使用線程池, 否則不會(huì)執(zhí)行到這里
//因?yàn)? 包裝類是通過ThreadLocal來判斷當(dāng)前是否有活動(dòng)的事務(wù)信息.
//TransactionalEventListener.fallbackExecution就是為了決定當(dāng)當(dāng)前線程沒有事務(wù)上下文時(shí),
//是否還調(diào)用 handle 方法, 默認(rèn)不調(diào)用.
}
}
2赚爵、TransactionalEventListener詳解
@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@EventListener
public @interface TransactionalEventListener {
// 指定當(dāng)前標(biāo)注方法處理事務(wù)的類型
TransactionPhase phase() default TransactionPhase.AFTER_COMMIT;
// 用于指定當(dāng)前方法如果沒有事務(wù),是否執(zhí)行相應(yīng)的事務(wù)事件監(jiān)聽器
boolean fallbackExecution() default false;
// 與classes屬性一樣法瑟,指定了當(dāng)前事件傳入的參數(shù)類型冀膝,指定了這個(gè)參數(shù)之后就可以在監(jiān)聽方法上
// 直接什么一個(gè)這個(gè)參數(shù)了
@AliasFor(annotation = EventListener.class, attribute = "classes")
Class<?>[] value() default {};
// 作用于value屬性一樣,用于指定當(dāng)前監(jiān)聽方法的參數(shù)類型
@AliasFor(annotation = EventListener.class, attribute = "classes")
Class<?>[] classes() default {};
// 這個(gè)屬性使用Spring Expression Language對(duì)目標(biāo)類和方法進(jìn)行匹配霎挟,對(duì)于不匹配的方法將會(huì)過濾掉
String condition() default "";
}
關(guān)于這里的classes屬性需要說明一下窝剖,如果指定了classes屬性,那么當(dāng)前監(jiān)聽方法的參數(shù)類型就可以直接使用所發(fā)布的事件的參數(shù)類型酥夭,如果沒有指定赐纱,那么這里監(jiān)聽的參數(shù)類型可以使用兩種:ApplicationEvent和PayloadApplicationEvent。對(duì)于ApplicationEvent類型的參數(shù)熬北,可以通過其getSource()方法獲取發(fā)布的事件參數(shù)疙描,只不過其返回值是一個(gè)Object類型的,如果想獲取具體的類型還需要進(jìn)行強(qiáng)轉(zhuǎn)讶隐;對(duì)于PayloadApplicationEvent類型起胰,其可以指定一個(gè)泛型參數(shù),該泛型參數(shù)必須與發(fā)布的事件的參數(shù)類型一致巫延,這樣就可以通過其getPayload()方法獲取事務(wù)事件發(fā)布的數(shù)據(jù)了效五。關(guān)于上述屬性中的TransactionPhase,其可以取如下幾個(gè)類型的值:
public enum TransactionPhase {
// 指定目標(biāo)方法在事務(wù)commit之前執(zhí)行
BEFORE_COMMIT,
// 指定目標(biāo)方法在事務(wù)commit之后執(zhí)行
AFTER_COMMIT,
// 指定目標(biāo)方法在事務(wù)rollback之后執(zhí)行
AFTER_ROLLBACK,
// 指定目標(biāo)方法在事務(wù)完成時(shí)執(zhí)行烈评,這里的完成是指無論事務(wù)是成功提交還是事務(wù)回滾了
AFTER_COMPLETION
}
如何通過程序判斷是否存在事務(wù)火俄?
boolean flag = TransactionSynchronizationManager.isActualTransactionActive();
在同一類中一個(gè)調(diào)用本類中另一個(gè)有事務(wù)的方法,事務(wù)是無效的 解決辦法
1、 將這部分業(yè)務(wù)代碼寫到另一個(gè)service中,然后注入調(diào)用
2讲冠、要調(diào)用代理類才會(huì)被切進(jìn)去
-
((TestService) SpringContextUtils.getBean("testService")).testTransactional2()
;
applicationContext 如何獲裙峡汀:
https://blog.csdn.net/u010784959/article/details/78892020
@Autowired
private ApplicationContext applicationContext;
applicationContext.getBean(TestService.class);
3、(推薦
)通過ThreadLocal暴露代理對(duì)象
- 第一步:開啟cglib代理竿开。spring.aop.proxy-target-class:true
- 第二步:@EnableAspectJAutoProxy(exposeProxy = true)
- 第三步:方法一中調(diào)用方法兒如:
((TestService)AopContext.currentProxy()).testTransactional2()
;
參考: