Java高并发秒杀Api-业务分析与DAO层构建2
2018-05-05 本文已影响227人
markfork
章节目录
- 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.png5.DAO层接口设计与SQL编写的反思
DAO - data access object 数据访问层的简称
这一层我们主要做的是核心数据操作的接口声明,以及SQL编写,并没有涉及到Service 业务逻辑层代码的编写。这样做的好处是什么呢?
主要有以下三点
- 代码分层,DAO层只关注于核心数据操作的接口声明&SQL编写
- 代码和SQL的分离,方便代码review
- 业务逻辑层主要做事务控制、事务中完成DAO层的代码组合与拼接。每一层都可以做单元测试。
接下来详解mybatis 与 spring 整合编码的过程