文章目录
- 1. 项目概述
- 1.1 SSM 框架优点
- 1.2 相关技术介绍
- 1.3 秒杀业务分析
- 1.3.1 秒杀系统业务流程
- 1.3.2 秒杀业务的核心:对库存的处理
- 1.3.3 为什么需要事务
- 1.3.4 关于数据落地
- 1.4 MySQL 实现秒杀难点分析
- 1.5 实现哪些秒杀功能
- 2. 创建项目和依赖
- 2.1 创建前的说明
- 2.2 开始创建项目
- 2.3 导入依赖
- 3. 数据库设计编码
- 4. DAO 层实体和接口编码
- 4.1 编写 entity 实体类
- 4.1.1 Seckill 实体类
- 4.1.2 SuccessKilled 类
- 4.2 设计 DAO 接口
- 4.2.1 SeckillDao 接口
- 4.2.2 SuccessKilledDao 接口
1. 项目概述
1.1 SSM 框架优点
- 框架易于使用和轻量级
- 低代码侵入性
- 成熟的社区和用户群
1.2 相关技术介绍
1.MySQL
- 表设计
- SQL 技巧
- 事务和行级锁
2.MyBatis
- DAO 层设计与开发
- MyBatis 合理使用
- MyBatis 与 Spring 整合
3.Spring
- Spring IOC 整合 Service
- 声明式事务运用
4.SpringMVC
- Restful 接口设计和使用
- 框架运作流程
- Controller 开发技巧
5.前端
- 交互设计
- Bootstrap
- jQuery
6.高并发
- 高并发点和高并发分析
- 优化思路并实现
1.3 秒杀业务分析
1.3.1 秒杀系统业务流程
1.3.2 秒杀业务的核心:对库存的处理
1.3.3 为什么需要事务
1.3.4 关于数据落地
MySQL VS NoSQL
NoSQL:对事务支持不是很好,追求的是性能、高可用、分布式
MySQL:内置的事务机制依然是目前最有效、最可靠的数据落地方案 -> 可以更可靠的记录减库存、记录购买行为
1.4 MySQL 实现秒杀难点分析
难点问题: 竞争 -> 事务 +行级锁
事务工作机制:
- Start Transaction
- Update 库存数量 -> 竞争出现在此处
- Insert 购买明细
- Commit
行级锁 -> 伴随着竞争
如何高效的处理竞争:高并发
1.5 实现哪些秒杀功能
- 秒杀接口暴露
- 执行秒杀
- 相关查询: 列表查询、详情页查询
代码开发阶段
- DAO 设计编码 :数据库表设计、DAO 接口、MyBatis 如何实现 DAO
- Service 设计编码:
Service 接口设计及编码实现、通过 Spring 管理 Service、通过 Spring 声明式事务标注 Service 方法是一个事务操作简化事务控制 - Web 设计编码:Restful 接口、 前端交互
2. 创建项目和依赖
2.1 创建前的说明
- 从零开始创建
- 从官网获取相关配置
1.文档更全面权威
2.避免过时或错误 - 使用 Maven 创建项目
2.2 开始创建项目
- 新建 Maven 项目
Maven 安装及其在 IDEA 中的配置和使用 (保姆级教程)
- 新建 Web 项目
Java 学习【框架篇(三)】SpringMVC(一)回顾 MVC 架构 & Servlet
结果如下:
2.3 导入依赖
<?xml version="1.0" encoding="UTF-8"?>
<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/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.seckill</groupId>
<artifactId>seckill</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<!-- 使用 junit4(3.0 默认使用编程的方式,4.0 以后用注解方式运行 junit)-->
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<!-- 补全项目依赖 -->
<!-- 1:日志 java 日志:slf4j, log4j, logback, common-logging
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.1</version>
</dependency>
<!-- 实现 slf4j 接口并整合 -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.1.1</version>
</dependency>
<!-- 2. 数据库相关依赖 -->
<!-- mysql 驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.35</version>
<scope>runtime</scope>
</dependency>
<!-- 数据库连接池-->
<dependency>
<groupId>com.mchange</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.5.2</version>
</dependency>
<!-- 3. DAO 框架:MyBatis 依赖 -->
<!-- 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>
<!-- 3. 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>
<!-- SpringMVC 默认需要 jackson -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.5.4</version>
</dependency>
<!-- servlet -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
</dependency>
<!-- 4. Spring 依赖-->
<!-- 1)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>
<!-- 2) 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>
<!-- 3) Spring web 相关依赖-->
<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>
<!-- 4) Spring test 相关依赖(用于做单元测试) -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>4.1.7.RELEASE</version>
</dependency>
</dependencies>
</project>
3. 数据库设计编码
- main 下新建 schema.sql
schema.sql 中 SQL 语句如下:
-- 数据库初始化脚本
-- 创建数据库
CREATE DATABASE `seckill`;
-- 使用数据库
use `seckill`;
-- 创建秒杀库存表
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 DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
-- 设计索引
-- idx 为 index 简称
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, '2015-11-01 00:00:00', '2015-11-02 00:00:00'),
('500 元秒杀 ipad2', 200, '2015-11-01 00:00:00', '2015-11-02 00:00:00'),
('300 元秒杀小米 4', 300, '2015-11-01 00:00:00', '2015-11-02 00:00:00'),
('200 元秒杀红米 note', 400, '2015-11-01 00:00:00', '2015-11-02 00:00:00');
-- 秒杀成功明细表
-- 用户登录认证相关的信息
create table `success_killed`(
`seckill_id` bigint NOT NULL COMMENT '秒杀商品 id',
`user_phone` bigint NOT NULL COMMENT '用户手机号',
`state` tinyint NOT NULL DEFAULT -1 COMMENT '状态标识: -1: 无效 0: 成功 1: 已付款 2: 已发货',
`create_time` timestamp NOT NULL COMMENT '创建时间',
PRIMARY KEY (`seckill_id`, `user_phone`), -- 联合主键
key idx_create_time(create_time)
)ENGINE = InnoDB DEFAULT CHARSET = utf8 COMMENT '秒杀成功明细表';
修改数据库字段属性:
ALTER TABLE `success_killed` MODIFY `create_time` datetime;
显示表是怎么创建的:
show CREATE TABLE `success_killed`;
CREATE TABLE `success_killed` (
`seckill_id` bigint(20) NOT NULL COMMENT '秒杀商品 id',
`user_phone` bigint(20) NOT NULL COMMENT '用户手机号',
`state` tinyint(4) NOT NULL DEFAULT '-1' COMMENT '状态标识: -1: 无效 0: 成功 1: 已付款 2: 已发货',
`create_time` timestamp DEFAULT NULL,
PRIMARY KEY (`seckill_id`,`user_phone`),
KEY `idx_create_time` (`create_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='秒杀成功明细表'
结果如下:
4. DAO 层实体和接口编码
tabel 和 entity 的对应关系
-
表
对应 Java 中的实体类
-
列
对应实体中的属性
创建实体包 & DAO 层的包
org.seckill.entity
org.seckill.dao
4.1 编写 entity 实体类
4.1.1 Seckill 实体类
package org.seckill.entity;
import java.util.Date;
public class Seckill {
private long seckillId;
private String name;
private int number;
private Date startTime;
private Date endTime;
private Date createTime;
// Alt + insert
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 getNumber() {
return number;
}
public void setNumber(int number) {
this.number = number;
}
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 + '\'' +
", number=" + number +
", startTime=" + startTime +
", endTime=" + endTime +
", createTime=" + createTime +
'}';
}
}
4.1.2 SuccessKilled 类
package org.seckill.entity;
import java.util.Date;
public class SuccessKilled {
private long skillId;
private long userPhone;
private short state;
private Date createTime;
// 变通
// 多对一(复合属性): 一个 Seckill 实体对应多个秒杀成功的记录
private Seckill seckill;
public long getSkillId() {
return skillId;
}
public void setSkillId(long skillId) {
this.skillId = skillId;
}
public long getUserPhone() {
return userPhone;
}
public void setUserPhone(long 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{" +
"skillId=" + skillId +
", userPhone=" + userPhone +
", state=" + state +
", createTime=" + createTime +
'}';
}
}
4.2 设计 DAO 接口
4.2.1 SeckillDao 接口
命名规范: 对应的实体 + Dao -> 如:SeckillDao
package org.seckill.dao;
import org.apache.ibatis.annotations.Param;
import org.seckill.entity.Seckill;
import java.util.Date;
import java.util.List;
public interface SeckillDao {
// 减库存 (killTime 执行减库存的时间 对应数据库中的 creatTime)
// 如果影响行数 >1, 表示更新的记录行数
int reduceNumber(@Param("seckillId") long seckillId, @Param("killTime") Date killTime);
// 查询需求(根据 id 查询秒杀对象)
Seckill queryById(long seckillId);
// 根据偏移量查询秒杀商品列表
// offset:偏移量, limit:在偏移量之后取多少条记录
// java 编程语言的问题:
// java 没有保存形参的记录:queryAll(int offset, int limit) ->java 运行时参数变为 queryAll(arg0,arg1)
// 一个参数时没有问题,当有多个参数时要告诉 MyBatis 哪个参数叫什么名字 这样在 xml 里通过 #{} 提取参数时 MyBatis 才能找到具体值
// 具体做法:修改接口 -> 增加 @Param 注解 说明形参是什么
List<Seckill> queryAll(@Param("offset") int offset, @Param("limit") int limit);
}
4.2.2 SuccessKilledDao 接口
package org.seckill.dao;
import org.apache.ibatis.annotations.Param;
import org.seckill.entity.SuccessKilled;
public interface SuccessKilledDao {
// 插入购买明细,可过滤重复(联合唯一主键)
// 插入的行数
int insertSuccessKilled(@Param("seckillId") long seckillId, @Param("userPhone") long userPhone);
// 根据 id 查询 SuccessKilled 并携带秒杀产品对象实体
SuccessKilled queryByIdWithSeckill(@Param("seckillId") long seckillId, @Param("userPhone") long userPhone);
}