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 设计编码
三、项目搭建
多查看官方文档,而不是仅仅通过搜索引擎,因为很多博客等技术类的文档都从在这过时以及不完善性,而官方文档不仅新且全。
相关配置的官方地址:
- 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进行单元测试
<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
- 通过 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
- 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("-------------------------------");
}
}