Java高并发秒杀API之业务分析与DAO层
本文主要是针对慕课网的Java高并发秒杀API之业务分析与DAO层这门课程内容做一些整理,这是实现秒杀系统的第一门课程。
1. 相关技术介绍
2. 基于maven创建web骨架项目
2.1 用命令行创建maven项目
1.创建项目
mvn archetype:generate -DgroupId=org.seckill -DartifactId=seckill -DarchetypeArtifactId=maven-archetype-webapp
[INFO] Generating project in Interactive mode (这里会卡一会儿,因为要联网获取项目模板)
[INFO] No archetype defined. Using maven-archetype-quickstart (org.apache.maven.archetypes:maven-archetype-quickstart:1.0)
Choose archetype:
1: remote -> br.com.ingenieux:elasticbeanstalk-service-webapp-archetype (A Maven Archetype Encompassing RestAssured, Jetty, Jackson, Guice and Jersey for Publishing JAX-RS-based Services on AWS' Elastic Beanstalk Service)... (这里会自动列出很多项目模板,每种模板前面会有一个数字序号)
336: remote -> org.apache.maven.archetypes:maven-archetype-quickstart (An archetype which contains a sample Maven project.)...Choose a number or apply filter (format: [groupId:]artifactId, case sensitive contains): 336: (这里根据每个人机器上安装的eclipse插件不同,可能默认的数字不是这个,先不管,直接回车)
Choose org.apache.maven.archetypes:maven-archetype-quickstart version:
1: 1.0-alpha-12: 1.0-alpha-23: 1.0-alpha-34: 1.0-alpha-45: 1.06: 1.1Choose a number: 6: (直接回车)
Define value for property 'groupId': : cnblogs (可暂时先理解成类似package或namespace的名称,通常我们填写组织机构名称缩写)
Define value for property 'artifactId': : maven-hello-world (组件名称,可暂时理解成项目名称)
Define value for property 'version': 1.0-SNAPSHOT: : (版本号,直接回车,默认1.0-SNAPSHOT)
Define value for property 'package': cnblogs: : (打包后的jar文件名,相当于.net中项目最后生成的程序集dll名称)
Confirm properties configuration:groupId: cnblogsartifactId: maven-hello-worldversion: 1.0-SNAPSHOTpackage: cnblogs Y: : (直接回车确认)
[INFO] ----------------------------------------------------------------------------
[INFO] Using following parameters for creating project from Old (1.x) Archetype: maven-archetype-quickstart:1.1
[INFO] ----------------------------------------------------------------------------
[INFO] Parameter: groupId, Value: cnblogs[INFO] Parameter: packageName, Value: cnblogs
[INFO] Parameter: package, Value: cnblogs[INFO] Parameter: artifactId, Value: maven-hello-world...
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS (看到这个,表示项目创建成功!)
[INFO] ------------------------------------------------------------------------...
这里是摘自一篇博主的文章,但是链接忘记保存了,如有侵权请联系我。
2.修改信息
- 增加servlet依赖
- 提高servlet版本,由于2.3版本太低,jsp默认的el表达式不工作
- 打开tomcat下的webapps/examples/WEB-INF目录下的web.xml进行修改
- 用3.1版本的头替换源项目web.xml文件[外链图片转存失败
- 新建目录
- 将pom.xml中junit的依赖变成4.11版本,3.0的版本默认使用编程,4.0的版本支持注解
- junit依赖是单元测试时所需要的
- 补全项目依赖
- 1.日志:slf4j log4j logback common-logging
- slf4j 是规范/接口
- 日志实现:log4j,logback,common-logging
- 使用:slf4j+logback
- 通过logback实现slf4j规范
-
- 数据库相关的依赖
- 数据库链接驱动
- 数据库链接池:优化数据库操作
-
- DAO框架:MyBatis依赖(数据持久化层)
- 3.1 mybatis自身依赖
- 3.2 mybatis自身实现的spring整合依赖
-
- Servlet web相关依赖
- 4.1 servlet两个js标签库
- 4.2 jackson API
- 4.3 servlet 依赖
- 5.spring依赖
- 5.1 spring核心依赖,核心api:core、beans(ioc)、context(扩展)
- 5.2 spring DAO层的依赖
- 5.3 spring web相关依赖
- web工程要通过servlet容器来启动,比如Tomact、jetty,启动时sevlet容器要加载spring ioc和aop来启动spring的工厂,所以需要spring-web的api提供支持
- web工程要通过servlet容器来启动,比如Tomact、jetty,启动时sevlet容器要加载spring ioc和aop来启动spring的工厂,所以需要spring-web的api提供支持
- 5.4 spring test相关依赖 :方便做单元测试
- 5.1 spring核心依赖,核心api:core、beans(ioc)、context(扩展)
- 1.日志:slf4j log4j logback common-logging
3. 秒杀业务分析
3.1 秒杀系统业务流程
秒杀业务的核心 -> 库存的处理
事务机制依然是目前最可靠的落地方案
3.2MySQL实现秒杀难点分析
难点问题–竞争
事务:
- Start Transaction
- Update库存数量
- Insert购买明细
- Commit
行级锁:
3.3 实现哪些秒杀功能
4. DAO层设计与开发
4.1 数据库设计与编码
- 在src/main下新建一个sql文件夹,里面新建一个schema.sql文件
- 编写sql语句
--数据库初始化脚本
--创建数据库
CREATE DATABASE seckill;
--使用数据库
use seckill;
--创建秒杀库存表
CREATE TABLE seckill(
--主键(自增id),最大的int类型,自增列,注释
`seckill_id` bigint NOT NULL AUTO_INCREMENT COMMENT '商品库存id',
--库存名字
`name` varchar(120) NOT NULL COMMENT '商品名称',
--库存,int最大21
`number` int NOT NULL COMMENT '库存数量',
--库存的开始时间
`start_time` timestamp NOT NULL COMMENT '秒杀开启时间',
--秒杀结束时间,不管库存有没有都要结束
`end_time` timestamp NOT NULL COMMENT '秒杀结束时间',
--创建时间,DEFAULT CURRENT_TIMESTAMP默认值
`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='秒杀库存表';
--只有InnoDB是支持事务的引擎
--此数据库采用自增,所以要给出初始自增id为1000
--初始化数据
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元秒杀ipd','200','2015-11-01
00:00:00','2015-11-02 00:00:00'),
('300元秒杀小米','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='秒杀成功明细表';
--连接数据库控制台
--连接本地MySQL数据库,-p 密码为空
mysql -uroot -p
--为什么手写DDL?
--记录每次上线的DDL修改
--上线一个新的版本v1.1
ALTER TABLE seckill
DROP INDEX idx_create_time,
add index idx_c_s(start_time,create_time);
3.登录数据库
- 下载MySQL
- https://www.runoob.com/mysql/mysql-install.html
- 用管理员身份打开cmd
- 转到安装目录bin
- 初始化数据库:mysqld --initialize --console
- 安装数据库:mysqld install
- 启动服务器:net start mysql
- 修改密码:SET PASSWORD FOR root@localhost=‘123456’;
- 登录服务器:mysql -u root -p 密码:1234567
- 输入schema.sql文件里的命令
4.2 DAO实体和接口编码
通过Table对应到java实体Entity的概念
表对应java实体类
表中的列对应实体的属性
- 在main/java中创建两个包为org.seckill.entity和org.seckill.dao分别为实体包和dao层的包
- 创建事务:在entity中创建类为Seckill
- 定义完变量直接选中右键source->Generate getters and setters生成
- 右键source->Generate toString( ) 生成复写方法
- 同样的方法创建SuccessKilled类
- 在dao下创建Seckill的接口SeckillDao,里面放置三个接口:reduceNumber、queryById和queryAll
- 在dao下创建SuccessKilled的接口SuccessKilledDao,里面有insertSucessKilled、queryByIdWithSeckill
4.3 基于myBatis实现DAO
MyBatis用来作什么?
MyBatis特点
- 参数:需要自己提供(基本类型、对象、实体…)
- SQL(完全自己完成)
- entity/list完成封装
MyBatis怎么用?
- SQL写在哪?
- XML提供SQL(推荐使用)
- 注解提供SQL
- 如何实现DAO接口?
- Mapper自动实现DAO接口(推荐使用)
- API编程方式实现DAO接口
4.4 基于mybatis实现DAO编程
- 在main/resources下建一个xml文件(mybatis全局映射文件),头文件是mybatis官方文档中入门的配置
- 在main/resources下建一个mapper目录(放置mybatis的数据库映射)
- 在main/resources/mapper下创建一个seckilldao的xml文件,头文件为xml配置例子里的头
- 在main/resources/mapper下创建一个successkilleddao的xml文件
4.5 MyBatis整合Spring
更少的编码:只写接口,不写实现
只需要知道类名就行,实现自动扫描
5.6 mybatis整合Spring编码
- 在main/resources下建一个spring目录(放置spring相关配置)
- spring-dao.xml文件:dao相关配置
- https://docs.spring.io/spring/docs/4.1.7.RELEASE/spring-framework-reference/找到pdf官方文档下载
- 找到5.2容器相关,复制头文件
- 配置spring时出现一个问题idea application context not configured for this file,只需要file->project structure->modules->spring将其配置文件加上就好
- 新建jdbc.properties文件
5.7 单元测试与问题排查
- 所有测试在test下的java文件夹中
- 先对seckillDao进行测试,点击接口名称:Ctrl+shift+t --> create new Test,勾选junit4,勾选下面三个方法
运行中出现的一些问题:
https://blog.csdn.net/qq_28289405/article/details/82698856
https://www.cnblogs.com/ampl/p/10454721.html
记录调bug的路程:
-
jdbc.properties文件要放在resources下,否则会报“连接不到数据库”的错误
-
sql版本要和pom文件中的mysql版本一致
-
junit要在4.12以上版本
-
jdbc.properties文件中的语句:
- jdbc.url =
jdbc:mysql://localhost:3306/seckill?useUnicode=true&characterEncoding=utf8&serverTimezone=UTC
- jdbc.url =
-
MySQL jdbc 6.0 版本以上必须配置“serverTimezone”参数 UTC代表的是全球标准时间
-
多个参数时会报错:Parameter ‘offset’ not found.因为java不保存形参,所以要在定义的方法中将其变成
- List queryAll(@Param(“offset”) int offset, @Param(“limit”) int limit);
- 这样能保证通过他找到真正的参数
sqlyog最开始连接时报错:1251
解决方法:https://blog.csdn.net/xxydzyr/article/details/90759013
**Error attempting to get column ‘create_time’ from result set. **
解决方法:在jdbc配置后面添加 zeroDateTimeBehavior=convertToNull,当遇到DATETIME值完全由0组成时将这个值转换为null(convertToNull)