web开发的基本框架分为Dao、Service、Web三层,Dao层主要解决的是跟数据库之间的交互以及数据库的增删改查功能的实现。因为所有功能是面向数据库实现的,因此数据库的开发是Dao层开发的基础。
一个秒杀系统的数据库表单应该有两张:商品的内存表,秒杀成功的用户信息记录表。 商品的内存表设计:
CREATE DATABASE seckill; USE seckill; CREATE TABLE seckill ( `seckill_id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '秒杀id', `name` VARCHAR(120) NOT NULL COMMENT '秒杀商品名称', `number` INT NOT NULL COMMENT '商品数量', `start_time` TIMESTAMP NOT NULL COMMENT '秒杀开始时间', `end_time` TIMESTAMP NOT NULL COMMENT '秒杀结束时间', `creat_time` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '秒杀创建时间', PRIMARY KEY (seckill_id), KEY idx_start_time (start_time), KEY idx_end_time (end_time), KEY idx_create_time (creat_time) ) ENGINE InnoDB AUTO_INCREMENT = 1000 DEFAULT CHARSET = utf8 COMMENT = '库存秒杀表';因为我们的秒杀系统对高并发是有要求的,因此使用InnoDB引擎,该引擎支持事务管理。 该表的主键是seckill_id,使用了自增特性,并且从1000开始。
秒杀用户记录表设计
CREATE TABLE success_killed ( `seckill_id` BIGINT NOT NULL COMMENT '秒杀商品ID', `user_phone` BIGINT NOT NULL COMMENT '用户手机号', `state` TINYINT NOT NULL DEFAULT -1 COMMENT '用户订单状态 1表示已付款 0表示未付款 -1表示无效', `creat_time` TIMESTAMP NOT NULL COMMENT '用户订单创建时间', PRIMARY KEY (seckill_id, user_phone), KEY (creat_time) ) ENGINE = InnoDB DEFAULT CHARSET utf8 COMMENT = '秒杀成功表单';这里要注意,因为我们为了避免一个用户多次秒杀一个商品,使用user_phone以及seckill_id联合主键。
设计完表单之后,对秒杀商品库存进行初始化。
INSERT INTO seckill(name, number, start_time, end_time) VALUES ('1000元秒杀iphone6', 100, '2019-11-01 00:00:00', '2019-11-02 00:00:00'), ('500元秒杀ipad2', 200, '2019-11-01 00:00:00', '2019-11-02 00:00:00'), ('300元秒杀小米4', 300, '2019-11-01 00:00:00', '2019-11-02 00:00:00'), ('200元秒杀红米note', 400, '2019-11-01 00:00:00', '2019-11-02 00:00:00');实现JAVA与数据库之间的数据交互,需要面向数据库的列开发Entity类,开发的标准是,Entity类的属性对应数据库的列。所以我们的Entity也应有两个类:秒杀库存类及秒杀用户信息类。
秒杀库存类:
public class Seckill { private long seckillId; private String name; private long number; private Date createTime; private Date startTime; private Date endTime; public long getSeckillId() { return seckillId; } public void setSeckillId(long seckillId) { this.seckillId = seckillId; } public String getName() { return name; } public void setName(String name) { this.name = name; } public long getNumber() { return number; } public void setNumber(long number) { this.number = number; } public Date getStartTime() { return startTime; } public void setStartTime(Date startTime) { this.startTime = startTime; } public Date getCreateTime() { return createTime; } public void setCreateTime(Date createTime) { this.createTime = createTime; } @Override public String toString() { return "Seckill{" + "seckillId=" + seckillId + ", name='" + name + '\'' + ", number=" + number + ", createTime=" + createTime + ", startTime=" + startTime + ", endTime=" + endTime + '}'; } }用户信息类
public class SuccessKilled { private long seckillId; private long userPhone; private Date createTime; private short state; private Seckill seckill; @Override public String toString() { return "SuccessKilled{" + "seckillId=" + seckillId + ", userPhone=" + userPhone + ", createTime=" + createTime + ", state=" + state + '}'; } public long getSeckillId() { return seckillId; } public void setSeckillId(long seckillId) { this.seckillId = seckillId; } public long getUserPhone() { return userPhone; } public void setUserPhone(long userPhone) { this.userPhone = userPhone; } public Date getCreateTime() { return createTime; } public void setCreateTime(Date createTime) { this.createTime = createTime; } public short getState() { return state; } public void setState(short state) { this.state = state; } public Seckill getSeckill() { return seckill; } public void setSeckill(Seckill seckill) { this.seckill = seckill; } }这里有一点需要强调,在success_killed实体类里面包涵了seckill类。
使用mybatis开发dao层的一个特点就是mybatis会通过动态代理帮助我们直接实现dao的实现类,开发者在开发过程中只需要负责SQL语言以及接口设计。
public interface SeckillDao { /** * 传入ID以及秒杀时间减库存 * * @param seckillId * @param killTime * @return */ int reduceNumber(@Param("seckillId") long seckillId, @Param("killTime") Date killTime); /** * 使用ID查询库存 * * @param seckillId * @return */ Seckill queryById(long seckillId); /** * 使用窗口查询所有秒杀对象 * * @param offset * @param limit * @return */ List<Seckill> queryAll(@Param( "offset") int offset, @Param("limit") int limit); }这里需要注意几点: 一、接口设计要合理,依靠以下标准: 1.接口名要明确,可以通过接口名指导该接口实现的效果。 2.接口的参数设计明确。 二、在接口参数处添加的注解,是为了告诉Mybatis该处的参数在Mapper中对应的是哪一个参数。
public interface SuccessKilledDao { /** * 插入秒杀成功对象 * @param seckillId * @param userPhone * @return */ int insertSuccessKilled(@Param("seckillId") long seckillId, @Param("userPhone") long userPhone); /** * 使用ID查询秒杀成功对象并携带秒杀对象实体 * @param seckillId * @return */ SuccessKilled queryByIdWithSeckill(@Param("seckillId") long seckillId,@Param("userPhone")long userPhone); }这里的queryByIdWithSeckill方法,返回的是SuccessKilled实体,但是因为在SuccesssKilled实体中我们携带了seckill实体。因此在Mapper设计中我们要表示出这一点。