1. 程式人生 > >實際springMVC專案中自定義異常、spring事務與異常的簡單應用

實際springMVC專案中自定義異常、spring事務與異常的簡單應用

一、異常相關知識:

  1. 非執行時異常(Checked Exception)
    Java中凡是繼承自Exception但不是繼承自RuntimeException的類都是非執行時異常。

  2. 執行時異常(Runtime Exception/Unchecked Exception)
    RuntimeException類直接繼承自Exception類,稱為執行時異常。Java中所有的執行時異常都直接或間接的繼承自RuntimeException。

3、Java中所有的異常類都直接或間接的繼承自Exception。

4、spring事務回滾機制只處理執行時異常,發生非執行時異常則不會回滾操作。

二、自定義異常的大體思路

1、通過上面異常相關知識介紹,在實際的springmvc專案中自定義異常採用繼承RuntimeException類的方式;

2、為了防止註解了spring事務的service方法中發生非執行其異常,導致的事務不回滾操作,service層觸發事務相關程式碼主動用try{}catch(){}包裹,最後的catch捕獲Exception異常(即包含了執行期和非執行期異常),
catch內部丟擲自定義的執行期異常,以達到把非執行其異常轉換為執行期異常的效果,保證所有異常spring事務都執行回滾操作。

三、自定義異常例項

自定義基類異常:

/**
 * 所有秒殺相關異常(執行期異常)
 * spring事務,只接收執行期異常,執行回滾操作
 */
public class SeckillException extends RuntimeException{ private static final long serialVersionUID = 1L; //過載建構函式 public SeckillException(String message, Throwable cause) { super(message, cause); } //過載建構函式 public SeckillException(String message) { super(message); } }

自定義兩個子類異常:

/**
 *  秒殺關閉異常:可能未開啟,可能已結束(時間到期,庫存為0),可能執行失敗等
 *  秒殺異常的子型別異常
 */
public class SeckillClosedException extends SeckillException{

    private static final long serialVersionUID = 1L;

    //過載建構函式
    public SeckillClosedException(String message, Throwable cause) {
        super(message, cause);
    }

    //過載建構函式
    public SeckillClosedException(String message) {
        super(message);
    }
}
/**
 *  重複秒殺異常,秒殺異常的子型別異常
 */
public class RepeatSeckillException extends SeckillException{

    private static final long serialVersionUID = 1L;

    //過載建構函式
    public RepeatSeckillException(String message){
        super(message);
    }

    //過載建構函式
    public RepeatSeckillException(String message,Throwable cause){
        super(message, cause);
    }
}

service層事務方法:

@Transactional
@Override
public SeckillExecution executeSeckill(。。。) throws SeckillException, SeckillClosedException,RepeatSeckillException {

    //將所有操作用try{}catch(){}包裹,然後在catch中丟擲執行期異常,以便發生異常時spring事務回滾操作
    try{
        //insert語句並沒有觸發事務操作,但是inset要保證與update事務的一致性
        int insertCount = successkilledDao.insertSuccessKilled(seckillId, userPhone);
        //重複秒殺
        if(insertCount <= 0){
            throw new RepeatSeckillException(SeckillStateEnum.REPEAT_KILL.getStateInfo());
        }else{

            //減庫存
            Date nowTime = new Date();
            //開啟事務,獲取updateCount值,提交併關閉事務;如果丟擲異常則回滾事務
            int updateCount = seckillDao.reduceNumber(seckillId, nowTime);
            //沒有更新資料,秒殺結束
            if(updateCount  <= 0){
                throw new SeckillClosedException(SeckillStateEnum.END.getStateInfo());
            }else{
                //秒殺成功,獲取當前購買明細實體
                SuccessKilled successKilled = successkilledDao.querySuccessKilledWithSeckill(seckillId, userPhone);
                return new SeckillExecution(seckillId,SeckillStateEnum.SUCCESS,successKilled);
            }
        }

    //將try中丟擲的已知異常用已知異常捕獲,然後再次丟擲
    //為的是避免被最後的用於處理未知異常的Exception e捕獲,讓呼叫者獲取最為準確的資訊
    }catch(SeckillClosedException se){
        logger.error(se.getMessage());
        throw se;
    }catch(RepeatSeckillException re){
        logger.error(re.getMessage());
        throw re;

    //最後捕獲所有未知異常(相對於上面兩個已知異常),然後再主動丟擲自定義執行期異常
    }catch(Exception e){
        logger.error(e.getMessage(),e);
        //將檢查(編譯期)異常轉換為執行期異常,spring事務回滾只負責執行期異常
        throw new SeckillException(SeckillStateEnum.INSERT_ERROR.getStateInfo()+" : "+e.getMessage());
    }
}

上述方式的弊端是,如果業務線比較多,自定義的異常子類也會比較多,完全可以定義為一個通用的UncheckedException異常,如下:

/**
 * @Description:通用的UncheckedException異常,繼承RuntimeException,為執行期異常
 */
public class UncheckedException extends RuntimeException {
    private static final long serialVersionUID = 1L;
    /** 錯誤Key,用於唯一標識錯誤型別 */
    private String errorCode = null;
    /** 錯誤資訊 */
    private String errorMessage;
    /** 傳遞給變數的錯誤值 */
    private Object[] errorParam = null;

    /**
     * 建構函式
     * @param errorCode 異常編碼
     */
    public UncheckedException(String errorCode) {
        this.errorCode = errorCode;
    }

    /**
     * 建構函式
     * @param errorCode 異常編碼
     * @param errorParam Object[] 異常資訊用到的引數
     */
    public UncheckedException(String errorCode, Object[] errorParam) {
        this.errorCode = errorCode;
        this.errorParam = errorParam;
    }

    /**
     * 過載建構函式
     * @param errorCode 異常編碼
     * @param errorParam 異常資訊用到的引數
     * @param t 異常例項
     */
    public UncheckedException(String errorCode, Object[] errorParam, Throwable t) {
        super(t);
        this.errorCode = errorCode;
        this.errorParam = errorParam;
    }

    /**
     * 過載建構函式
     * @param message 異常資訊
     * @param t 異常例項
     */
    public UncheckedException(String message, Throwable t) {
        super(message, t);
        setErrorMessage(message);
    }



    /**
     * 異常編碼
     * @return String
     */
    public String getErrorCode() {
        return this.errorCode;
    }

    /**
     * 異常資訊用到的引數
     * @return Object[]
     */
    public Object[] getErrorParam() {
        return this.errorParam;
    }

    /**
     * 錯誤資訊
     * 
     * @return
     */
    public String getErrorMessage() {
        return errorMessage;
    }

    /**
     * 錯誤資訊
     * 
     * @param errorMessage
     */
    public void setErrorMessage(String errorMessage) {
        this.errorMessage = errorMessage;
    }

    /**
     * 覆蓋方法:getMessage
     * @return String
     */
    @Override
    public String getMessage() {
        if (errorMessage != null) {
            return errorMessage;
        }

        //異常資訊以資原始檔的形式儲存,並且支援國際化,此處通過errorCode去讀取國際化異常資訊
        if (errorCode != null && !errorCode.trim().equals("")) {
            setErrorMessage(AppLang.getLU().getMessage(errorCode, errorParam,Locale.SIMPLIFIED_CHINESE));
        }

        return getErrorMessage();
    }
}