1. 程式人生 > >ssm框架中通過自定義異常實現對事務的管理

ssm框架中通過自定義異常實現對事務的管理

什麼時候回滾事務?

在spring的事務管理中我們首先要明白這個問題,一般是在丟擲執行期異常的時候會進行事務的回滾。而spring的宣告式事務管理只接受執行期異常。

異常通常分為執行期異常和編譯期異常。

在java中常見的執行期異常有:

NullPointerException - 空指標引用異常
ClassCastException - 型別強制轉換異常。
IndexOutOfBoundsException - 下標越界異常
NumberFormatException - 數字格式異常
IllegalArgumentException - 傳遞非法引數異常。
java.lang.NoSuchMethodError-方法不存在異常
java.sql.SQLException -Sql語句執行異常
java.io.IOException -輸入輸出異常

在java中對異常的處理方式通常有兩種:

A:當前方法明確知道該如何處理該異常,程式應該使用try-catch來捕捉該異常,然後再抵押的catch塊中修補該異常。
B:當前方法不知道如何處理這種異常,應該在定義該方法時宣告丟擲該異常。

但是執行期異常比較靈活,不需要手動的去try-catch,如果程式需要捕捉執行時異常,也可以通過try-catch塊來捕捉。

下面的一段程式碼就是我們自定義一個執行時異常:

package cn.codingxiaxw.exception;

/**
 * 重複秒殺異常,是一個執行期異常,不需要我們手動try catch
 * Mysql只支援執行期異常的回滾操作
 */
public class RepeatKillException extends SeckillException { public RepeatKillException(String message) { super(message); } public RepeatKillException(String message, Throwable cause) { super(message, cause); } }

可以看出這個重複秒殺的異常繼承了SeckillException,這個SeckillException也是我們自定義的異常:

package cn.codingxiaxw.exception;

/**
 * 秒殺相關的所有業務異常
 * Created by codingBoy on 16/11/27.
 */
public class SeckillException extends RuntimeException {
    public SeckillException(String message) {
        super(message);
    }

    public SeckillException(String message, Throwable cause) {
        super(message, cause);
    }
}

SeckillException 異常繼承了RuntimeException ,表明他是一個執行時異常,這裡定義了兩個構造方法,可以在丟擲異常時說明錯誤的資訊。

這裡使用RepeatKillException 繼承SeckillException 的好處就是,如果遇到重複秒殺的行為,我們可以丟擲RepeatKillException ,其他的異常可以丟擲SeckillException 。這樣對於重複秒殺這些需要特別注意的操作,我們丟擲異常時可以知道是因為重複秒殺而造成的。

再來看宣告式事務管理,他有三種使用方式:
① ProxyFactoryBean+XML的方式:這是早期的使用方式,現在使用的比較少。
② Tx:advice+aop名稱空間,這種配置一次配置永久生效
③ 通過註解@Transactional 。

這裡我們是通過註解的方式, 使用註解控制事務方法的優點:
1.開發團隊達成一致約定,明確標註事務方法的程式設計風格
2.保證事務方法的執行時間儘可能短,不要穿插其他網路操作RPC/HTTP請求或者剝離到事務方法外部
3.不是所有的方法都需要事務,如只有一條修改操作、只讀操作不要事務控制

這裡我們通過sql插入一條資料。

INSERT ignore INTO success_killed(seckill_id,user_phone,state)
        VALUES (#{seckillId},#{userPhone},0)

當出現主鍵衝突時(即重複秒殺時),會報錯;不想讓程式報錯,加入ignore。
我們可以根據執行insert語句返回的int型別的值進行異常處理。

@Transactional
    public SeckillExecution executeSeckill(long seckillId, long userPhone, String md5)
            throws SeckillException, RepeatKillException, SeckillCloseException {

        if (md5==null||!md5.equals(getMD5(seckillId)))
        {
            throw new SeckillException("seckill data rewrite");//秒殺資料被重寫了
        }
        //執行秒殺邏輯:減庫存+增加購買明細
        Date nowTime=new Date();

        try{

            //否則更新了庫存,秒殺成功,增加明細
            int insertCount=successKilledDao.insertSuccessKilled(seckillId,userPhone);
            //看是否該明細被重複插入,即使用者是否重複秒殺
            if (insertCount<=0)
            {
                throw new RepeatKillException("seckill repeated");
            }else {

                //減庫存,熱點商品競爭
                int updateCount=seckillDao.reduceNumber(seckillId,nowTime);
                if (updateCount<=0)
                {
                    //沒有更新庫存記錄,說明秒殺結束 rollback
                    throw new SeckillCloseException("seckill is closed");
                }else {
                    //秒殺成功,得到成功插入的明細記錄,並返回成功秒殺的資訊 commit
                    SuccessKilled successKilled=successKilledDao.queryByIdWithSeckill(seckillId,userPhone);
                    return new SeckillExecution(seckillId, SeckillStatEnum.SUCCESS,successKilled);
                }

            }


        }catch (SeckillCloseException e1)
        {
            throw e1;
        }catch (RepeatKillException e2)
        {
            throw e2;
        }catch (Exception e)
        {
            logger.error(e.getMessage(),e);
            //所以編譯期異常轉化為執行期異常
            throw new SeckillException("seckill inner error :"+e.getMessage());
        }

    }

最後執行插入的方法,返回值如果錯誤就會丟擲異常,從而事務回滾,實現對事務的控制。