SSM架构之高并发秒杀之DAO详解

SSM架构之高并发秒杀之Service : http://www.jianshu.com/p/1d91a3c0edc7

一、相关技术

1、前端

  • 前端交互设计
  • Bootstrap
  • Jquery

2、Spring

  • Spring IOC整合Service
  • 声明式事务运用

3、Spring MVC

  • Restful接口设计与使用
  • 框架用作流程
  • Controller开发技巧

4、MyBatis

  • DAO层的设计与开发
  • MyBatis合理使用
  • MyBatis与Spring的整合

5、MySQL

  • 表设计
  • SQL技巧
  • 事务与行级锁

6、高并发

  • 高并发点和高并发分析
  • 优化思路并实现

7、环境与工具

  • Windows
  • Maven
  • IntelliJ IDEA / eclipse

二、需求分析

1、基本需求分析

SSM架构之高并发秒杀之DAO详解_第1张图片
秒杀系统基本业务流程
  • 秒杀业务的核心:就是对库存的处理
SSM架构之高并发秒杀之DAO详解_第2张图片
用户针对库存业务分析
  • 用户的购买行为
SSM架构之高并发秒杀之DAO详解_第3张图片
用户的秒杀信息
  • 潜在问题分析
SSM架构之高并发秒杀之DAO详解_第4张图片
可能存在的问题

2、难点分析

  • 竞争

mysql 事务 + 行级锁 ,是竞争在技术上的真正体现

  • 事务:开启事务——update 数据库数量更新——insert 购买明细——commit提交(update 库存更新,是资源占用最多的时候)
SSM架构之高并发秒杀之DAO详解_第5张图片
用户竞争
  • 行级锁
SSM架构之高并发秒杀之DAO详解_第6张图片
行级锁
  • ** 如何高效的处理竞争 **

3、主要的秒杀功能

SSM架构之高并发秒杀之DAO详解_第7张图片
秒杀库存系统-天猫
  • 秒杀接口暴露

  • 执行秒杀

  • 秒杀相关查询

4、代码开发

  • DAO 层设计编码
  • service 层设计编码
  • web 设计编码

三、项目搭建

SSM架构之高并发秒杀之DAO详解_第8张图片
项目结构图

多查看官方文档,而不是仅仅通过搜索引擎,因为很多博客等技术类的文档都从在这过时以及不完善性,而官方文档不仅新且全。

相关配置的官方地址:

  • logback 配置:https://logback.qos.ch/manual/
  • spring 配置:http://docs.spring.io/spring/docs/
  • mybatis 配置:http://www.mybatis.org/mybatis-3/index.html

1、pom.xml 文件配置

pom.xml配置文件主要用于项目所依赖jar包的管理
对于该项目,主要依赖的jar包有:

  • 单元测试(4.0以上版本主要以注入的方式进行单元测试)
  • junit 4.10
  • 日志:日志记录的主要有:slf4j、log4j、logback、common-logging,其中 slf4j 是规范接口,而 log4j、logback、common-logging 则为日志的实现
    • slf4j-api 1.7.12
    • logback-core 1.1.1 实现log日志的核心功能
    • logback-classic 1.1.1 实现 slf4j ,并对其进行整合
  • 数据库相关依赖
* mysql-connector-java  5.1.35   数据库驱动依赖
* c3p0  0.9.1.2   数据库链接池
  • DAO 层Mybatis依赖
 * mybatis  3.3.0  MyBatis自身的依赖
 * mybatis-spring   1.2.3  MyBatis 与 Spring 整合依赖(由MyBatis提供)
  • servlet web 相关依赖
* standard  1.2.1 jsp依赖的相关标签
* jstl  1.2   js默认的标签库
* jackson-databind  2.5.4  Jackson的标签
* javax.servlet-api  3.1.0  servlet依赖的api
  • Spring依赖
* 1、Spring核心依赖
  * spring-core  4.1.7.RELEASE   Spring核心
  * spring-beans  4.1.7.RELEASE  Spring IOC 依赖
  * spring-context 4.1.7.RELEASE  Spring 扩展依赖
  • Spring DAO 依赖
* spring-jdbc  4.1.7.RELEASE    Spring jdbc依赖
* spring-tx   4.1.7.RELEASE  Spring 事务依赖
  • Spring web 依赖
* spring-web  4.1.7.RELEASE  Spring web 项目在启动时依赖
* spring-webmvc   4.1.7.RELEASE  Spring-MVC依赖
  • Spring test依赖(单元测试时要加载Spring的容器)
* spring-test    4.1.7.RELEASE 方便通过junit进行单元测试

  4.0.0
  org.seckill
  seckill
  war
  1.0-SNAPSHOT
  seckill Maven Webapp
  http://maven.apache.org
  
    
    
      junit
      junit
      4.10
      test
    

    
    
    
      org.slf4j
      slf4j-api
      1.7.6
    

    
    
      ch.qos.logback
      logback-core
      1.1.1
    

    
    
      ch.qos.logback
      logback-classic
      1.1.1
    

    
    
    
      mysql
      mysql-connector-java
      5.1.37
      runtime
    

    
    
      c3p0
      c3p0
      0.9.1.2
    

    
      com.alibaba
      dubbo
      2.5.3
      
        
          org.springframework
          spring
        
      
    

    
      com.alibaba
      druid
      0.2.19
    


    
    
    
      org.mybatis
      mybatis
      3.3.0
    

    
    
      org.mybatis
      mybatis-spring
      1.2.3
    

    
    
    
      taglibs
      standard
      1.1.2
    

    
    
      jstl
      jstl
      1.2
    

    
    
      com.fasterxml.jackson.core
      jackson-databind
      2.5.4
    

    
    
      javax.servlet
      javax.servlet-api
      3.1.0
    

    
    
    
    
      org.springframework
      spring-core
      4.1.7.RELEASE
    
    
    
      org.springframework
      spring-beans
      4.1.7.RELEASE
    
    
    
      org.springframework
      spring-context
      4.1.7.RELEASE
    

    
    
    
      org.springframework
      spring-jdbc
      4.1.7.RELEASE
    
    
    
      org.springframework
      spring-tx
      4.1.7.RELEASE
    

    
    
    
      org.springframework
      spring-web
      4.1.7.RELEASE
    
    
    
      org.springframework
      spring-webmvc
      4.1.7.RELEASE
    
    
    
      org.springframework
      spring-test
      4.1.7.RELEASE
    

  
  
    seckill
  


2、web.xml

修改 servlet 版本为 3.0 以上,因为只有3.0 以上才支持el等表达式


  

四、DAO 层、数据库开发设计

SSM架构之高并发秒杀之DAO详解_第9张图片
目录结构

1、schema.sql

进行数据设计

-- 数据库初始化脚本

-- 1、创建数据库
-- 创建秒杀库存表
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 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元秒杀iPhone6',100,'2017-02-13 00:00:00','2017-02-14 00:00:00'),
  ('500元秒杀iPad2',200,'2017-02-13 00:00:00','2017-02-14 00:00:00'),
  ('300元秒杀小米4',300,'2017-02-13 00:00:00','2017-02-14 00:00:00'),
  ('100元秒杀小米note',400,'2017-02-13 00:00:00','2017-02-14 00:00:00');

-- 秒杀成功明细表
-- 用户登陆认真相关信息
CREATE TABLE success_killed (
  seckill_id BIGINT NOT NULL AUTO_INCREMENT 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 '秒杀成功明细表';

2、DAO 实体与接口设计

1) 实体类创建

  • Seckill.java

产品实体类

package org.seckill.bean;

import java.util.Date;

/**
 * 产品秒杀实体类
 * Created by wangxf on 2017/2/13.
 */
public class Seckill {
    private long seckillId;     // 产品id
    private String name;        // 产品名称
    private int number;         // 产品数量
    private Date startTime;     // 秒杀开始时间
    private Date endTime;       // 秒杀介绍时间
    private Date createTime;    // 创建时间

    public Seckill() {
    }

    public Seckill(long seckillId, String name, int number, Date startTime, Date endTime, Date createTime) {
        this.seckillId = seckillId;
        this.name = name;
        this.number = number;
        this.startTime = startTime;
        this.endTime = endTime;
        this.createTime = createTime;
    }

    public long getSeckillId() {
        return seckillId;
    }

    public String getName() {
        return name;
    }

    public int getNumber() {
        return number;
    }

    public Date getStartTime() {
        return startTime;
    }

    public Date getEndTime() {
        return endTime;
    }

    public Date getCreateTime() {
        return createTime;
    }

    public void setSeckillId(long seckillId) {
        this.seckillId = seckillId;
    }

    public void setName(String name) {
        this.name = name;
    }

    public void setNumber(int number) {
        this.number = number;
    }

    public void setStartTime(Date startTime) {
        this.startTime = startTime;
    }

    public void setEndTime(Date endTime) {
        this.endTime = endTime;
    }

    public void setCreateTime(Date createTime) {
        this.createTime = createTime;
    }

    @Override
    public String toString() {
        return "Seckill{" +
                "seckillId=" + seckillId +
                ", name='" + name + '\'' +
                ", number=" + number +
                ", startTime=" + startTime +
                ", endTime=" + endTime +
                ", createTime=" + createTime +
                '}';
    }
}
  • SuccessKilled.java

秒杀明细实体类

package org.seckill.bean;

import java.util.Date;

/**
 * 秒杀明细实体类
 * Created by wangxf on 2017/2/13.
 */
public class SuccessKilled {
    private long seckillId;     // 产品id
    private long userPhone;     // 用户手机号码
    private short state;        // 状态 -1 无效 0 秒杀成功 1 已付款 2 已收货
    private Date createTime;    // 创建时间
    private Seckill seckill;    // 产品实体对象   多对一

    public SuccessKilled() {
    }

    public SuccessKilled(long seckillId, long userPhone, short state, Date createTime) {
        this.seckillId = seckillId;
        this.userPhone = userPhone;
        this.state = state;
        this.createTime = createTime;
    }

    public Seckill getSeckill() {
        return seckill;
    }

    public void setSeckill(Seckill seckill) {
        this.seckill = seckill;
    }

    public long getSeckillId() {
        return seckillId;
    }

    public long getUserPhone() {
        return userPhone;
    }

    public short getState() {
        return state;
    }

    public Date getCreateTime() {
        return createTime;
    }

    public void setSeckillId(long seckillId) {
        this.seckillId = seckillId;
    }

    public void setUserPhone(long userPhone) {
        this.userPhone = userPhone;
    }

    public void setState(short state) {
        this.state = state;
    }

    public void setCreateTime(Date createTime) {
        this.createTime = createTime;
    }

    @Override
    public String toString() {
        return "SuccessKilled{" +
                "seckillId=" + seckillId +
                ", userPhone=" + userPhone +
                ", state=" + state +
                ", createTime=" + createTime +
                ", seckill=" + seckill +
                '}';
    }
}

2) Dao层接口

  • ISeckillDao.java

库存操作Dao层接口

package org.seckill.dao.interfaces;

import org.apache.ibatis.annotations.Param;
import org.seckill.bean.Seckill;

import java.util.Date;
import java.util.List;

/**
 * 库存操作Dao层接口
 * Created by wangxf on 2017/2/13.
 */
public interface ISeckillDao {

    /**
     * 减库存,返回更新的行数 0 表示更新失败
     * @param seckillId
     * @param killTime
     * @return
     */
    public int updateProductNumber( @Param("seckillId")long seckillId, @Param("killTime") Date killTime );

    /**
     * 通过产品id来查询产品信息
     * @param seckillId
     * @return
     */
    public Seckill selectProductById(long seckillId);

    /**
     * 根据偏移量查询秒杀商品列表
     * @param offet 偏移量
     * @param limit 要取得行数
     * @return
     */
    /**
     * 会遇到的ERROR: Caused by: org.apache.ibatis.binding.BindingException: Parameter 'offset' not found. Available parameters are [0, 1, param1, param2]
     * 原因:java中没有保存形参的记录,selectProductAll( long offset, int limit ) ——> selectProductAll(args1,args2),
     *       即当有多个参数时,会无法分清哪个参数时那个,但一个参数时没有问题的
     * 解决方法:利用 MyBatis提供的@Param() ,selectProductAll(@Param("offset") long offset, @Param("linit")int limit ) 显式的告诉 MyBatis 谁是谁
     */
    public List selectProductAll(@Param("offset") long offset, @Param("limit")int limit );
}
  • ISuccessKilledDao.java

秒杀明细Dao层操作接口

package org.seckill.dao.interfaces;

import org.apache.ibatis.annotations.Param;
import org.seckill.bean.SuccessKilled;

/**
 * 秒杀详细信息记录Dao操作接口
 * Created by wangxf on 2017/2/13.
 */
public interface ISuccessKilledDao {

    /**
     * 插入购买明细
     * 可以过滤重复,返回插入的行数
     * @param seckillId
     * @param userPhone
     * @return
     */
    public int insertSuccessKilled(@Param("seckillId") long seckillId, @Param("userPhone") long userPhone );

    /**
     * 根据 id 查询 SuccessKilled 并携带秒杀产品对象实体
     * @param seckillId
     * @return
     */
    public SuccessKilled selectByIdWithSeckill(@Param("seckillId") long seckillId, @Param("userPhone") long userPhone );
}

3) 基于 MyBatis 实现DAO

SSM架构之高并发秒杀之DAO详解_第10张图片
MyBatis的作用
SSM架构之高并发秒杀之DAO详解_第11张图片
MyBatis的特点
  • 通过 xml 配置文件来实现 sql 执行语句 (MyBatis也提供注解的方式,但是不提倡使用)。
  • 通过 Mapper 自动实现 DAO 层接口(API 编程的方式实现DAO层接口,但不推荐使用)
SSM架构之高并发秒杀之DAO详解_第12张图片
项目结构
  • **mybatis-config.xml **

mybatis 相关配置




    
    
        
        
        
        
        
        
    

  • SeckillDao.xml

Dao层接口 ISeckillDao.java 接口的实现




    

    

    
        
        
        UPDATE seckill SET number = number -1
        WHERE seckill_id = #{seckillId} AND start_time  #{killTime} AND end_time >= #{killTime} AND number > 0
    

    

    


  • SuccessKilledDao.xml

Dao层接口 ISuccessKilledDao.java 接口的实现




    
    
        
        INSERT ignore INTO success_killed(seckill_id,user_phone,state)
        VALUES (#{seckillId},#{userPhone},0)
    
    

4) MyBatis 整合 Spring

  • 目标:
    • 更少的编码:只写接口,不写实现
    • 更少的配置:达到自动实现DAO、自动的注入到Spring中
    • 足够的灵活:MyBatis能够自由的定制SQL,自由的传参、结果集自动赋值

提倡的整合方式:xml提供sql,DAO 接口Mapper

SSM架构之高并发秒杀之DAO详解_第13张图片
MyBatis-Spring整合流程
  • spring-dao.xml



    
    

    
    
    
    
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
    

    
    
        
        
        
        
        
        
        
        
    

    
    
        
        
        
        
    

5) Junit 单元测试

  • ISeckillDaoTest.java
package org.seckill.dao.interfaces;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.seckill.bean.Seckill;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import javax.annotation.Resource;
import java.util.Date;
import java.util.List;

/**
 * ISeckillDao 接口的测试类
 * Spring 和 junit 整合
 * 目的:视为了让 junit 在启动时加载 Spring IOC 容器
 * 原因:因为 Dao 接口的实现是由 Spring 完成的
 *
 * 实现:通过 JUnit 的@RunWith(SpringJUnit4ClassRunner.class) 接口来加载Spring 的SpringJUnit4ClassRunner
 *       在加载时,用Spring 的ContextConfiguration来加载验证 MyBatis 与 Spring 的整合文件
 * spring-test、junit{}
 * Created by wangxf on 2017/2/22.
 */
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration({"classpath:spring/spring-dao.xml"})
public class ISeckillDaoTest {

    // 注入 dao 实现类
    @Resource
    private ISeckillDao seckillDao;

    /**
     * 库存信息更细测试方法
     * @throws Exception
     */
    @Test
    public void updateProductNumber() throws Exception {
        Date killTime = new Date();
        int updateCount = seckillDao.updateProductNumber(1000L, killTime);
        System.out.println("-------------------------------");
        System.out.println(updateCount);
        System.out.println("-------------------------------");
    }

    /**
     * 通过id查询库存产品信息
     * @throws Exception
     */
    @Test
    public void selectProductById() throws Exception {
        long id = 1000; // id
        Seckill seckill = seckillDao.selectProductById(id); // 获取 seckill对象

        System.out.println("-------------------------------");
        System.out.println(seckill.getName());
        System.out.println(seckill);
        System.out.println("-------------------------------");
    }

    /**
     * 查询库中所有的产品信息
     * @throws Exception
     */
    @Test
    public void selectProductAll() throws Exception {
        List seckillList = seckillDao.selectProductAll(0, 100);
        for (Seckill seckill : seckillList) {
            System.out.println("----------------------------");
            System.out.println(seckill.getName());
            System.out.println(seckill);
            System.out.println("----------------------------");
        }
    }
}
  • ISuccessSeckilledDaoTest.java
package org.seckill.dao.interfaces;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.seckill.bean.SuccessKilled;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import javax.annotation.Resource;

import static org.junit.Assert.*;

/**
 * ISuccessKilledDao 接口的测试类
 * Created by wangxf on 2017/2/23.
 */

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:spring/spring-dao.xml")
public class ISuccessKilledDaoTest {

    @Resource
    private ISuccessKilledDao successKilledDao;

    /**
     * 插入购买明细测试方法
     * @throws Exception
     */
    @Test
    public void insertSuccessKilled() throws Exception {
        long seckillId = 1000L;
        long userPhone = 18779118283L;

        int insertCount = successKilledDao.insertSuccessKilled(seckillId, userPhone);
        System.out.println("-------------------------------");
        System.out.println(insertCount);
        System.out.println("-------------------------------");
    }

    /**
     * 根据 id 查询 SuccessKilled 并携带秒杀产品对象实体 测试类
     * @throws Exception
     */
    @Test
    public void selectByIdWithSeckill() throws Exception {

        long seckillId = 1000L;
        long userPhone = 18779118283L;
        SuccessKilled successKilled = successKilledDao.selectByIdWithSeckill(seckillId,userPhone);
        System.out.println("-------------------------------");
        System.out.println(successKilled.toString());
        System.out.println("-------------------------------");
        System.out.println(successKilled.getSeckill());
        System.out.println("-------------------------------");
    }
}

项目参考:http://www.imooc.com/learn/587

你可能感兴趣的:(SSM架构之高并发秒杀之DAO详解)