美文网首页Java并发编程Java Web知识Spring boot
Java高并发秒杀Api-业务分析与DAO层构建2

Java高并发秒杀Api-业务分析与DAO层构建2

作者: markfork | 来源:发表于2018-05-05 11:38 被阅读227次

    章节目录

    • DAO 设计编码
      • 数据库设计与编码
      • DAO层实体和接口编码
      • 基于mybatis实现DAO理论
      • 基于mybatis实现DAO接口-1
      • mybatis整合Spring
      • DAO层编码解析

    Dao 设计编码

    1.pom.xml 引入项目依赖的包

    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
        <modelVersion>4.0.0</modelVersion>
        <groupId>org.seckill</groupId>
        <artifactId>seckill</artifactId>
        <packaging>war</packaging>
        <version>1.0</version>
        <name>seckill Maven Webapp</name>
        <url>http://maven.apache.org</url>
        <dependencies>
            <dependency>
                <groupId>junit</groupId>
                <artifactId>junit</artifactId>
                <!--使用注解方式运行junit-->
                <version>4.11</version>
                <scope>test</scope>
            </dependency>
            <!--补全项目依赖-->
            <!--日志-->
            <!--1.日志 java日志,slf4j,log4j,logback,common-logging-->
            <!--2.
                  slf4j 是规范、接口
                  日志实现 log4j,logback,common-logging
                  使用slf4j+logback
            -->
            <dependency>
                <groupId>org.slf4j</groupId>
                <artifactId>slf4j-api</artifactId>
                <version>1.7.12</version>
            </dependency>
    
            <dependency>
                <groupId>ch.qos.logback</groupId>
                <artifactId>logback-core</artifactId>
                <version>1.1.2</version>
            </dependency>
            <!--实现slf4j接口并实现,直接通过slf4j来进行日志记录-->
            <dependency>
                <groupId>ch.qos.logback</groupId>
                <artifactId>logback-classic</artifactId>
                <version>1.1.2</version>
            </dependency>
    
            <!--数据库依赖-->
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <version>5.1.35</version>
                <scope>runtime</scope>
            </dependency>
            <!--数据库连接池-->
            <dependency>
                <groupId>c3p0</groupId>
                <artifactId>c3p0</artifactId>
                <version>0.9.1.2</version>
            </dependency>
            <!--dao层依赖:MyBatis-->
            <dependency>
                <groupId>org.mybatis</groupId>
                <artifactId>mybatis</artifactId>
                <version>3.3.0</version>
            </dependency>
    
            <!--mybatis自身实现的spring依赖-->
            <dependency>
                <groupId>org.mybatis</groupId>
                <artifactId>mybatis-spring</artifactId>
                <version>1.2.3</version>
            </dependency>
    
            <!--servlet web相关依赖-->
            <dependency>
                <groupId>taglibs</groupId>
                <artifactId>standard</artifactId>
                <version>1.1.2</version>
            </dependency>
            <dependency>
                <groupId>jstl</groupId>
                <artifactId>jstl</artifactId>
                <version>1.2</version>
            </dependency>
            <dependency>
                <groupId>com.fasterxml.jackson.core</groupId>
                <artifactId>jackson-databind</artifactId>
                <version>2.5.4</version>
            </dependency>
            <dependency>
                <groupId>javax.servlet</groupId>
                <artifactId>javax.servlet-api</artifactId>
                <version>3.1.0</version>
            </dependency>
    
            <!--end  java web 相关依赖-->
            <!--spring 依赖-->
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-core</artifactId>
                <version>4.1.7.RELEASE</version>
            </dependency>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-beans</artifactId>
                <version>4.1.7.RELEASE</version>
            </dependency>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-context</artifactId>
                <version>4.1.7.RELEASE</version>
            </dependency>
            <!--spring dao层依赖 -->
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-jdbc</artifactId>
                <version>4.1.7.RELEASE</version>
            </dependency>
            <!--spring 声明式事务-->
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-tx</artifactId>
                <version>4.1.7.RELEASE</version>
            </dependency>
    
            <!--spring web 相关依赖 servlet容器加载spring ioc spring  aop  启动spring 的工厂-->
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-web</artifactId>
                <version>4.1.7.RELEASE</version>
            </dependency>
    
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-webmvc</artifactId>
                <version>4.1.7.RELEASE</version>
            </dependency>
    
            <!--junit相关依赖-->
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-test</artifactId>
                <version>4.1.7.RELEASE</version>
            </dependency>
    
    
        </dependencies>
        <build>
            <finalName>seckill</finalName>
        </build>
    </project>
    

    2.数据库设计与编码
    数据库脚本如下所示:

    --数据库初始化脚本
    
    --创建数据库
    CREATE DATABASE seckill;
    
    --使用数据库
    use seckill;
    
    --创建秒杀库存表
    CREATE TABLE seckill(
       `seckill_id` bigint not null AUTO_INCREMENT COMMENT '商品库存id',
       `name` VARCHAR (120) not null COMMENT '商品名称',
       `stock` 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 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,stock,start_time,end_time)
    VALUES ('1000元秒杀iphone x','100','2018-05-04 00:00:00','2018-05-05 00:00:00'),
           ('500元秒杀ipad x','200','2018-05-04 00:00:00','2018-05-05 00:00:00'),
           ('300元秒杀小米4','300','2018-05-04 00:00:00','2018-05-05 00:00:00'),
           ('200元秒杀小米note','400','2018-05-04 00:00:00','2018-05-05 00:00:00');
    
    --秒杀成功明细表
    CREATE TABLE success_killed(
      `seckill_id` bigint NOT NULL COMMENT '秒杀商品id',
      `user_phone` VARCHAR(11) NOT NULL COMMENT '用户手机号',
      `state` tinyint NOT NULL DEFAULT -1 COMMENT '状态标志:-1:无效 0:成功 1:已付款',
      `create_time` TIMESTAMP NOT NULL COMMENT '创建时间',
      PRIMARY KEY (seckill_id,user_phone),/*联合主键*/
      key idx_create_time(create_time)
    ) ENGINE=InnoDB AUTO_INCREMENT=1000 DEFAULT CHARSET=utf8 COMMENT='秒杀成功明细表';
    
    --连接数据库的控制台
    mysql -u root -p
    

    3.DAO层实体与接口编码

    数据库与实体类映射

    3.1MyBatis 实现DAO接口的方式

    • Mapper自动实现DAO接口
      sql直接编写,注解sql(sql更改,原代码需要重新编译),xml方式,单独更新sql,源文件不需要重新编译。
    • API 编程方式自动实现DAO 接口

    3.2 基于mybatis实现DAO接口-1

    • 全局mybatis-conf.xml
    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE configuration
            PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
            "http://mybatis.org/dtd/mybatis-3-config.dtd">
    <!--配置全局属性-->
    <configuration>
        <settings>
            <!-- changes from the defaults for testing -->
            <setting name="cacheEnabled" value="false" />
            <!--使用jdbc 的getGeneratedKeys 获取数据库自增主键值-->
            <setting name="useGeneratedKeys" value="true" />
            <!--使用列别名 替换列名 默认true 复制到对应的entity属性中
              select name as tile from table
            -->
            <setting name="useColumnLable" value="true"/>
            <!--开启驼峰命名转化-->
            <setting name="mapUnderscoreCameCase" value="true"/>
            <!--REUSE 执行器会重用预处理语句-->
            <setting name="defaultExecutorType" value="REUSE" />
        </settings>
    </configuration>
    
    • SecKillDao.xml编写
    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE mapper
            PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
            "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <mapper namespace="org.seckill.dao.SecKillDao">
        <!--目的:为DAO方法提供SQL语句配置
            parameter-type不用给
            #与$的区别
            预编译
            1.#{} 解析为一个 JDBC 预编译语句(prepared statement)的参数标记符。
            会被解析为 ?占位符 动态SQL-会对SQL进行动态解析,解析为一个BoundSql对象
    
            变量替换
            2.${} 仅仅为一个纯粹的 string 替换,在动态 SQL 解析阶段将会进行变量替换
            3.#{}的变量的替换是在 DBMS 中。
    
            4.${} 在预编译之前已经被变量替换了,这会存在 sql 注入问题
            5.表名是不能带''号的,所以使用${} 防止出现变量替换后表名带''
    
        -->
        <update id="reduceStock">
            <!--具体sql-->
            UPDATE
            seckill
            SET
            stock = stock - 1
            WHERE
            seckill_id = #{secKillId}
            AND
            start_time <![CDATA[<=]]> #{killTime}
            AND
            end_time >= #{killTime}
            and stock > 0
        </update>
    
        <select id="queryById" resultType="SecKill" parameterType="long">
             SELECT
             seckill_id as seckillId,name,stock,start_time,end_time,create_time
             from seckill
             where
             seckill_id = #{secKillId}
        </select>
    
        <select id="queryAll" resultType="SecKill" parameterType="int">
             SELECT
             seckill_id as seckillId,name,stock,start_time,end_time,create_time
             from seckill
             order by create_time desc
             limit #{offset},${limit}
        </select>
    
    </mapper>
    
    • SuccessKilledDao.xml 编写
    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE mapper
            PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
            "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <mapper namespace="org.seckill.dao.SuccessKilledDao">
       <insert id="insertSuccessKilled" >
          <!--如果出现重复,主键冲突,直接产生一个错误-->
          INSERT ignore INTO
          success_killed(seckill_id,user_phone)
          VALUES
          (#{secKillId},#{userPhone})
       </insert>
    
        <select id="queryByIdWithSecKill" resultType="SuccessKilled" >
            <!--根据id查询SuccessKilled并携带SecKill实体-->
            <!--如何告诉mybatis 把结果映射到SuccessKilled的同时映射secKill 属性-->
            <!--可以自由控制SQL 优化等-->
            SELECT
            sk.seckill_id,
            sk.user_phone,
            sk.create_time,
            sk.state,
            sc.seckill_id as "secKill.seckill_id",
            sc.name as "secKill.name",
            sc.stock as "secKill.stock",
            sc.start_time as "secKill.start_time",
            sc.end_time as "sceKill.end_time",
            sc.create_time as "secKill.create_time"
            FROM
            success_killed sk
            INNER  JOIN
            seckill sc on sk.seckill_id = sc.seckill_id
            WHERE sk.seckill_id = #{secKillId}
    
        </select>
    </mapper>
    
    • entity实体层实现-SecKill、SuccessKilled
    package org.seckill.domain;
    
    import java.util.Date;
    
    /**
     * 秒杀-库存表 entity
     */
    public class SecKill {
        private long seckillId; //秒杀-商品id
        private String name;    //秒杀-商品名字
        private int stock;      //秒杀-商品库存
        private Date startTime; //秒杀-开始时间
        private Date endTime;   //秒杀-结束时间
        private Date createTime; //秒杀-商品新建时间
    
        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 int getStock() {
            return stock;
        }
    
        public void setStock(int stock) {
            this.stock = stock;
        }
    
        public Date getStartTime() {
            return startTime;
        }
    
        public void setStartTime(Date startTime) {
            this.startTime = startTime;
        }
    
        public Date getEndTime() {
            return endTime;
        }
    
        public void setEndTime(Date endTime) {
            this.endTime = endTime;
        }
    
        public Date getCreateTime() {
            return createTime;
        }
    
        public void setCreateTime(Date createTime) {
            this.createTime = createTime;
        }
    
        @Override
        public String toString() {
            return "SecKill{" +
                    "seckillId=" + seckillId +
                    ", name='" + name + '\'' +
                    ", stock=" + stock +
                    ", startTime=" + startTime +
                    ", endTime=" + endTime +
                    ", createTime=" + createTime +
                    '}';
        }
    }
    
    
    package org.seckill.domain;
    
    import java.util.Date;
    
    /**
     * 成功秒杀明细表-SuccessKilled
     */
    public class SuccessKilled {
        private long secKillId;    //秒杀-成功的商品id
        private String userPhone;  //秒杀-成功的用户手机号
        private short state;       //秒杀-明细状态
        private Date createTime;   //秒杀-秒杀成功的时间
    
        //one(被秒杀商品)-to-many(秒杀记录)
        private SecKill secKill;
    
        public long getSecKillId() {
            return secKillId;
        }
    
        public void setSecKillId(long secKillId) {
            this.secKillId = secKillId;
        }
    
        public String getUserPhone() {
            return userPhone;
        }
    
        public void setUserPhone(String userPhone) {
            this.userPhone = userPhone;
        }
    
        public short getState() {
            return state;
        }
    
        public void setState(short state) {
            this.state = state;
        }
    
        public Date getCreateTime() {
            return createTime;
        }
    
        public void setCreateTime(Date createTime) {
            this.createTime = createTime;
        }
    
        public SecKill getSecKill() {
            return secKill;
        }
    
        public void setSecKill(SecKill secKill) {
            this.secKill = secKill;
        }
    
        @Override
        public String toString() {
            return "SuccessKilled{" +
                    "secKillId=" + secKillId +
                    ", userPhone='" + userPhone + '\'' +
                    ", state=" + state +
                    ", createTime=" + createTime +
                    '}';
        }
    }
    
    • DAO层接口功能声明-SecKillDao、SuccessKilledDao
    package org.seckill.dao;
    
    import org.seckill.domain.SecKill;
    
    import java.util.Date;
    import java.util.List;
    
    /**
     * 常用的操作
     */
    
    public interface SecKillDao {
        /**
         * 功能:减库存
         * @param secKillId
         * @param killTime create_time?
         * @return 如果影响行数>1,表示更新的记录行数
         */
        int reduceStock(long secKillId,Date killTime);
    
        /**
         * 根据秒杀商品id查询秒杀商品详情
         * @param secKillId
         * @return
         */
        SecKill queryById(long secKillId);
    
        /**
         * 根据偏移量查询秒杀商品列表,
         * @param offset
         * @param limit
         * @return
         */
        List<SecKill> queryAll(int offset,int limit);
    
    }
    
    
    package org.seckill.dao;
    
    import org.seckill.domain.SuccessKilled;
    
    public interface SuccessKilledDao {
        /**
         * 功能:新增用户秒杀明细
         * @param secKillId
         * @param userPhone
         * @return 插入的行数,禁止插入表示插入失败 则返回0
         */
        int insertSuccessKilled(long secKillId,long userPhone);
    
        /**
         * 功能:根据明细id查询具体的秒杀明细并携带秒杀商品对象
         * @param secKillId
         * @return
         */
        SuccessKilled queryByIdWithSecKill(long secKillId);
    }
    
    

    MyBatis整合Spring

    整合的目标:

    • 更少的编码
    • 更少的配置
    • 足够的灵活性
      1.mybatis优点
    更少的编码、只写接口、不写实现
    

    2.DAO层接口声明能显示说明很多事情

    轻量级显式接口声明

    更少的配置-别名

    image.png

    使用spring 提供的package-scan 扫描实体包下的所有实体类。可以简写resultType

    更少的配置-配置扫描

    • 单独使用mybatis的场景下,配置文件扫描方式

    <mapper resource="mapper/SecKillDao.xml">....

    • 使用spring 整合 mybatis 自动扫描配置文件,采用通配符的方式。

    • 自动实现DAO接口,DAO接口的实现类是自动注入至Spring 容器

    足够的灵活性

    image.png

    5.DAO层接口设计与SQL编写的反思

    DAO - data access object 数据访问层的简称
    这一层我们主要做的是核心数据操作的接口声明,以及SQL编写,并没有涉及到Service 业务逻辑层代码的编写。这样做的好处是什么呢?
    主要有以下三点

    • 代码分层,DAO层只关注于核心数据操作的接口声明&SQL编写
    • 代码和SQL的分离,方便代码review
    • 业务逻辑层主要做事务控制、事务中完成DAO层的代码组合与拼接。每一层都可以做单元测试。

    接下来详解mybatis 与 spring 整合编码的过程

    相关文章

      网友评论

      本文标题:Java高并发秒杀Api-业务分析与DAO层构建2

      本文链接:https://www.haomeiwen.com/subject/yfojrftx.html