ssm框架中通过自定义异常实现对事务的管理

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/nb7474/article/details/79730639

什么时候回滚事务?

在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());
        }

    }

最后执行插入的方法,返回值如果错误就会抛出异常,从而事务回滚,实现对事务的控制。

猜你喜欢

转载自blog.csdn.net/nb7474/article/details/79730639
今日推荐