秒杀系统Service层设计

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

一、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)可以先把大的控制结构写好,再去解决具体细节。
@Transactional
public 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容器中:

@Service
public 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
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值