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

本文涉及的产品
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
日志服务 SLS,月写入数据量 50GB 1个月
云解析 DNS,旗舰版 1个月
简介: 章节目录DAO 设计编码数据库设计与编码DAO层实体和接口编码基于mybatis实现DAO理论基于mybatis实现DAO接口-1mybatis整合SpringDAO层编码解析Dao 设计编码1.

章节目录

  • 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层实体与接口编码

img_4d4b795c7bd0b0d2c71f8a1d9f783726.png
数据库与实体类映射

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层接口声明能显示说明很多事情

img_e30135972b761f4e01a3de640426198e.png
轻量级显式接口声明

更少的配置-别名

img_e871e8d92ca50763fbc90333b9ba5d4d.png
image.png

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

更少的配置-配置扫描

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

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

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

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

足够的灵活性

img_94b027caef0949d158bc678731ed62bc.png
image.png

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

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

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

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

相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
目录
相关文章
|
22天前
|
Java API Spring
打造未来电商新引擎:揭秘Java可扩展API设计,让支付与物流灵活如丝,引领电商时代潮流!
【8月更文挑战第30天】本文通过电商平台案例,探讨了如何设计可扩展的Java API。首先定义支付和物流服务的接口与抽象类,然后实现具体服务,接着引入工厂模式或依赖注入管理服务实例,最后通过配置实现灵活扩展。这种设计确保了应用架构的灵活性和长期稳定性。
37 3
|
9天前
|
Java API C++
Java 8 Stream Api 中的 peek 操作
本文介绍了Java中`Stream`的`peek`操作,该操作通过`Consumer&lt;T&gt;`函数消费流中的每个元素,但不改变元素类型。文章详细解释了`Consumer&lt;T&gt;`接口及其使用场景,并通过示例代码展示了`peek`操作的应用。此外,还对比了`peek`与`map`的区别,帮助读者更好地理解这两种操作的不同用途。作者为码农小胖哥,原文发布于稀土掘金。
Java 8 Stream Api 中的 peek 操作
|
15天前
|
安全 Java API
【本地与Java无缝对接】JDK 22外部函数和内存API:JNI终结者,性能与安全双提升!
【9月更文挑战第6天】JDK 22的外部函数和内存API无疑是Java编程语言发展史上的一个重要里程碑。它不仅解决了JNI的诸多局限和挑战,还为Java与本地代码的互操作提供了更加高效、安全和简洁的解决方案。随着FFM API的逐渐成熟和完善,我们有理由相信,Java将在更多领域展现出其强大的生命力和竞争力。让我们共同期待Java编程新纪元的到来!
38 11
|
12天前
|
监控 Java 大数据
【Java内存管理新突破】JDK 22:细粒度内存管理API,精准控制每一块内存!
【9月更文挑战第9天】虽然目前JDK 22的确切内容尚未公布,但我们可以根据Java语言的发展趋势和社区的需求,预测细粒度内存管理API可能成为未来Java内存管理领域的新突破。这套API将为开发者提供前所未有的内存控制能力,助力Java应用在更多领域发挥更大作用。我们期待JDK 22的发布,期待Java语言在内存管理领域的持续创新和发展。
|
14天前
|
Java API 数据处理
【Java的SIMD革命】JDK 22向量API:释放硬件潜能,让Java应用性能飙升!
【9月更文挑战第7天】 JDK 22向量API的发布标志着Java编程语言在SIMD技术领域的重大突破。这一新特性不仅释放了现代硬件的潜能,更让Java应用性能实现了飙升。我们有理由相信,在未来的发展中,Java将继续引领编程语言的潮流,为开发者们带来更加高效、更加强大的编程体验。让我们共同期待Java在SIMD技术的推动下开启一个全新的性能提升时代!
|
15天前
|
Java API 开发者
【Java字节码操控新篇章】JDK 22类文件API预览:解锁Java底层的无限可能!
【9月更文挑战第6天】JDK 22的类文件API为Java开发者们打开了一扇通往Java底层世界的大门。通过这个API,我们可以更加深入地理解Java程序的工作原理,实现更加灵活和强大的功能。虽然目前它还处于预览版阶段,但我们已经可以预见其在未来Java开发中的重要地位。让我们共同期待Java字节码操控新篇章的到来!
|
13天前
|
Java API 开发者
【Java字节码的掌控者】JDK 22类文件API:解锁Java深层次的奥秘,赋能开发者无限可能!
【9月更文挑战第8天】JDK 22类文件API的引入,为Java开发者们打开了一扇通往Java字节码操控新世界的大门。通过这个API,我们可以更加深入地理解Java程序的底层行为,实现更加高效、可靠和创新的Java应用。虽然目前它还处于预览版阶段,但我们已经可以预见其在未来Java开发中的重要地位。让我们共同期待Java字节码操控新篇章的到来,并积极探索类文件API带来的无限可能!
|
21天前
|
Java API
Java 8新特性:Lambda表达式与Stream API的深度解析
【7月更文挑战第61天】本文将深入探讨Java 8中的两个重要特性:Lambda表达式和Stream API。我们将首先介绍Lambda表达式的基本概念和语法,然后详细解析Stream API的使用和优势。最后,我们将通过实例代码演示如何结合使用Lambda表达式和Stream API,以提高Java编程的效率和可读性。
|
20天前
|
Java 数据库连接 缓存
Hibernate性能调优:五大秘籍,让应用效能飙升,告别慢如蜗牛的加载,体验丝滑般流畅!
【8月更文挑战第31天】本文深入探讨了提升Hibernate应用性能的五大技巧,包括选择合适的缓存策略、优化查询语句、合理使用Eager与Lazy加载、批量操作与事务管理以及利用索引和数据库优化。通过正确配置多级缓存、分页查询、延迟加载、批量处理及合理创建索引,能够显著提高应用响应速度与吞吐量,改善用户体验。这些技巧需根据具体应用场景灵活调整,以实现最佳性能优化效果。
48 0
|
20天前
|
UED 开发工具 iOS开发
Uno Platform大揭秘:如何在你的跨平台应用中,巧妙融入第三方库与服务,一键解锁无限可能,让应用功能飙升,用户体验爆棚!
【8月更文挑战第31天】Uno Platform 让开发者能用同一代码库打造 Windows、iOS、Android、macOS 甚至 Web 的多彩应用。本文介绍如何在 Uno Platform 中集成第三方库和服务,如 Mapbox 或 Google Maps 的 .NET SDK,以增强应用功能并提升用户体验。通过 NuGet 安装所需库,并在 XAML 页面中添加相应控件,即可实现地图等功能。尽管 Uno 平台减少了平台差异,但仍需关注版本兼容性和性能问题,确保应用在多平台上表现一致。掌握正确方法,让跨平台应用更出色。
27 0