1. 程式人生 > >Spring事務總結---傳播級別以及REQUIRED_NEW及NESTED的使用場景

Spring事務總結---傳播級別以及REQUIRED_NEW及NESTED的使用場景

三、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呼叫,而是方法的直接呼叫,所以也就沒有檢查該方法是否含有事務這個過程,那麼他生命的事務也就不成立了。

    另外,除了這個解決方法,開濤大神那個部落格還提供了其他解決方式,可以根據自己的需求選擇。