事務回滾@ Transactional 詳談

在這裏插入圖片描述

前言:
事務回滾我們總是在用到,但是有可能不太瞭解具體的細節,接下來我會通過源碼解讀以及真實的案例測試,來說明。
接下來我會從三方面來講述事務的運用:

  1. 源碼解讀
  2. 事務使用
  3. 事務的失效場景

1.源碼解讀


/**
 描述事務的屬性在一個方法或者類上(個人覺得應該是使用事務的屬性在方法或者類上)
 *<p>
  這種註釋類型通常可以直接與Spring的註釋類型進行比較,
 實際上將直接將數據轉換爲後者,因此Spring的事務支持代碼不必知道註釋。 如果沒有規則與異常相關,則將其視爲回滾運行時異常
 */

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Transactional {

    /**
     * 主要是spring不提供事務管理器,可以通過這個去定義自己想要的事務管理器,例如hibernate的、JTA的、JDBC的
     * 大致上有以下幾種
     * org.springframework.jdbc.datasource.DataSourceTransactionManager  DBC及iBATIS、MyBatis框架事務管理器
     * org.springframework.orm.jdo.JdoTransactionManager Jdo事務管理器
     * org.springframework.orm.jpa.JpaTransactionManager Jpa事務管理器
     * org.springframework.orm.hibernate3.HibernateTransactionManager  hibernate事務管理器
     * org.springframework.transaction.jta.JtaTransactionManager Jta事務管理器
     */
    String value() default "";


    /**
     * * 事務傳播類型 默認是required 具體解釋如下:
     * @Transactional(propagation=Propagation.REQUIRED) :如果有事務, 那麼加入事務, 沒有的話新建一個(默認情況下)
     * @Transactional(propagation=Propagation.NOT_SUPPORTED) :容器不爲這個方法開啓事務
     * @Transactional(propagation=Propagation.REQUIRES_NEW) :不管是否存在事務,都創建一個新的事務,原來的掛起,新的執行完畢,繼續執行老的事務
     * @Transactional(propagation=Propagation.MANDATORY) :必須在一個已有的事務中執行,否則拋出異常
     * @Transactional(propagation=Propagation.NEVER) :必須在一個沒有的事務中執行,否則拋出異常(與Propagation.MANDATORY相反)
     * @Transactional(propagation=Propagation.SUPPORTS) :如果其他bean調用這個方法,在其他bean中聲明事務,那就用事務.如果其他bean沒有聲明事務,那就不用事務.
     * <p>
     */
    Propagation propagation() default Propagation.REQUIRED;
    
    
    /**
     * 事務的隔離機制 默認是數據庫的隔離機制 需要是innodb引擎數據庫纔可以
     * READ_UNCOMMITTED(TransactionDefinition.ISOLATION_READ_UNCOMMITTED),  	讀取未提交數據(會出現髒讀, 不可重複讀) 基本不使用
     * READ_COMMITTED(TransactionDefinition.ISOLATION_READ_COMMITTED), 讀取已提交數據(會出現不可重複讀和幻讀)
     * REPEATABLE_READ(TransactionDefinition.ISOLATION_REPEATABLE_READ), 可重複讀(會出現幻讀)
     * SERIALIZABLE(TransactionDefinition.ISOLATION_SERIALIZABLE); 串行化
     */
    Isolation isolation() default Isolation.DEFAULT;


    /*   
     * 事務超時時間 默認-1 永遠不超時 如果設置之後  達到時間自動回滾
     */
    int timeout() default TransactionDefinition.TIMEOUT_DEFAULT;


    /**
     * 設置是否只讀 默認是false  如果設置之後會鎖住庫 如果有插入的話 會報異常 java.sql.SQLException: Connection is read-only. Queries leading to data modification are not allowed
     */
    boolean readOnly() default false;

    /**
     * 回滾的異常 默認只能是回滾runtimeExceptional
     如果想全部回滾需要加上 rollback=exceptional.class
     * CheckedException不回滾:
     * Java認爲Checked異常都是可以被處理的異常,所以Java程序必須顯式的處理Checked異常,如果程序沒有處理checked異常,程序在編譯時候將發生錯誤。
     * 我們比較熟悉的Checked異常有
     * Java.lang.ClassNotFoundException
     * Java.lang.NoSuchMetodException
     * java.io.IOException
     * <p>
     * RunTimeException 回滾:
     * Runtime如除數是0和數組下標越界等,其產生頻繁,處理麻煩,若顯示申明或者捕獲將會對程序的可讀性和運行效率影響很大。所以由系統自動檢測並將它們交給缺省的異常處理程序。當然如果你有處理要求也可以顯示捕獲它們。
     * 我們比較熟悉的RumtimeException類的子類有
     * Java.lang.ArithmeticException
     * Java.lang.ArrayStoreExcetpion
     * Java.lang.ClassCastException
     * Java.lang.IndexOutOfBoundsException
     * Java.lang.NullPointerException
     *
     * @Transactional(rollbackFor=RuntimeException.class)
     * 指定多個異常類:@Transactional(rollbackFor={RuntimeException.class, Exception.class})
     */
    Class<? extends Throwable>[] rollbackFor() default {};



    /**
     * 這個是支持輸入的類名稱 詳細功能同上
     * @Transactional(rollbackForClassName="RuntimeException")
     * 指定多個異常類名稱:@Transactional(rollbackForClassName={"RuntimeException","Exception"})
     */
    String[] rollbackForClassName() default {};


    /**
     
     * 不會滾的異常可以在這設置 ,也可以自己實現異常 但是必須要繼承runtimeExceptional
     */
    Class<? extends Throwable>[] noRollbackFor() default {};


    /**
     *  同上 這個是支持輸入的類名稱  不會滾的異常可以在這設置 ,也可以自己實現異常 但是必須要繼承runtimeExceptional
     */
    String[] noRollbackForClassName() default {};

}


2.事務使用

(1). 一個方法中涉及兩次及以上重要插入,用事務來保證完整性 原子性
(2). 事務只能用在public 方法上 ,其他方法不起作用
(3). 可以放在ServiceImpl類上。全局實現事務
下面舉一個簡單的例子:

@Override
@Transactional(rollbackFor = Exception.class)
public int insert(DneWechat record) {

    record.setId(1);
    record.setName("hj12334");
    updateByPrimaryKey(record);
    dneWechatMapper.insert(record);

    insertMemberStudent();
    return 1;
}

 //該方法是被第二次調用的方法 如果拋出異常 則全部回滾
 //該方法可以不用寫@transectional 因爲事務是可以繼承的 
@Transactional(rollbackFor = Exception.class)
public void insertMemberStudent() {
    DneMemberStudent dneMemberStudent = DneMemberStudent.builder()
            .receivedTime(new Date())
            .expireTime(new Date())
            .userId(123)
            .build();
    dneMemberStudentService.insert(dneMemberStudent);
    throw new RuntimeException();
}

3.事務失效的幾種場景

一、try catch之後沒有拋出對應的異常,這樣是不會回滾的,錯誤例子如下:

@Override
    @Transactional(rollbackFor = Exception.class)
    public int insert(DneWechat record) {

        record.setId(1);
        record.setName("hj12334");
        updateByPrimaryKey(record);
        dneWechatMapper.insert(record);

        insertMemberStudent();
        return 1;
    }

    @Transactional(rollbackFor = Exception.class)
    public void insertMemberStudent() {
        DneMemberStudent dneMemberStudent = DneMemberStudent.builder()
                .receivedTime(new Date())
                .expireTime(new Date())
                .userId(123)
                .build();
        dneMemberStudentService.insert(dneMemberStudent);
        try {

            int a = 1 / 0;
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

二、 private 方法 不會回滾,錯誤例子如下:

 @Override
    @Transactional
    public int insert(DneWechat record) throws IOException {

        record.setId(1);
        record.setName("hj12334");
        updateByPrimaryKey(record);
        dneWechatMapper.insert(record);

        insertMemberStudent();

        return 1;
    }

    @Transactional(rollbackFor = Exception.class)
    private void insertMemberStudent() {
        DneMemberStudent dneMemberStudent = DneMemberStudent.builder()
                .receivedTime(new Date())
                .expireTime(new Date())
                .userId(123)
                .build();
        dneMemberStudentService.insert(dneMemberStudent);
    }

三、 檢查異常 例如ioexception 不會回滾,例子如下:

 @Override
    @Transactional
    public int insert(DneWechat record) throws IOException {

        record.setId(1);
        record.setName("hj12334");
        updateByPrimaryKey(record);
        dneWechatMapper.insert(record);

        insertMemberStudent();
            throw new IOException();

    }

    @Transactional(rollbackFor = Exception.class)
    public void insertMemberStudent() {
        DneMemberStudent dneMemberStudent = DneMemberStudent.builder()
                .receivedTime(new Date())
                .expireTime(new Date())
                .userId(123)
                .build();
        dneMemberStudentService.insert(dneMemberStudent);
    }

四、 被調用的方法有註解,Override 方法沒有註解,不會回滾,因爲註解注入是通過aop實現的,例子如下:

@Override
    public int insert(DneWechat record) {

        record.setId(1);
        record.setName("hj12334");
        updateByPrimaryKey(record);
        dneWechatMapper.insert(record);

        insertMemberStudent();
        return 1;
    }

    @Transactional(rollbackFor = Exception.class)
    public void insertMemberStudent() {
        DneMemberStudent dneMemberStudent = DneMemberStudent.builder()
                .receivedTime(new Date())
                .expireTime(new Date())
                .userId(123)
                .build();
        dneMemberStudentService.insert(dneMemberStudent);
        throw new RuntimeException();
    }

我等採石之人,當心懷大教堂之願景!
歡迎關注我的公衆號!!
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-H9IITAOx-1590484165920)(evernotecid://8EE5EC85-4ED7-4997-B090-651003AE80EE/appyinxiangcom/20163437/ENResource/p38)]

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章