Spring事務總結---傳播級別以及REQUIRED_NEW及NESTED的使用場景
阿新 • • 發佈:2018-12-25
三、Spring事務的傳播性與隔離級別
Spring它對JDBC的隔離級別作出了補充和擴充套件,其提供了7種事務傳播行為。(通俗解釋原址)
1、PROPAGATION_REQUIRED:預設事務型別,如果沒有,就新建一個事務;如果有,就加入當前事務。適合絕大多數情況。
2、PROPAGATION_REQUIRES_NEW:如果沒有,就新建一個事務;如果有,就將當前事務掛起。
3、PROPAGATION_NESTED:如果沒有,就新建一個事務;如果有,就在當前事務中巢狀其他事務。
4、PROPAGATION_SUPPORTS:如果沒有,就以非事務方式執行;如果有,就使用當前事務。
5、PROPAGATION_NOT_SUPPORTED:如果沒有,就以非事務方式執行;如果有,就將當前事務掛起。即無論如何不支援事務。
6、PROPAGATION_NEVER:如果沒有,就以非事務方式執行;如果有,就丟擲異常。
7、PROPAGATION_MANDATORY:如果沒有,就丟擲異常;如果有,就使用當前事務。
第4、5、6、7種特性很好理解了,主要是前三種特性比較容易混淆或用錯。
那麼PROPAGATION_REQUIRED、PROPAGATION_REQUIRES_NEW、PROPAGATION_NESTED的區別在哪呢?
什麼有就建立一個什麼巢狀掛起,很明顯不如一些使用場景清晰,那就直接上例子。
先定義一些實驗性的方法。(例子程式碼:https://git.oschina.net/sluggarddd/spring-tx-demo.git)
@Service
public class IUserServiceImpl implements IUserService {
@Resource
IUserDAO userDAO;
@Resource
IUserService2 userService2;
//不帶事務的方法
public void funNone() throws Exception {
save(new UserEntity("zhw"));
}
//啟動預設事務的方法
@Transactional(propagation = Propagation.REQUIRED)
public void funRequire() throws Exception {
save(new UserEntity("wlj"));
}
//啟動預設事務的方法
@Transactional(propagation = Propagation.REQUIRED)
public void funRequire2() throws Exception {
save(new UserEntity("shifang"));
}
//啟動預設事務的方法,丟擲RuntimeException
@Override
@Transactional(propagation = Propagation.REQUIRED)
public void funRequireException() throws Exception {
save(new UserEntity("max"));
throwExcp();
}
//啟動巢狀事務的方法
@Transactional(propagation = Propagation.NESTED)
public void funNest() throws Exception {
save(new UserEntity("yunxinghe"));
}
//啟動巢狀事務的方法,但會丟擲異常
@Override
@Transactional(propagation = Propagation.NESTED)
public void funNestException() throws Exception {
save(new UserEntity("edward"));
throwExcp();
}
//REQUIRES_NEW事務的方法
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void funRequireNew() throws Exception {
save(new UserEntity("kb"));
}
//REQUIRES_NEW事務的方法,但會丟擲異常
@Override
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void funRequireNewException() throws Exception {
save(new UserEntity("laura"));
throwExcp();
}
//丟擲異常
private void throwExcp() throws Exception {
throw new RuntimeException("boom");
}
//儲存資料
public int save(UserEntity userEntity) throws Exception {
userDAO.save(userEntity);
return userEntity.getId();
}
}
我們定義了兩個映象(就是一樣的Serivce)分別是UserService,UserService2,是一樣的,只是相互呼叫,不重複上程式碼。
@Override
@Transactional
public void fun1() throws Exception {
//資料庫操作
funNone();
//呼叫另一個service的方法
userService2.funNest();
//當呼叫另一個Service的method的時候,想要將他的事務加到現在這個事務中,很可能自然而然想到了巢狀
//這麼想就錯了,Required的定義裡已經說明了如果沒有,就新建一個事務;如果有,就加入當前事務。
//那麼直接使用Required就滿足需求
//這樣在方法中任何地方發生unchecked異常將觸發整個方法的回滾
//而Nested的使用場景下面再介紹
}
NESTED事務使用場景
@Override
@Transactional
public void fun2() throws Exception {
//巢狀事務的使用場景
funNone();
try {
//當所呼叫的方法為NESTED事務,該事務的回滾可以不影響到呼叫者的事務
//當然如果沒有catch exception,異常冒泡而出,就將觸發呼叫者事務的回滾
userService2.funNestException();
} catch (Exception e) {
//do something
}
userService2.funRequire();
}
//執行結果:
//userService2.funNestException()被回滾
//其他插入成功
外部的異常能觸發所呼叫的NESTED事務回滾
@Override
@Transactional
public void fun3() throws Exception {
//巢狀事務的使用場景
funNone();
try {
//呼叫的事務為NESTED事務的方法
userService2.funNest();
} catch (Exception e) {
//do something
}
userService2.funRequire();
//此時在呼叫者處,觸發一個unchecked異常
throwExcp();
//此時會發現包括呼叫的userService2.funNest()也被回滾了
//也就是說,當呼叫的方法是NESTED事務,該方法丟擲異常如果得到了處理(try-catch),那麼該方法發生異常不會觸發整個方法的回滾
//而呼叫者出現unchecked異常,卻能觸發所呼叫的nested事務的回滾.
}
//執行結果
//全部被回滾
REQUIRES_NEW的使用場景
@Override
@Transactional
public void fun4() throws Exception {
//而REQUIRES_NEW,當被呼叫時,就相當於暫停(掛起)當前事務,先開一個新的事務去執行REQUIRES_NEW的方法,如果REQUIRES_NEW中的異常得到了處理
//那麼他將不影響呼叫者的事務,同時,呼叫者之後出現了異常,同樣也不會影響之前呼叫的REQUIRES_NEW方法的事務.
//不會回滾
funNone();
try {
//當異常得到處理,外部不會觸發回滾
userService2.funRequireNewException();
} catch (Exception e) {
}
}
//執行結果
//funNone()正常持久化
// userService2.funRequireNewException()回滾
@Override
@Transactional
public void fun5() throws Exception {
//資料庫操作
funNone();
//呼叫RequireNew型別事務的方法,呼叫者的異常回滾不會影響到它
userService2.funRequireNew();
//資料庫操作
funNone();
//丟擲unchecked異常,觸發回滾
throwExcp();
}
//執行結果
//userService2.funRequireNew();正常持久化
//其他操作被回滾
如果呼叫的是REQUIRED型別,即使處理了被呼叫方法丟擲的異常仍然會被回滾。
@Override
@Transactional
public void fun6() throws Exception {
funNone();
try {
//當呼叫的是Required時,就算異常被處理了,整個方法也將會回滾
userService2.funRequireException();
} catch (Exception e) {
System.out.println(e.getMessage());
}
}
//執行結果
//被回滾
總結:附上一段我覺得很好的總結(Jurgen Hoeller原話翻譯)(翻譯從這裡拷的)
PROPAGATION_REQUIRES_NEW 啟動一個新的, 不依賴於環境的 "內部" 事務. 這個事務將被完全 commited 或 rolled back 而不依賴於外部事務, 它擁有自己的隔離範圍, 自己的鎖, 等等. 當內部事務開始執行時, 外部事務將被掛起, 內務事務結束時, 外部事務將繼續執行.
另一方面, PROPAGATION_NESTED 開始一個 "巢狀的" 事務, 它是已經存在事務的一個真正的子事務. 潛套事務開始執行時, 它將取得一個 savepoint. 如果這個巢狀事務失敗, 我們將回滾到此 savepoint. 潛套事務是外部事務的一部分, 只有外部事務結束後它才會被提交.
由此可見, PROPAGATION_REQUIRES_NEW 和 PROPAGATION_NESTED 的最大區別在於, PROPAGATION_REQUIRES_NEW 完全是一個新的事務, 而 PROPAGATION_NESTED 則是外部事務的子事務, 如果外部事務 commit, 潛套事務也會被 commit, 這個規則同樣適用於 roll back.
四、Spring事務自我呼叫的坑
當Spring的事務在同一個類時,它的自我呼叫時事務就完犢子了!(不知道這個的時候被坑出翔)
當同一個類的方法之間事務發生自我呼叫,其事務的特性將失效。
@Override
@Transactional
public void fun7() throws Exception {
funRequire();
try {
//本應回滾這個方法,但發生了異常並沒有回滾
funNestException();
} catch (Exception e) {
System.out.println(e.getMessage());
}
funRequire();
}
例如上面Nested的特性就沒了,其執行結果是三個發生insert的語句都成功插入到資料庫了。
原因我自己肯定沒別人總結的好(連問題都說的不太清楚),就直接放連結了(原因點此),不想看的直接上解決方法。
1、首先引入
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>${aspectj.version}<ersion>
</dependency>
2、開啟暴露AOP代理到ThreadLocal支援
<aop:aspectj-autoproxy proxy-target-class="true" expose-proxy="true"/>
3、在自我呼叫的時候這麼寫
fun8() Exception {
((IUserService) AopContext.()).funRequire();
{
((IUserService) AopContext.()).funNestException();
} (Exception e) {
System..println(e.getMessage());
}
((IUserService) AopContext.()).funRequire();
}
這樣,就能讓配置在該方法上的事務發揮應有的特性啦。
原因我也來總結一句,因為開啟事務和事務回滾,實際這個過程是aop代理幫忙完成的,當呼叫一個方法時,他會先檢查時候有事務,有則開啟事務,當呼叫本類的方法是,他並沒有將其視為proxy呼叫,而是方法的直接呼叫,所以也就沒有檢查該方法是否含有事務這個過程,那麼他生命的事務也就不成立了。
另外,除了這個解決方法,開濤大神那個部落格還提供了其他解決方式,可以根據自己的需求選擇。
Spring它對JDBC的隔離級別作出了補充和擴充套件,其提供了7種事務傳播行為。(通俗解釋原址)
1、PROPAGATION_REQUIRED:預設事務型別,如果沒有,就新建一個事務;如果有,就加入當前事務。適合絕大多數情況。
2、PROPAGATION_REQUIRES_NEW:如果沒有,就新建一個事務;如果有,就將當前事務掛起。
3、PROPAGATION_NESTED:如果沒有,就新建一個事務;如果有,就在當前事務中巢狀其他事務。
4、PROPAGATION_SUPPORTS:如果沒有,就以非事務方式執行;如果有,就使用當前事務。
5、PROPAGATION_NOT_SUPPORTED:如果沒有,就以非事務方式執行;如果有,就將當前事務掛起。即無論如何不支援事務。
6、PROPAGATION_NEVER:如果沒有,就以非事務方式執行;如果有,就丟擲異常。
7、PROPAGATION_MANDATORY:如果沒有,就丟擲異常;如果有,就使用當前事務。
第4、5、6、7種特性很好理解了,主要是前三種特性比較容易混淆或用錯。
那麼PROPAGATION_REQUIRED、PROPAGATION_REQUIRES_NEW、PROPAGATION_NESTED的區別在哪呢?
什麼有就建立一個什麼巢狀掛起,很明顯不如一些使用場景清晰,那就直接上例子。
先定義一些實驗性的方法。(例子程式碼:https://git.oschina.net/sluggarddd/spring-tx-demo.git)
@Service
public class IUserServiceImpl implements IUserService {
@Resource
IUserDAO userDAO;
@Resource
IUserService2 userService2;
//不帶事務的方法
public void funNone() throws Exception {
save(new UserEntity("zhw"));
}
//啟動預設事務的方法
@Transactional(propagation = Propagation.REQUIRED)
public void funRequire() throws Exception {
save(new UserEntity("wlj"));
}
//啟動預設事務的方法
@Transactional(propagation = Propagation.REQUIRED)
public void funRequire2() throws Exception {
save(new UserEntity("shifang"));
}
//啟動預設事務的方法,丟擲RuntimeException
@Override
@Transactional(propagation = Propagation.REQUIRED)
public void funRequireException() throws Exception {
save(new UserEntity("max"));
throwExcp();
}
//啟動巢狀事務的方法
@Transactional(propagation = Propagation.NESTED)
public void funNest() throws Exception {
save(new UserEntity("yunxinghe"));
}
//啟動巢狀事務的方法,但會丟擲異常
@Override
@Transactional(propagation = Propagation.NESTED)
public void funNestException() throws Exception {
save(new UserEntity("edward"));
throwExcp();
}
//REQUIRES_NEW事務的方法
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void funRequireNew() throws Exception {
save(new UserEntity("kb"));
}
//REQUIRES_NEW事務的方法,但會丟擲異常
@Override
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void funRequireNewException() throws Exception {
save(new UserEntity("laura"));
throwExcp();
}
//丟擲異常
private void throwExcp() throws Exception {
throw new RuntimeException("boom");
}
//儲存資料
public int save(UserEntity userEntity) throws Exception {
userDAO.save(userEntity);
return userEntity.getId();
}
}
我們定義了兩個映象(就是一樣的Serivce)分別是UserService,UserService2,是一樣的,只是相互呼叫,不重複上程式碼。
@Override
@Transactional
public void fun1() throws Exception {
//資料庫操作
funNone();
//呼叫另一個service的方法
userService2.funNest();
//當呼叫另一個Service的method的時候,想要將他的事務加到現在這個事務中,很可能自然而然想到了巢狀
//這麼想就錯了,Required的定義裡已經說明了如果沒有,就新建一個事務;如果有,就加入當前事務。
//那麼直接使用Required就滿足需求
//這樣在方法中任何地方發生unchecked異常將觸發整個方法的回滾
//而Nested的使用場景下面再介紹
}
NESTED事務使用場景
@Override
@Transactional
public void fun2() throws Exception {
//巢狀事務的使用場景
funNone();
try {
//當所呼叫的方法為NESTED事務,該事務的回滾可以不影響到呼叫者的事務
//當然如果沒有catch exception,異常冒泡而出,就將觸發呼叫者事務的回滾
userService2.funNestException();
} catch (Exception e) {
//do something
}
userService2.funRequire();
}
//執行結果:
//userService2.funNestException()被回滾
//其他插入成功
外部的異常能觸發所呼叫的NESTED事務回滾
@Override
@Transactional
public void fun3() throws Exception {
//巢狀事務的使用場景
funNone();
try {
//呼叫的事務為NESTED事務的方法
userService2.funNest();
} catch (Exception e) {
//do something
}
userService2.funRequire();
//此時在呼叫者處,觸發一個unchecked異常
throwExcp();
//此時會發現包括呼叫的userService2.funNest()也被回滾了
//也就是說,當呼叫的方法是NESTED事務,該方法丟擲異常如果得到了處理(try-catch),那麼該方法發生異常不會觸發整個方法的回滾
//而呼叫者出現unchecked異常,卻能觸發所呼叫的nested事務的回滾.
}
//執行結果
//全部被回滾
REQUIRES_NEW的使用場景
@Override
@Transactional
public void fun4() throws Exception {
//而REQUIRES_NEW,當被呼叫時,就相當於暫停(掛起)當前事務,先開一個新的事務去執行REQUIRES_NEW的方法,如果REQUIRES_NEW中的異常得到了處理
//那麼他將不影響呼叫者的事務,同時,呼叫者之後出現了異常,同樣也不會影響之前呼叫的REQUIRES_NEW方法的事務.
//不會回滾
funNone();
try {
//當異常得到處理,外部不會觸發回滾
userService2.funRequireNewException();
} catch (Exception e) {
}
}
//執行結果
//funNone()正常持久化
// userService2.funRequireNewException()回滾
@Override
@Transactional
public void fun5() throws Exception {
//資料庫操作
funNone();
//呼叫RequireNew型別事務的方法,呼叫者的異常回滾不會影響到它
userService2.funRequireNew();
//資料庫操作
funNone();
//丟擲unchecked異常,觸發回滾
throwExcp();
}
//執行結果
//userService2.funRequireNew();正常持久化
//其他操作被回滾
如果呼叫的是REQUIRED型別,即使處理了被呼叫方法丟擲的異常仍然會被回滾。
@Override
@Transactional
public void fun6() throws Exception {
funNone();
try {
//當呼叫的是Required時,就算異常被處理了,整個方法也將會回滾
userService2.funRequireException();
} catch (Exception e) {
System.out.println(e.getMessage());
}
}
//執行結果
//被回滾
總結:附上一段我覺得很好的總結(Jurgen Hoeller原話翻譯)(翻譯從這裡拷的)
PROPAGATION_REQUIRES_NEW 啟動一個新的, 不依賴於環境的 "內部" 事務. 這個事務將被完全 commited 或 rolled back 而不依賴於外部事務, 它擁有自己的隔離範圍, 自己的鎖, 等等. 當內部事務開始執行時, 外部事務將被掛起, 內務事務結束時, 外部事務將繼續執行.
另一方面, PROPAGATION_NESTED 開始一個 "巢狀的" 事務, 它是已經存在事務的一個真正的子事務. 潛套事務開始執行時, 它將取得一個 savepoint. 如果這個巢狀事務失敗, 我們將回滾到此 savepoint. 潛套事務是外部事務的一部分, 只有外部事務結束後它才會被提交.
由此可見, PROPAGATION_REQUIRES_NEW 和 PROPAGATION_NESTED 的最大區別在於, PROPAGATION_REQUIRES_NEW 完全是一個新的事務, 而 PROPAGATION_NESTED 則是外部事務的子事務, 如果外部事務 commit, 潛套事務也會被 commit, 這個規則同樣適用於 roll back.
四、Spring事務自我呼叫的坑
當Spring的事務在同一個類時,它的自我呼叫時事務就完犢子了!(不知道這個的時候被坑出翔)
當同一個類的方法之間事務發生自我呼叫,其事務的特性將失效。
@Override
@Transactional
public void fun7() throws Exception {
funRequire();
try {
//本應回滾這個方法,但發生了異常並沒有回滾
funNestException();
} catch (Exception e) {
System.out.println(e.getMessage());
}
funRequire();
}
例如上面Nested的特性就沒了,其執行結果是三個發生insert的語句都成功插入到資料庫了。
原因我自己肯定沒別人總結的好(連問題都說的不太清楚),就直接放連結了(原因點此),不想看的直接上解決方法。
1、首先引入
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>${aspectj.version}<ersion>
</dependency>
2、開啟暴露AOP代理到ThreadLocal支援
<aop:aspectj-autoproxy proxy-target-class="true" expose-proxy="true"/>
3、在自我呼叫的時候這麼寫
fun8() Exception {
((IUserService) AopContext.()).funRequire();
{
((IUserService) AopContext.()).funNestException();
} (Exception e) {
System..println(e.getMessage());
}
((IUserService) AopContext.()).funRequire();
}
這樣,就能讓配置在該方法上的事務發揮應有的特性啦。
原因我也來總結一句,因為開啟事務和事務回滾,實際這個過程是aop代理幫忙完成的,當呼叫一個方法時,他會先檢查時候有事務,有則開啟事務,當呼叫本類的方法是,他並沒有將其視為proxy呼叫,而是方法的直接呼叫,所以也就沒有檢查該方法是否含有事務這個過程,那麼他生命的事務也就不成立了。
另外,除了這個解決方法,開濤大神那個部落格還提供了其他解決方式,可以根據自己的需求選擇。