SSM实现高并发秒杀功能之DAO层

一、该秒杀实现哪些功能

1.列出秒杀的商品
2.秒杀接口的暴露(到了秒杀的时间,把秒杀的地址暴露出来)
3.执行秒杀
4.相关查询(查询秒杀成功的商品等)

二、使用的技术

前端:Bootstrap,jquery,Ajax
IOC容器:Spring
Web框架:SpringMVC
ORM框架:Mybatis
日志:slf4j + logback
数据库:MySQL
项目构建工具:maven

三、代码开发的流程

1.DAO层设计编码
2.Service层设计编码
3.Web层设计编码
4.优化

四、DAO层的设计(使用Mybatis)

10.png

(1)数据表的设计

共创建了两张表,分别是秒杀的库存表seckill,秒杀成功的明细表success_killed

-- 创建秒杀库存表
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 '秒杀结束时间',
    `create_time` timestamp not null default current_timestamp() comment '创建时间',
    primary key(`seckill_id`),
    key index_start_time(`start_time`),
    key index_end_time(`end_time`),
    key index_create_time(`create_time`)
)ENGINE=InnoDB auto_increment=1000 default charset=utf8 comment='秒杀库存表';

-- 初始化数据
insert into seckill(name,number,start_time,end_time) values
    ('1000元秒杀iPhoneX',100,'2018-06-13 00:00:00','2018-06-13 01:00:00'),
    ('800元秒杀ipad3',100,'2018-06-13 00:00:00','2018-06-13 01:00:00'),
    ('500元秒杀AirPods',100,'2018-06-13 00:00:00','2018-06-13 01:00:00'),
    ('200元秒杀小米8',100,'2018-06-13 00:00:00','2018-06-13 01:00:00');


-- 秒杀成功的明细表
-- 用户登录认证的相关信息
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表示已付款,2表示已发货',
    `create_time` timestamp not null comment '创建时间',
    primary key(`seckill_id`,`user_phone`),
    key index_create_time(`create_time`)
)ENGINE=InnoDB default charset=utf8 comment='秒杀成功的明细表';

(2)DAO实体和接口的编码

/**
 * 对应数据库中秒杀的库存表
 * @author liu
 */
public class Seckill {
    // 库存商品的id
    private long seckillId;
    // 库存商品的名称
    private String name;
    // 库存商品的数量
    private int number;
    // 秒杀开始时间
    private Date startTime;
    // 秒杀结束时间
    private Date endTime;
    // 创建时间
    private Date createTime;
......set方法和get方法
}
/**
 * 对应数据库中秒杀成功的明细表
 * @author liu
 */
public class SuccessKilled {
    // 秒杀商品的id
    private long seckillId;
    // 秒杀人的手机号
    private long userPhone;
    // 秒杀状态
    private short state;
    // 创建时间
    private Date createTime;

    // 多对一
    private Seckill seckill;
......set方法和get方法
}

相应的接口

public interface SeckillDao {
    /**
     * 秒杀成功后库存减少
     * @param seckillId 商品的id
     * @param killTime 秒杀的时间
     * 注解@Param是参数校正
     * @return 商品减少的数量
     */
    int reduceNumber(@Param("seckillId") long seckillId, @Param("killTime") Date killTime);

    /**
     * 根据id查询商品
     * @param seckillId 商品的id
     * @return 对应的商品
     */
    Seckill queryById(long seckillId);

    /**
     * 根据偏移量查询商品的列表
     * @param offset
     * @param limit
     * @return
     */
    List<Seckill> queryAll(@Param("offset") int offset, @Param("limit") int limit);
}
public interface SuccessKilledDao {
    /**
     * 秒杀成功后插入一条记录,seckillId和userPhone一起构成了主键,能防止同一个人秒杀多次
     * @param seckillId 秒杀成功的商品id
     * @param userPhone 秒杀人的手机号
     * @return 插入的行数
     */
    int insertSuccessKilled(@Param("seckillId") long seckillId, @Param("userPhone") long userPhone);

    /**
     * 根据商品的id查询SuccessKilled,SuccessKilled携带Seckill
     * @param seckillId
     * @param userPhone
     * @return
     */
    SuccessKilled queryByIdWithSeckill(@Param("seckillId") long seckillId, @Param("userPhone") long userPhone);
}

五、遇到的错误

(1)junit测试时报initializationError,除了这个没有其他任何的提示。

解决办法:首先得确保你的代码没有问题,比如方法上加了@Test,方法的修饰符为public void并且没有参数,如果代码没有问题,那就是junit jar的问题,我换成junit5后问题解决。

(2)java.lang.IllegalStateException: Failed to load ApplicationContext

紧接着测试又报这个错误,查看控制台的错误提示(只要给了错误提示,就一定有解决的办法,就怕什么提示都没有)
error3.png

发现是手误把password写错了

(3)Loading class ‘com.mysql.jdbc.Driver’. This is deprecated. The new driver class is `com.mysql.cj.jdb

按照要求改好,错误消失。这个其实不改也能跑,只是会有警告。

(4)java.sql.SQLException: The server time zone value ‘�й���׼ʱ��’ is unrecognize…

一开始我的jdbc的url写成了下面的形式

jdbc.url=jdbc:mysql://127.0.0.1:3306/seckill?useUnicode=true&characterEncoding=utf-8

连接jdbc的url改成如下

jdbc.url=jdbc:mysql://127.0.0.1:3306/seckill?useUnicode=true&characterEncoding=utf-8&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=UTC

错误消失,我记得我之前一直写成第一种形式,没报过错。

上面3和4出现错误的原因是因为我使用的mysql-connector-java是 6.0.6版本,6版本以上的driver和url必须写成上面这样的形式才行。

error4.png

出现错误不要紧,只要它有提示,肯定就有解决的办法,另外很多时候写的快了,就会有一些手误,这个也得慢慢排查。

(5)org.mybatis.spring.MyBatisSystemException: nested exception is org.apache.ibatis.binding.BindingException: Parameter ‘seckillId’ not found. Available parameters are [arg1, arg0, param1, param2]

接口中的方法有两个参数以上时,使用@Param注解进行参数校正
error5.png

(6)错误如下图
error6.png

(7)mybatis接口不能注入

error7.png

6和7的原因是我建测试类的时候选错了类型

error9.png

得选框框里的,不能选右边那个。。。。太丢人了。

六、总结

发现自己编码的能力太差了。。。好多小毛病,太粗心了。

可以看到我们和数据库交互时,并不像之前用jdbc一样写大量的代码,我们只需要写接口和在配置文件里写sql语句即可,大大减轻了我们的工作量,但同时配置文件也比较多,搞不好就容易出错。这是框架的好处也是它的坏处。同时我们只需要写接口,并不用给出它的实现类,这样也减少了我们维护的成本。

同时也可以看到,这里我们总共执行了5条sql语句

  • 查询所有的秒杀商品

  • 根据商品id查询相应的秒杀商品

  • 秒杀成功后,相应的秒杀商品数量减1

  • 秒杀成功后,插入一条秒杀成功的记录

  • 根据商品id查询秒杀成功的记录

猜你喜欢

转载自blog.csdn.net/a_helloword/article/details/80721320