秒杀系统Service层设计

来源:互联网 发布:高清网络播放机 编辑:程序博客网 时间:2024/05/16 10:10

文章关注是架构设计和一些以前学习时没有理解的点,具体代码需参考慕课网相关教程。

一、Service接口和实现类
在J2EE工程中,service层一般负责接收servlet从前端获取的数据,并进行数据的初步处理(组装成查询条件),将其扔给DAO层去处理,得到的结果交到servlet中。由servlet返回给前端,利用c标签、JQuery、Ajax等进行数据展示处理。
service包和serviceImpl包:对应接口定义、接口实现。

提出几个问题,复习完项目代码后进行回答:
1. 定义接口时,该如何去考虑方法需要什么参数?
站在接口调用者的角度去设计接口,接口需要的参数应该简单直白,尽量避免扔一个map进去,然后key-value取值。返回类型同理。例如,秒杀系统只在规定的时间暴露出秒杀接口的链接,因此本程序设计了一个DTO对象exposer,封装了待暴露的秒杀接口,而不是采用一个map然后让秒杀接口地址作为map的一个key。

/** * 暴露秒杀地址DTO *  */public class Exposer {    // 是否开启秒杀    private boolean exposed;    // 一种加密措施    private String md5;    // id    private long seckillId;    // 系统当前时间(毫秒)    private long now;    // 开启时间    private long start;    // 结束时间    private long end;    public Exposer(boolean exposed, String md5, long seckillId) {        this.exposed = exposed;        this.md5 = md5;        this.seckillId = seckillId;    }    public Exposer(boolean exposed, long seckillId, long now, long start,            long end) {        this.exposed = exposed;        this.seckillId = seckillId;        this.now = now;        this.start = start;        this.end = end;    }    public Exposer(boolean exposed, long seckillId) {        this.exposed = exposed;        this.seckillId = seckillId;    }
  1. 接口的实现类该关注哪些点
    serviceImpl类里的业务都需要通过dao层来操作数据库,因此在声明时要用@Autowired注入dao类的依赖作为成员变量。
    要实现一个业务,首先要考虑业务的逻辑。在本例中,执行一次秒杀业务,其成功逻辑过程应该是:
    1)验证MD5(使用Spring提供的工具类:DigestUtils.md5DigestAsHex())
    2)成功,减少库存(对seckill表进行update操作),并生成一个秒杀成功对象实体SuccessKilled(在successkilled表中插入一条数据)。
    3)如果当中发生重复秒杀、秒杀超时等错误,要主动抛出对应的异常,在catch语句块中捕获,让Spring进行事务回滚(运行期异常才会发生回滚)。
    4)将秒杀商品id、秒杀成功记录、秒杀状态标识等信息封装成一个DTO交给前端web层。
    5)可以先把大的控制结构写好,再去解决具体细节。
@Transactionalpublic SeckillExecution excuteSeckill(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);            // 数据库中的唯一记录:(seckillId, userPhone)            if (insertCount <= 0) {                // 重复秒杀                throw new RepeatKillException("seckill repeated");            } else {                // 减库存,热点商品竞争(rowLock出现的地方)                int updateCount = seckillDao.reduceNumber(seckillId, nowTime);                if (updateCount <= 0) {                    // 没有更新记录,秒杀结束,rollback                    throw new SeckillCloseException("seckill is closed");                } else {                    // 秒杀成功,commit                    SuccessKilled secKilled = successKilledDao.queryByIdWithSeckill(seckillId, userPhone);                    // 使用秒杀成功的构造函数                    return new SeckillExecution(seckillId, SeckillStatEnum.SUCCESS, secKilled);                }            }        } 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());        }    }
  1. service层在Spring中如何配置生效
    1)创建一个spring-service.xml文件,内容如下:
    <!-- 扫描service包下所有使用注解的类型 -->    <context:component-scan base-package="org.seckill.service"></context:component-scan>

2)用注解的方式将Service的实现类加入到Spring IOC容器中:

@Servicepublic class SeckillServiceImpl implements SeckillService {...}

二、DTO层设计
DTO包:数据传输层对象(和entity区别,entity是和业务数据库存储相关的,而dto关注web和service之间的数据传递)

三、异常exception设计
自定义异常exception包:封装和业务相关的具体异常,如重复秒杀异常、秒杀关闭异常等。和Spring的事务结合使用。

Java的异常分为两大类:Checked异常和Runtime异常。除了RuntimeException类和其子类的实例之外,别的异常都称为Checked异常。Spring的事务管理处理的是Runtime异常,所以在打了@Transactional注解的方法体,要把Checked异常捕获之后,主动throw new RuntimeException(实际上是继承RuntimeException类的某个自定义异常类)。
四、枚举类的应用
程序一般都会用自己的数据字典,数据字典可放在枚举当中,在代码里不要用中文硬编码。便于管理,保持代码整洁。

public enum SeckillStatEnum {    SUCCESS(1, "秒杀成功"), END(0, "秒杀结束"), REPEAT_KILL(-1, "重复秒杀"), INNER_ERROR(            -2, "系统异常"), DATA_REWRITE(-3, "数据篡改");    private int state;    private String stateInfo;    private SeckillStatEnum(int state, String stateInfo) {        this.state = state;        this.stateInfo = stateInfo;    }}

五、Spring声明式事务
1)spring配置文件中写

<!-- 配置事务管理器 -->    <bean id="transactionManager"        class="org.springframework.jdbc.datasource.DataSourceTransactionManager">        <!-- 注入数据库的连接池 -->        <property name="dataSource" ref="dataSource" />    </bean>    <!-- 配置基于注解的声明式事务 -->       <tx:annotation-driven transaction-manager="transactionManager" />

2)service实现类的方法中:

@Transactional    /**     * 使用注解控制事务方法的优点: 1:开发团队达成一致约定,明确标注事务方法的编程风格。     * 2:保证事务方法的执行时间尽可能短,不要穿插其他网络操作,RPC(操作缓存)/HTTP请求或者剥离到事务方法外部.     * 3:不是所有的方法都需要事务,如只有一条修改操作,只读操作不需要事务控制.(参考MySql行级锁)     */    public SeckillExecution excuteSeckill(long seckillId, long userPhone, String md5)            throws SeckillException, RepeatKillException, SeckillCloseException {...}

六、Service层的Junit的集成测试
在需要测试的类名上,new-other-junit,生成对应的测试类。编写测试方法。

七、额外的知识补充——MySQL的行级锁

0 0