秒杀系统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; }
- 接口的实现类该关注哪些点
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()); } }
- 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的行级锁
- 秒杀系统Service层设计
- 秒杀系统DAO层设计
- 秒杀系统Web层设计
- 秒杀系统设计
- 秒杀系统设计
- 三、高并发秒杀API之Service层设计与实现
- 基于Spring+SpringMVC+Mybatis的秒杀系统之Service层(2)
- JAVA高并发秒杀系统构建之——Service层
- 4.29 SSM项目实战(二)--秒杀系统api之Service层
- 秒杀系统设计实现
- 秒杀系统架构设计
- 秒杀系统设计详解
- 秒杀系统的设计
- 秒杀后台系统设计
- 秒杀系统设计优化
- 项目-设计秒杀系统
- 秒杀系统设计详解
- 秒杀系统设计-转载
- 二分图判定 (挑战程序设计竞赛)
- servlet生命周期及表单数据
- 制作 OpenStack Linux 镜像
- Density of Power Network ZOJ 3708
- SpringBoot学习:整合MyBatis,使用Druid连接池
- 秒杀系统Service层设计
- qnx的无人机方案
- com.microsoft.sqlserver.jdbc.SQLServerException: 到主机 的 TCP/IP 连接失败
- C十进制转二进制
- 最长子串
- php配置数据库连接机制
- 优先级调度:优先级高的任务先执行。
- 【洛谷 2296】寻找道路
- 字符串与编码