實際springMVC專案中自定義異常、spring事務與異常的簡單應用
阿新 • • 發佈:2019-02-07
一、異常相關知識:
非執行時異常(Checked Exception)
Java中凡是繼承自Exception但不是繼承自RuntimeException的類都是非執行時異常。執行時異常(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();
}
}