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、基本需求分析

秒杀系统基本业务流程
  • 秒杀业务的核心:就是对库存的处理
用户针对库存业务分析
  • 用户的购买行为
用户的秒杀信息
  • 潜在问题分析
可能存在的问题

2、难点分析

  • 竞争

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

  • 事务:开启事务——update 数据库数量更新——insert 购买明细——commit提交(update 库存更新,是资源占用最多的时候)
用户竞争
  • 行级锁
行级锁
  • ** 如何高效的处理竞争 **

3、主要的秒杀功能

秒杀库存系统-天猫
  • 秒杀接口暴露

  • 执行秒杀

  • 秒杀相关查询

4、代码开发

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

三、项目搭建

项目结构图

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

相关配置的官方地址:

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进行单元测试
<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-SNAPSHOT</version>
  <name>seckill Maven Webapp</name>
  <url>http://maven.apache.org</url>
  <dependencies>
    <!-- 一、单元测试 -->
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.10</version>
      <scope>test</scope>
    </dependency>

    <!-- 二、日志:slf4j、logback -->
    <!-- slf4j api -->
    <dependency>
      <groupId>org.slf4j</groupId>
      <artifactId>slf4j-api</artifactId>
      <version>1.7.6</version>
    </dependency>

    <!-- 实现log日志的核心功能 -->
    <dependency>
      <groupId>ch.qos.logback</groupId>
      <artifactId>logback-core</artifactId>
      <version>1.1.1</version>
    </dependency>

    <!-- 实现slf4j,并对其进行整合 -->
    <dependency>
      <groupId>ch.qos.logback</groupId>
      <artifactId>logback-classic</artifactId>
      <version>1.1.1</version>
    </dependency>

    <!-- 三、数据库相关依赖 -->
    <!-- 数据库驱动依赖 -->
    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>5.1.37</version>
      <scope>runtime</scope>
    </dependency>

    <!-- 数据库连接池 -->
    <dependency>
      <groupId>c3p0</groupId>
      <artifactId>c3p0</artifactId>
      <version>0.9.1.2</version>
    </dependency>

    <dependency>
      <groupId>com.alibaba</groupId>
      <artifactId>dubbo</artifactId>
      <version>2.5.3</version>
      <exclusions>
        <exclusion>
          <groupId>org.springframework</groupId>
          <artifactId>spring</artifactId>
        </exclusion>
      </exclusions>
    </dependency>

    <dependency>
      <groupId>com.alibaba</groupId>
      <artifactId>druid</artifactId>
      <version>0.2.19</version>
    </dependency>


    <!-- 四、DAO 层 MyBatis 相关依赖 -->
    <!-- mybatix 自身的依赖 -->
    <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 相关依赖 -->
    <!-- jsp 相关依赖标签 -->
    <dependency>
      <groupId>taglibs</groupId>
      <artifactId>standard</artifactId>
      <version>1.1.2</version>
    </dependency>

    <!-- js 默认依赖标签 -->
    <dependency>
      <groupId>jstl</groupId>
      <artifactId>jstl</artifactId>
      <version>1.2</version>
    </dependency>

    <!-- jackson 依赖标签 -->
    <dependency>
      <groupId>com.fasterxml.jackson.core</groupId>
      <artifactId>jackson-databind</artifactId>
      <version>2.5.4</version>
    </dependency>

    <!-- servlet 依赖的api -->
    <dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>javax.servlet-api</artifactId>
      <version>3.1.0</version>
    </dependency>

    <!-- 六、Spring 相关依赖 -->
    <!-- 1、Spring 核心依赖 -->
    <!-- Spring 核心 -->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-core</artifactId>
      <version>4.1.7.RELEASE</version>
    </dependency>
    <!-- Spring IOC 依赖 -->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-beans</artifactId>
      <version>4.1.7.RELEASE</version>
    </dependency>
    <!-- Spring 扩展依赖 -->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>4.1.7.RELEASE</version>
    </dependency>

    <!-- 2、Spring dao 依赖 -->
    <!-- Spring jdbc 依赖 -->
    <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>

    <!-- 3、Spring web 依赖 -->
    <!-- spring web项目启动时依赖 -->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-web</artifactId>
      <version>4.1.7.RELEASE</version>
    </dependency>
    <!--Spring MVC 依赖 -->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-webmvc</artifactId>
      <version>4.1.7.RELEASE</version>
    </dependency>
    <!-- Spring Test 依赖L: 单元测试时要进行Spring容器的加载-->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-test</artifactId>
      <version>4.1.7.RELEASE</version>
    </dependency>

  </dependencies>
  <build>
    <finalName>seckill</finalName>
  </build>
</project>

2、web.xml

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

<web-app xmlns="http://java.sun.com/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
                      http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
         version="3.0"
         metadata-complete="true">
  <!-- 修改servlet 版本 -->
</web-app>

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

目录结构

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<Seckill> 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

MyBatis的作用
MyBatis的特点
  • 通过 xml 配置文件来实现 sql 执行语句 (MyBatis也提供注解的方式,但是不提倡使用)。
  • 通过 Mapper 自动实现 DAO 层接口(API 编程的方式实现DAO层接口,但不推荐使用)
项目结构
  • **mybatis-config.xml **

mybatis 相关配置

<?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>
        <!-- 使用 jdbc 的getGeneratedKeys 获取数据库的自增主键值 -->
        <setting name="useGeneratedKeys" value="true"/>
        <!-- 使用列的别名来替换列名 默认为true-->
        <setting name="useColumnLabel" value="true"/>
        <!-- 开启驼峰命名转换 -->
        <setting name="mapUnderscoreToCamelCase" value="true"/>
    </settings>
</configuration>
  • SeckillDao.xml

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

<?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.interfaces.ISeckillDao">
    <!-- 目的:为dao接口方法提供sql语句配置 -->

    <!-- 对应实现Dao层接口中的方法,以方法名作为id,类型与返回值等会自动识别 -->

    <update id="updateProductNumber">
        <!-- 具体的update语句 -->
        <!-- 不允许使用 <=(用 <![CDATA[<=]]> 来替代) ,但 >= 可以 -->
        UPDATE seckill SET number = number -1
        WHERE seckill_id = #{seckillId} AND start_time <![CDATA[<=]]> #{killTime} AND end_time >= #{killTime} AND number > 0
    </update>

    <select id="selectProductById" resultType="Seckill" parameterType="long">
        SELECT seckill_id,name,number,start_time,end_time,create_time
        FROM seckill
        WHERE seckill_id = #{seckillId}
    </select>

    <select id="selectProductAll" resultType="Seckill">
        SELECT seckill_id,name,number,start_time,end_time,create_time
        FROM seckill
        ORDER BY create_time DESC
        limit #{offset},#{limit}
    </select>

</mapper>
  • SuccessKilledDao.xml

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

<?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.interfaces.ISuccessKilledDao">
    <!-- 目的:为dao接口方法提供sql语句配置 -->
    <insert id="insertSuccessKilled">
        <!-- 忽略主键冲突 -->
        INSERT ignore INTO success_killed(seckill_id,user_phone,state)
        VALUES (#{seckillId},#{userPhone},0)
    </insert>
    <select id="selectByIdWithSeckill" resultType="SuccessKilled">
        <!-- 根据 id 查询 SuccessKilled 并携带秒杀产品对象实体 -->
        <!-- 告诉MyBatis如何映射到SuccessKilled,同时映射到Seckill属性对象 -->
        <!-- 通过别名告诉MyBatis那些是seckill的属性,MyBatis会自动的将其赋值给Seckill对象,as 可以省略 -->
        <!-- MyBatis 可以自由的控制SQL -->
        SELECT
        sk.seckill_id,
        sk.user_phone,
        sk.create_time,
        sk.state,
        s.seckill_id AS "seckill.seckill_id",
        s.name "seckill.name",
        s.number "seckill.number",
        s.create_time "seckill.create_time",
        s.start_time "seckill.start_time",
        s.end_time "seckill.end_time"
        FROM success_killed sk INNER JOIN seckill s ON sk.seckill_id = s.seckill_id
        WHERE sk.seckill_id = #{seckillId} AND sk.user_phone = #{userPhone}
    </select>
</mapper>

4) MyBatis 整合 Spring

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

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

MyBatis-Spring整合流程
  • spring-dao.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
                           http://www.springframework.org/schema/beans/spring-beans.xsd
                           http://www.springframework.org/schema/context
                           http://www.springframework.org/schema/context/spring-context.xsd">
<!--
    配置整合MyBatis-Spring过程
-->
    <!-- 1、配置数据库相关配置 properties 的属性:${url}-->
    <context:property-placeholder location="classpath:jdbc.properties"/>

    <!-- 2、配置数据库的连接池 -->
    <!-- com.alibaba.druid.pool.DruidDataSource -->
    <!-- com.mchange.v2.c3p0.ComboPooledDataSource -->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
        <!-- 配置连接池属性信息 -->
        <!--
        <property name="driverClass" value="${driverClass}"/>
        <property name="jdbcUrl" value="${url}"/>
        <property name="user" value="${username}"/>
        <property name="password" value="${password}"/>
        -->
        <!-- 配置链接池的c3p0私有属性 -->
        <!-- 每个数据库连接池的最大链接量 -->
        <!--
        <property name="maxPoolSize" value="30"/>
        -->
        <!-- 每个数据库连接诶吃的最小链接量设置 -->
        <!--
        <property name="minPoolSize" value="10"/>
        -->
        <!-- 在调用close方法时,对数据库不进行清理 -->
        <!--
        <property name="autoCommitOnClose" value="false"/>
        -->
        <!-- 当数据库连接池满了之后,等待连接池的等待时间 -->
        <!--
        <property name="checkoutTimeout" value="1000"/>
         -->
        <!-- 当链接失败后的重试次数 -->
        <!--
        <property name="acquireRetryAttempts" value="2"/>
        -->
        <property name="driverClassName" value="${master.jdbc.driverClassName}" />
        <property name="url" value="${master.jdbc.url}" />
        <property name="username" value="${master.jdbc.username}" />
        <property name="password" value="${master.jdbc.password}" />
        <property name="initialSize" value="${master.jdbc.initialSize}" />
        <property name="maxActive" value="${master.jdbc.maxActive}" />
        <property name="filters" value="stat" />
        <property name="maxWait" value="60000" />
        <property name="minIdle" value="10" />
        <property name="timeBetweenEvictionRunsMillis" value="3000" />
        <property name="minEvictableIdleTimeMillis" value="300000" />
        <property name="validationQuery" value="SELECT 'x'" />
        <property name="testWhileIdle" value="true" />
        <property name="testOnBorrow" value="false" />
        <property name="testOnReturn" value="false" />
        <property name="poolPreparedStatements" value="false" />
        <property name="maxPoolPreparedStatementPerConnectionSize" value="100" />
        <property name="removeAbandoned" value="true" />
        <property name="removeAbandonedTimeout" value="18000" />
        <property name="logAbandoned" value="true" />
    </bean>

    <!-- 3、配置 SqlSessionFactory 对象——重点 -->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <!-- 注入数据库连接池,ref 来关联到2、配置中的dataSource -->
        <property name="dataSource" ref="dataSource"/>
        <!-- 配置MyBatis全局配置文件:mybatis-config.xml -->
        <property name="configLocation" value="classpath:mybatis-config.xml"/>
        <!-- 扫描 bean 包,使用别名(org.seckill.bean.Seckill——>Seckill),可以达到减少配置的目的,
             当有多个bean时,value="org.seckill.bean;org.seckill.bean2" 用“;”分割-->
        <property name="typeAliasesPackage" value="org.seckill.bean"/>
        <!-- 扫描 sql 配置文件,即 mapper 需要的xml配置文件 ,从而达到减少配置的目的-->
        <property name="mapperLocations" value="classpath:mapper/*.xml"/>
    </bean>

    <!-- 4、配置扫描dao接口,目的:动态实现dao接口,并注入到Spring容器中,真正的达到减少配置的目的,不需要配置id,因为此配置不需要在其他地方引用 -->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <!-- 注入SqlSessionFactory ,此处用sqlSessionFactoryBeanName是为了防止在还没有初始化sqlSessionFactory时就初始化,即,为了避免提前 初始化sqlSessionFactory -->
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
        <!-- 给出需要扫描的 Dao 接口包 -->
        <property name="basePackage" value="org.seckill.dao"/>
    </bean>
</beans>

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<Seckill> 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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,874评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,102评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,676评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,911评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,937评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,935评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,860评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,660评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,113评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,363评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,506评论 1 346
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,238评论 5 341
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,861评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,486评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,674评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,513评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,426评论 2 352

推荐阅读更多精彩内容

  • 一步一步的搭建JAVA WEB项目,采用Maven构建,基于MYBatis+Spring+Spring MVC+B...
    叶子的翅膀阅读 12,661评论 5 25
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,649评论 18 139
  • 前言 本篇将完成DAO层的设计与开发,包括: 数据库、DAO实体与接口设计与编码 基于MyBatis实现DAO编程...
    MOVE1925阅读 1,354评论 0 4
  • 之前写了一个用SSM框架搭建的商品查询系统,分两篇文章分别记录了自己整合SSM框架的过程以及利用SSM开发的一些基...
    codingXiaxw阅读 5,154评论 7 29
  • 前几天出门,住处的窗帘是欧洲田园式的小碎花,映衬着水蓝色的壁纸,自有一种清新少女的甜美气息,我和舞相视而笑,然后拿...
    哞儿哞儿阅读 261评论 0 0