事務(wù)的嵌套概念
所謂事務(wù)的嵌套就是兩個事務(wù)方法之間相互調(diào)用。spring事務(wù)開啟 ,或者是基于接口的或者是基于類的代理被創(chuàng)建(注意一定要是代理,不能手動new 一個對象,并且此類(有無接口都行)一定要被代理——spring中的bean只要納入了IOC管理都是被代理的)。所以在同一個類中一個方法調(diào)用另一個方法有事務(wù)的方法,事務(wù)是不會起作用的。
事務(wù)異常回滾
Spring默認(rèn)情況下會對運(yùn)行期例外(RunTimeException),即uncheck異常,進(jìn)行事務(wù)回滾。
如果遇到checked異常就不回滾。
如何改變默認(rèn)規(guī)則
- 讓checked例外也回滾:在整個方法前加上 @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ù),就新建一個事務(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類似的操作。
前六個策略類似于EJB CMT,第七個(PROPAGATION_NESTED)是Spring所提供的一個特殊變量。
它要求事務(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ù)不在另外一個事務(wù)里,那么就起一個新的事務(wù)
比如說,ServiceB.methodB的事務(wù)級別定義為PROPAGATION_REQUIRED, 那么由于執(zhí)行ServiceA.methodA的時候
(1)、如果ServiceA.methodA已經(jīng)起了事務(wù),這時調(diào)用ServiceB.methodB,ServiceB.methodB看到自己已經(jīng)運(yùn)行在ServiceA.methodA的事務(wù)內(nèi)部,就不再起新的事務(wù)。這時只有外部事務(wù)并且他們是共用的,所以這時ServiceA.methodA或者ServiceB.methodB無論哪個發(fā)生異常methodA和methodB作為一個整體都將一起回滾
。
(2)、如果ServiceA.methodA沒有事務(wù),ServiceB.methodB就會為自己分配一個事務(wù)。這樣,在ServiceA.methodA中是沒有事務(wù)控制的。只是在ServiceB.methodB內(nèi)的任何地方出現(xiàn)異常,ServiceB.methodB將會被回滾,不會引起ServiceA.methodA的回滾
2、PROPAGATION_SUPPORTS
如果當(dāng)前在事務(wù)中,即以事務(wù)的形式運(yùn)行,如果當(dāng)前不再一個事務(wù)中,那么就以非事務(wù)的形式運(yùn)行 。
3、PROPAGATION_MANDATORY
必須在一個事務(wù)中運(yùn)行。也就是說,他只能被一個父事務(wù)調(diào)用。否則,他就要拋出異常
4、PROPAGATION_REQUIRES_NEW
啟動一個新的, 不依賴于環(huán)境的 "內(nèi)部" 事務(wù). 這個事務(wù)將被完全 commited 或 rolled back 而不依賴于外部事務(wù), 它擁有自己的隔離范圍, 自己的鎖, 等等. 當(dāng)內(nèi)部事務(wù)開始執(zhí)行時, 外部事務(wù)將被掛起, 內(nèi)務(wù)事務(wù)結(jié)束時, 外部事務(wù)將繼續(xù)執(zhí)行.
比如我們設(shè)計ServiceA.methodA的事務(wù)級別為PROPAGATION_REQUIRED,ServiceB.methodB的事務(wù)級別為PROPAGATION_REQUIRES_NEW,那么當(dāng)執(zhí)行到ServiceB.methodB的時候,ServiceA.methodA所在的事務(wù)就會掛起,ServiceB.methodB會起一個新的事務(wù),等待ServiceB.methodB的事務(wù)完成以后,他才繼續(xù)執(zhí)行。他與PROPAGATION_REQUIRED 的事務(wù)區(qū)別在于事務(wù)的回滾程度了。因?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ù)將回滾。
場景: 不管業(yè)務(wù)邏輯的service是否有異常,Log Service都應(yīng)該能夠記錄成功,所以Log Service的傳播屬性可以配為此屬性。最下面將會貼出配置代碼。
5、PROPAGATION_NOT_SUPPORTED
當(dāng)前不支持事務(wù)。比如ServiceA.methodA的事務(wù)級別是PROPAGATION_REQUIRED ,而ServiceB.methodB的事務(wù)級別是PROPAGATION_NOT_SUPPORTED ,那么當(dāng)執(zhí)行到ServiceB.methodB時,ServiceA.methodA的事務(wù)掛起,而他以非事務(wù)的狀態(tài)運(yùn)行完,再繼續(xù)ServiceA.methodA的事務(wù)。
6、PROPAGATION_NEVER
不能在事務(wù)中運(yùn)行。假設(shè)ServiceA.methodA的事務(wù)級別是PROPAGATION_REQUIRED, 而ServiceB.methodB的事務(wù)級別是PROPAGATION_NEVER ,那么ServiceB.methodB就要拋出異常了。
7、PROPAGATION_NESTED (特殊)
開始一個 "嵌套的" 事務(wù), 它是已經(jīng)存在事務(wù)的一個真正的子事務(wù). 潛套事務(wù)開始執(zhí)行時, 它將取得一個 savepoint. 如果這個嵌套事務(wù)失敗, 我們將回滾到此 savepoint. 潛套事務(wù)是外部事務(wù)的一部分, 只有外部事務(wù)結(jié)束后它才會被提交.
比如我們設(shè)計ServiceA.methodA的事務(wù)級別為PROPAGATION_REQUIRED,ServiceB.methodB的事務(wù)級別為PROPAGATION_NESTED,那么當(dāng)執(zhí)行到ServiceB.methodB的時候,ServiceA.methodA所在的事務(wù)就會掛起,ServiceB.methodB會起一個新的子事務(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 完全是一個新的事務(wù),它與外部事務(wù)相互獨(dú)立; 而 PROPAGATION_NESTED 則是外部事務(wù)的子事務(wù), 如果外部事務(wù) commit, 嵌套事務(wù)也會被 commit, 這個規(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 了.
隔離級別(Isolation Level)
1、Serializable:最嚴(yán)格的級別,事務(wù)串行執(zhí)行,資源消耗最大;
2、REPEATABLE READ:保證了一個事務(wù)不會修改已經(jīng)由另一個事務(wù)讀取但未提交(回滾)的數(shù)據(jù)。避免了“臟讀取”和“不可重復(fù)讀取”的情況,但是帶來了更多的性能損失。
3、READ COMMITTED:大多數(shù)主流數(shù)據(jù)庫的默認(rèn)事務(wù)等級,保證了一個事務(wù)不會讀到另一個并行事務(wù)已修改但未提交的數(shù)據(jù),避免了“臟讀取”。該級別適用于大多數(shù)系統(tǒng)。
4、Read Uncommitted:保證了讀取過程中不會讀取到非法數(shù)據(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失敗回滾,會導(dǎo)致事務(wù)B所讀取的的數(shù)據(jù)是錯誤的。
2: non-repeatable reads--數(shù)據(jù)不可重復(fù)讀。比如事務(wù)A中兩處讀取數(shù)據(jù)-total-的值。在第一讀的時候,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ù),這個和non-repeatable reads相似,也是同一個事務(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個符合條件的id,第二次讀取的時候,由于事務(wù)b把一個帳號的名字由"dd"改成"ppgogo1",結(jié)果取出來了7個數(shù)據(jù)。
數(shù)據(jù)庫提供了四種事務(wù)隔離級別, 不同的隔離級別采用不同的鎖類開來實(shí)現(xiàn).
在四種隔離級別中, Serializable的級別最高, Read Uncommited級別最低.
大多數(shù)數(shù)據(jù)庫的默認(rèn)隔離級別為: Read Commited,如Sql Server , Oracle.
少數(shù)數(shù)據(jù)庫默認(rèn)的隔離級別為Repeatable Read, 如MySQL InnoDB存儲引擎
3、Transactional注意點(diǎn)
Transactional特性
-
service實(shí)現(xiàn)類標(biāo)簽
在service實(shí)現(xiàn)類 類頭(一般不建議在接口上)上添加@Transactional,可以將整個類納入spring事務(wù)管理,在每個業(yè)務(wù)方法執(zhí)行時都會開啟一個事務(wù),不過這些事務(wù)采用相同的管理方式。 -
可見度
@Transactional 注解只能應(yīng)用到 public 可見度的方法上。 如果應(yīng)用在protected、private或者 package可見度的方法上,也不會報錯,不過事務(wù)設(shè)置不會起作用。 -
回滾
默認(rèn)情況下,spring會對unchecked異常進(jìn)行事務(wù)回滾;如果是checked異常則不回滾。
通俗一點(diǎn):你寫代碼出現(xiàn)的空指針等異常,會被回滾,文件讀寫,網(wǎng)絡(luò)出問題,spring就沒法回滾了。
java里面將派生于Error或者RuntimeException(比如空指針,1/0)的異常稱為unchecked異常,其他繼承自java.lang.Exception得異常統(tǒng)稱為Checked Exception,如IOException、TimeoutException
-
只讀事務(wù)
只讀標(biāo)志只在事務(wù)啟動時應(yīng)用,否則即使配置也會被忽略。
啟動事務(wù)會增加線程開銷,數(shù)據(jù)庫因共享讀取而鎖定(具體跟數(shù)據(jù)庫類型和事務(wù)隔離級別有關(guān))。通常情況下,僅是讀取數(shù)據(jù)時,不必設(shè)置只讀事務(wù)而增加額外的系統(tǒng)開銷。
解決Transactional不回滾
1、檢查方法是否是public的。
2、異常類型是否是unchecked異常
如果想check異常也想回滾怎么辦,注解上面寫明異常類型即可
@Transactional(rollbackFor=Exception.class)
3、spring是否開啟對注解的解析
- @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)部會包裝為PayloadApplicationEvent
//對于@TransactionalEventListener, 會在事務(wù)提交后才執(zhí)行Listener處理邏輯.
//發(fā)布事件, 事務(wù)提交后, 記錄日志, 或發(fā)送消息等操作
publisher.publishEvent(model);
}
//當(dāng)事務(wù)提交后, 才會真正的執(zhí)行@TransactionalEventListener配置的Listener, 如果Listener拋異常, 方法返回失敗, 但事務(wù)不會回滾.
}
@Component
public class TransactionEventListener {
@TransactionalEventListener
public void handle(PayloadApplicationEvent<TestModel> event) {
System.out.println(event.getPayload().getName());
//這里可以記錄日志, 發(fā)送消息等操作.
//這里拋出異常, 會導(dǎo)致addTestModel方法異常, 但不會回滾事務(wù).
//注意, ApplicationEventPublisher不能使用線程池, 否則不會執(zhí)行到這里
//因?yàn)? 包裝類是通過ThreadLocal來判斷當(dāng)前是否有活動的事務(wù)信息.
//TransactionalEventListener.fallbackExecution就是為了決定當(dāng)當(dāng)前線程沒有事務(wù)上下文時,
//是否還調(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ù)類型,指定了這個參數(shù)之后就可以在監(jiān)聽方法上
// 直接什么一個這個參數(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 {};
// 這個屬性使用Spring Expression Language對目標(biāo)類和方法進(jìn)行匹配,對于不匹配的方法將會過濾掉
String condition() default "";
}
關(guān)于這里的classes屬性需要說明一下,如果指定了classes屬性,那么當(dāng)前監(jiān)聽方法的參數(shù)類型就可以直接使用所發(fā)布的事件的參數(shù)類型,如果沒有指定,那么這里監(jiān)聽的參數(shù)類型可以使用兩種:ApplicationEvent和PayloadApplicationEvent。對于ApplicationEvent類型的參數(shù),可以通過其getSource()方法獲取發(fā)布的事件參數(shù),只不過其返回值是一個Object類型的,如果想獲取具體的類型還需要進(jìn)行強(qiáng)轉(zhuǎn);對于PayloadApplicationEvent類型,其可以指定一個泛型參數(shù),該泛型參數(shù)必須與發(fā)布的事件的參數(shù)類型一致,這樣就可以通過其getPayload()方法獲取事務(wù)事件發(fā)布的數(shù)據(jù)了。關(guān)于上述屬性中的TransactionPhase,其可以取如下幾個類型的值:
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ù)完成時執(zhí)行,這里的完成是指無論事務(wù)是成功提交還是事務(wù)回滾了
AFTER_COMPLETION
}
如何通過程序判斷是否存在事務(wù)?
boolean flag = TransactionSynchronizationManager.isActualTransactionActive();
在同一類中一個調(diào)用本類中另一個有事務(wù)的方法,事務(wù)是無效的 解決辦法
1、 將這部分業(yè)務(wù)代碼寫到另一個service中,然后注入調(diào)用
2、要調(diào)用代理類才會被切進(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暴露代理對象
- 第一步:開啟cglib代理。spring.aop.proxy-target-class:true
- 第二步:@EnableAspectJAutoProxy(exposeProxy = true)
- 第三步:方法一中調(diào)用方法兒如:
((TestService)AopContext.currentProxy()).testTransactional2()
;
參考: