栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

Java秒杀系统一:环境搭建和DAO层设计

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

Java秒杀系统一:环境搭建和DAO层设计

本文为Java高并发秒杀API之业务分析与DAO层课程笔记。

文章目录
    • 介绍
    • 创建项目
    • 秒杀业务分析
      • 秒杀系统业务流程
      • MySQL实现秒杀难点分析
      • 秒杀功能
    • DAO设计编码
      • DAO实体和接口编码
      • MyBatis
        • 实现DAO编程
        • mybatis整合spring
        • XML SQL
      • junit单元测试
        • SecKillDaoTest.java

编辑器:IDEA

java版本:java8

介绍

学习目标:

秒杀业务:

  • 具有典型的“事务”特性

    事务的四大特性主要是:原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)、持久性(Durability)。 原子性是指事务是一个不可分割的工作单位,事务中的操作要么全部成功,要么全部失败

  • 需求越来越常见

  • 面试常问

前提内容(链接可点):J2EE、spring一、模拟一个简单的tomcat、SpringMVC和SSM、动态代理、IoC、AOP、SpringBoot一、SpringBoot二、Thymeleaf、Java反射三四例。

具体学习:

MySQL:表设计、SQL技巧、事务和行级锁。

MyBatis:DAO层设计开发、合理使用、与Spring整合。

Spring:IOC整合Service、声明式事务运用。

SpringMVC:Restful接口设计和使用、框架运作流程、Controller技巧。

前端:交互设计、Bootstrap、jQuery。

高并发:高并发点和分析、优化思路。

创建项目

创建得到目录如下:

下一步,右键项目名,添加java和resources、test文件夹,IDEA会给相应的提示:

之后目录为:

web.xml修改servlet版本:




pom.xml依赖配置,详见注释:


    
        
        junit
        junit
        4.11
        test
    
    
    
    
    
        org.slf4j
        slf4j-api
        1.7.12
    
    
        ch.qos.logback
        logback-core
        1.1.1
    
    
    
        ch.qos.logback
        logback-classic
        1.1.1
    

    
    
        mysql
        mysql-connector-java
        5.1.35
        
        runtime
    
    
    
        c3p0
        c3p0
        0.9.1.2
    

    
    
        org.mybatis
        mybatis
        3.5.2
    
    
    
        org.mybatis
        mybatis-spring
        1.3.0
    
    
    
    
        taglibs
        standard
        1.1.2
    
    
        jstl
        jstl
        1.2
    
    
    
        com.fasterxml.jackson.core
        jackson-databind
        2.8.10
    

    
        javax.servlet
        javax.servlet-api
        3.1.0
    

    
    
    
        org.springframework
        spring-core
        5.3.9
    

    
        org.springframework
        spring-beans
        5.3.9
    
    
    
        org.springframework
        spring-context
        5.3.9
    
    
    
        org.springframework
        spring-jdbc
        5.1.4.RELEASE
    
    
    
        org.springframework
        spring-tx
        5.1.4.RELEASE
    
    
    
        org.springframework
        spring-web
        5.3.9
    
    
        org.springframework
        spring-webmvc
        5.3.9
    
    
    
        org.springframework
        spring-test
        5.3.9
    

秒杀业务分析 秒杀系统业务流程

核心在于对库存的处理。

用户针对库存业务分析:

购买行为:谁买、成功的时间、付款和发货信息。

事务:就像转账一样,要么扣钱加钱同时成功要么同时失败。

数据落地:MySQL VS NoSQL(关系型数据库和非关系型数据库)

MySQL实现秒杀难点分析

竞争!

解决竞争背后技术:事务+行级锁。

行级锁:

一个用户A利用SQL语句:

update table set num=num-1 where id=10 and num>1

去修改库存:id=10,name=xxx的项。

同时另一个用户B也想用该语句修改库存,就需要等待,直到A成功了。

难点:高效的处理竞争。

秒杀功能
  • 秒杀接口暴露
  • 执行秒杀
  • 相关查询

代码开发阶段:DAO设计编码、Service设计编码、Web设计编码

DAO设计编码

创建数据库,两张表:

mysql> show tables;
+-------------------+
| Tables_in_seckill |
+-------------------+
| seckill           | 
| success_killed    |
+-------------------+

详细见下面的注释:

-- 数据库初始化脚本

-- 创建数据库
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 '库存数量',
    `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
    `start_time` timestamp NOT NULL COMMENT '秒杀开启时间',
    `end_time` timestamp NOT NULL COMMENT '秒杀结束时间',
    PRIMARY KEY (seckill_id),
    KEY idx_start_time(start_time),
    KEY idx_end_time(end_time),
    KEY idx_create_time(create_time)
)ENGINE=InnoDB AUTO_INCREMENT=1000 DEFAULT CHARSET=utf8 COMMENT="秒杀库存表";

-- 初始化数据
insert into seckill(name,number,start_time,end_time)
values
    ('1000秒杀iphone13',100,'2021-10-04 18:00:00','2021-10-05 18:00:00'),
    ('500秒杀iphone12',200,'2021-10-04 18:00:00','2021-10-05 18:00:00'),
    ('300秒杀iphone11',300,'2021-10-04 18:00:00','2021-10-05 18:00:00'),
    ('100秒杀iphone6',400,'2021-10-04 18:00:00','2021-10-05 18: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 DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
    PRIMARY KEY(seckill_id,user_phone),
    KEY idx_create_time(create_time)
)ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT="秒杀成功明细表"

-- 连接数据库控制台
-- mysql -uroot -p

加COMMENT是为了能方便地查看创建时的想法:

SHOW CREATE TABLE 展示的内容更加丰富,它可以查看表的存储引擎和字符编码;另外,还可以通过 g 或者 G 参数来控制展示格式。

mysql> show create table seckillG
*************************** 1. row ***************************
       Table: seckill
Create Table: CREATE TABLE `seckill` (
  `seckill_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '商品库存id',
  `name` varchar(120) NOT NULL COMMENT '商品名称',
  `number` int(11) NOT NULL COMMENT '库存数量',
  `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `start_time` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00' COMMENT '秒杀开启时间',
  `end_time` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00' COMMENT '秒杀结束时间',
  PRIMARY KEY (`seckill_id`),
  KEY `idx_start_time` (`start_time`),
  KEY `idx_end_time` (`end_time`),
  KEY `idx_create_time` (`create_time`)
) ENGINE=InnoDB AUTO_INCREMENT=1004 DEFAULT CHARSET=utf8 COMMENT='秒杀库存表'
DAO实体和接口编码

目录如下:

entity:对应数据库,常规操作,具体看注释

SecKill.java - 对应seckill中的秒杀商品:

package cn.orzlinux.entity;

import java.util.Date;

public class SecKill {
    private long seckillId;
    private String name;
    private int number;
    private Date startTime;
    private Date endTime;
    private Date createTime;

    // getter setter toString
}

successkilled.java

package cn.orzlinux.entity;
import java.util.Date;

public class SuccessKilled {
    private long seckillId;
    private long userPhone;
    private short state;
    private Date createTime;

    // 为了能直接获取秒杀成功的商品对象
    private SecKill secKill;
    // getter setter toString
}

DAO:数据库要实现的方法接口

SecKillDao.java

public interface SecKillDao {
    
    int reduceNumber(long seckillId, Date killTime);

    
    SecKill queryById(long seckillId);

    List queryAll(int offset,int limit);
}

SuccessKilledDao.java

public interface SuccessKilledDao {
    // 插入购买明细,可过滤重复
    // 通过联合唯一主键
    // 返回插入的行数
    int insertSuccessKilled(long seckillId,long userPhone);

    // 根据商品id和用户查询秒杀成功对象实体(带秒杀商品实体)
    // 原视频应该有误,这里仅凭一个商品Id得不到唯一的秒杀成功对象
    SuccessKilled queryByIdWithSeckill(long seckillId,long userPhone);
}
MyBatis

MyBatis特点:参数+SQL=Entity/List

SQL可以写在xml或者注解中。一般应该用xml

DAO接口:Mapper自动实现DAO接口(推荐)或者API方式

官方文档链接:mybatis文档

实现DAO编程

在resources文件夹下创建mybatis配置文件和mapper文件夹:

mybatis-config.xml配置如下:

DTD(document Type Definition),全称为文档类型定义。具体头可以在官方文档例子中找到。





    
    
        
        
        
        
        
        
        
    

mybatis整合spring

mybatis整合spring可以实现更少的配置:

  • 别名,如resultType="cn.orzlinux.entity.SecKill"可以简写为SecKill。

  • 配置扫描,如:

    
    
    ...
    

    可以简化为自动配置扫描。

  • DAO实现

    
    
    ...
    

    简化为自动实现DAO接口,自动注入spring容器。

  • 依然具有足够的灵活性、定制SQL、自由传参、结果集自动赋值。

新建配置文件:

spring-dao.xml



    

    
    
    

    
    
        
        
        
        
        

        
        
        
        
        
        
        
        
        
        
    

    
    
        
        
        
        
        
        
        
         

在queryById的测试中一切正常:

@Test
public void queryByIdTest() {
    long id =1000;
    SecKill secKill = secKillDao.queryById(id);
    System.out.println(secKill.getName());
    System.out.println(secKill);
    // output:1000秒杀iphone13
    //SecKill{seckillId=1000, name='1000秒杀iphone13',
    // number=100, startTime=Tue Oct 05 02:00:00 CST 2021,
    // endTime=Wed Oct 06 02:00:00 CST 2021,
    // createTime=Tue Oct 05 10:01:47 CST 2021}
}

但是在queryAllTest中出现了问题:

@Test
public void queryAllTest() {
    List secKillList = secKillDao.queryAll(0,100);
    for(SecKill secKill:secKillList) {
        System.out.println(secKill);
    }
}

问题的原因在于:




    select seckill_id,name,number,start_time,end_time,create_time
    from seckill
    order by create_time DESC
    limit ${offset},#{limit};

所以需要在SecKillDao.java里的函数那里配置参数Param:

List queryAll(@Param("offset") int offset, @Param("limit") int limit);

问题解决,结果如下:

SecKill{seckillId=1000, name='1000秒杀iphone13', number=100, startTime=Tue Oct 05 02:00:00 CST 2021, endTime=Wed Oct 06 02:00:00 CST 2021, createTime=Tue Oct 05 10:01:47 CST 2021}
SecKill{seckillId=1001, name='500秒杀iphone12', number=200, startTime=Tue Oct 05 02:00:00 CST 2021, endTime=Wed Oct 06 02:00:00 CST 2021, createTime=Tue Oct 05 10:01:47 CST 2021}
SecKill{seckillId=1002, name='300秒杀iphone11', number=300, startTime=Tue Oct 05 02:00:00 CST 2021, endTime=Wed Oct 06 02:00:00 CST 2021, createTime=Tue Oct 05 10:01:47 CST 2021}
SecKill{seckillId=1003, name='100秒杀iphone6', number=400, startTime=Tue Oct 05 02:00:00 CST 2021, endTime=Wed Oct 06 02:00:00 CST 2021, createTime=Tue Oct 05 10:01:47 CST 2021}

减库存测试:

@Test
public void reduceNumberTest() {
    Date killTime = new Date();
    int updateCount = secKillDao.reduceNumber(1000L,killTime);
    System.out.println(updateCount);
    // 1:表示更改了一条记录
}

测试记录,查看这里的确少了一件:

分析一下mybatis的行为:

# 从c3p0连接池拿到链接,没有被spring托管
02:18:42.793 [main] DEBUG o.m.s.t.SpringManagedTransaction - JDBC Connection [com.mchange.v2.c3p0.impl.NewProxyConnection@1cd629b3] will not be managed by Spring

# SQL
02:18:42.818 [main] DEBUG c.o.dao.SecKillDao.reduceNumber - ==>  Preparing: update seckill set number = number - 1 where seckill_id = ? and start_time <= ? and end_time >= ? and number > 0; 

# 参数传递
02:18:42.882 [main] DEBUG c.o.dao.SecKillDao.reduceNumber - ==> Parameters: 1000(Long), 2021-10-05 02:18:42.442(Timestamp), 2021-10-05 02:18:42.442(Timestamp)

# 参数返回
02:18:42.897 [main] DEBUG c.o.dao.SecKillDao.reduceNumber - <==    Updates: 1

同理另一个dao SuccessKilledDao也进行测试。

package cn.orzlinux.dao;

import cn.orzlinux.entity.SuccessKilled;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import javax.annotation.Resource;

import static org.junit.Assert.*;

@RunWith(SpringJUnit4ClassRunner.class)
//告诉junit spring配置文件
@ContextConfiguration({"classpath:spring/spring-dao.xml"})
public class SuccessKilledDaoTest {
    // 注入DAO实现类依赖,resource注解去spring容器找其实现类
    @Resource
    private SuccessKilledDao successKilledDao;
    @Test
    public void insertSuccessKilled() {
        long id = 1000L;
        long phone = 12345678901L;
        int insertCount = successKilledDao.insertSuccessKilled(id,phone);
        System.out.println(insertCount);
        // 输出1
        // 再运行一次输出 0,联合主键的效果
    }

    @Test
    public void queryByIdWithSeckill() {
        long id = 1000L;
        long phone = 12345678901L;
        SuccessKilled successKilled = successKilledDao.queryByIdWithSeckill(id,phone);
        System.out.println(successKilled);
        System.out.println(successKilled.getSecKill());
        // SuccessKilled{seckillId=1000, userPhone=12345678901, state=-1,
        //              createTime=Tue Oct 05 10:33:43 CST 2021}
        // SecKill{seckillId=1000, name='1000秒杀iphone13', number=99,
        //          startTime=Tue Oct 05 02:00:00 CST 2021, endTime=Wed Oct 06 02:00:00 CST 2021, createTime=Tue Oct 05 10:01:47 CST 2021}
    }
}

还有一点就是更改秒杀成功的状态,更改为0,也就是秒杀成功:

状态标识:-1无效,0成功,1已付款,2已发


    
    insert ignore into success_killed(seckill_id,user_phone,state)
    values (#{seckillId},#{userPhone},0)

本文同步发布于orzlinux.cn

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/294569.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号