二、高并发秒杀API之Dao层设计与实现
来源:互联网 发布:java smb 木马 编辑:程序博客网 时间:2024/06/16 06:11
源码参照:并发系统源码
1 业务分析
此部分见:业务分析
2 创建Maven项目
该秒杀系统是一个基于Maven管理的项目,一般用到的资源建议从官网获取,会更加全面和权威,避免过时和错误。
首先,用Eclipse创建Maven项目,详细创建过程见链接:Maven项目手动创建和自动创建
(1)完善目录结构。创建文件夹,得到如下目录结构。
(2)web.xml文件改变servlet版本,相关问题见
(3)pom.xml文件引入依赖
3 数据库设计与实现
数据库构建主要是两个表的创建:秒杀表、明细表。这里主要使用手写DDL(Data Definition Language)来创建,
--记录每次上线的DDL修改--上线V1.1ALTER TABLE seckillDROP INDEX idx_create_time,ADD index idx_c_s(start_time,create_time);--上线v1.2--ddl --数据库初始化脚本 --创建数据库CREATE databaseseckill;--使用数据库useseckill;--创建秒杀库存表,只有InnoDB数据库支持事务CREATE TABLE seckill(seckill_idBIGINT NOT NULL AUTO_INCREMENT COMMENT '商品库存id',name VARCHAR(120) NOT NULL COMMENT '商品名称',numberint NOT NULL COMMENT '库存数量',/*默认时间戳要在自定义的时间前面,否则会出错*/create_timeTIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',start_timeTIMESTAMP NOT NULL COMMENT '秒杀开启时间',end_timeTIMESTAMP NOT NULL COMMENT '秒杀结束时间',PRIMARY KEY (seckill_id),keyidx_start_time(start_time),keyidx_end_time(end_time),keyidx_create_time(create_time))ENGINE=InnoDBAUTO_INCREMENT=1000 DEFAULTCHARSET=utf8 COMMENT='秒杀库存表'; --初始化数据insert into seckill(name,number,start_time,end_time)values('1000元秒杀iphone6',100,'2016-07-07 00:00:00','2016-07-08 00:00:00'),values('500元秒杀ipad2',200,'2016-07-07 00:00:00','2016-07-08 00:00:00'),values('400元秒杀小米note4',300,'2016-07-07 00:00:00','2016-07-08 00:00:00'),values('200元秒杀红米note',300,'2016-07-07 00:00:00','2016-07-08 00:00:00'); --秒杀成功明细表--用户登录认证相关信息create table success_killed(seckill_idBIGINT not null COMMENT '秒杀商品id',user_phoneBIGINT not null COMMENT '用户手机号',statetinyint not null DEFAULT -1 COMMENT '状态标识:-1:无效 0:成功 1:已付款',create_timetimestamp not null COMMENT '创建时间',PRIMARY key(seckill_id,user_phone),/*联合主键*/keyidx_create_time(create_time))ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='秒杀成功明细表';
4 Dao层设计开发
4.1 Entity实体和Dao接口编码
创建完数据库表
表(table)----对应---》实体(Entity), 表中的列(column)----对应---》实体中的属性(property)。
同时也可以变通一下:用多对一的关系。
4.1.1Entity
创建实体类Seckill.java
public class Seckill { private long seckillId; private String name; private int number; private Date createTime; private Date startTime; private Date endTime; 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 getCreateTime() { return createTime; } public void setCreateTime(Date createTime) { this.createTime = createTime; } 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; } @Override public String toString() { return "Seckill [seckillId=" + seckillId + ", name=" + name + ", number=" + number + ", createTime=" + createTime + ", startTime=" + startTime + ", endTime=" + endTime + "]"; } }
SuccessKilledDao.java
public class SuccessKilled { private long seckilled; private long userPhone; private short state; private Date createTime; //多对一的复合属性 private Seckill seckill; public long getSeckilled() { return seckilled; } public void setSeckilled(long seckilled) { this.seckilled = seckilled; } 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 [seckilled=" + seckilled + ", userPhone=" + userPhone + ", state=" + state + ", createTime=" + createTime + ", seckill=" + seckill + "]"; }}
4.1.2DAO
SeckillDao.java
public interface SeckillDao { /** * 减库存 * @param seckillId * @param killTime * @return如果影响行数>1,表示更新行数 */ int reduceNumber(@Param("seckillId") long seckillId,@Param("killTime") Date killTime); /** * 根据id查询秒杀库存 * @param seckillId * @return插入的行数 */ Seckill queryById(long seckillId); /** * 根据偏移量查询秒杀列表 * @param offset * @param limit * @return * 唯一形参自动赋值 * 当有多个参数的时候要指定实际的形参名称赋值,不然找不到对应值,因为Java并没有保存形参的记录 *java在运行的时候会把List<Seckill> queryAll(int offset,intlimit);中的参数变成这样:queryAll(int arg0,int arg1),这样我们就没有办法去传递多个参数 */ List<Seckill> queryAll(@Param("offset")int offset,@Param("limit")int limit);}
SuccessKilledDao.java
public interface SuccessKilledDao { /** * 插入购买明细,过滤重复(联合唯一主键) * @param seckillId * @param userPhone * @return */ int insertSuccessKilled(@Param("seckillId") long seckillId,@Param("userPhone") long userPhone); /** * 根据Id查询SuccessKilled并携带秒杀产品对象实体。 * @param seckillId * @return */ SuccessKilled queryByIdWithSeckill(@Param("seckillId") long seckillId,@Param("userPhone") long userPhone);}
4.2 基于Mybatis的Dao层实现
设计好了Database和Dao层,剩下的就是需要一个映射关系来链接两者,将数据和对象对应起来,这时就需要Mybatis或者Hibernate来完成任务,“关系型对象映射--- ORM(Object/RelationshipMapping)”的名称也是由此而来。
Mybatis特点:
参数需要提供+SQL需要自己写(比较灵活)=Entity/List;
SQL文件可以写在xml文件或注解当中,实际使用当中提倡使用xml配置文件来写SQL,避免修改重新编译类等。
如何实现DAO接口?
第一种方式:通过Mapper自动实现Dao层接口(推荐使用),将执行结果集封装成我们想要的方式;
第二种方式:通过API编程的方式实现Dao接口。
Xml配置文件:
参照官网相关标准配置:点击打开链接
4.2.1Mybatis总配置文件
配置文件 mybatis-config.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> <!--使用jdbc的getGeneratekeys获取自增主键值--> <setting name="useGeneratedKeys" value="true"/> <!--使用列别名替换列名 默认值为true select name as title(实体中的属性名是title) form table; 开启后mybatis会自动帮我们把表中name的值赋到对应实体的title属性中 --> <setting name="useColumnLabel" value="true"/> <!--开启驼峰命名转换Table:create_time到 Entity(createTime)--> <!-- 驼峰命名规范:Mybatis会自动转化列属性 --> <setting name="mapUnderscoreToCamelCase" value="true"/> </settings> </configuration>
<!-- 在mybatis官方文档的入门-全局配置-有mybatis的标准配置 -->
4.2.2Dao配置文件
保证Dao配置文件和Dao层类名相同,形成一种规范。
SeckillDao.xml文件,为Dao层定义的接口提供sql语句,Dao层的接口中的参数可以自动被Mybatis绑定到配置文件对应的Sql语句里面的参数,文件内容如下:
<?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> <!-- 目的:为Dao层接口方法提供sql语句配置 --> <!-- 为第一个方法提供sql语句 --> <update id="reduceNumber"> <!-- 具体sql,返回影响的行数 --> update seckill set number=number-1 where seckill_id=#{seckill_Id} and start_time <![CDATA[<=]]> #{killTime} and end_time<![CDATA[>=]]> #{killTime} and number>0; </update> <select id="queryById"resultType="Seckill" parameterType="long"> selectseckill_id,name,number,create_time,start_time,end_time from seckill where seckill_id=#{seckillId} </select> <select id="queryAll"resultType="Seckill"> selectseckill_id,name,number,create_time,start_time,end_time from seckill order by create_time desc limit #{offset},#{limit} <!-- 在偏移量之后取的行数 --> </select> </mapper>
配置文件里面的SQL 语句和Dao层的接口定义的方法一一对应,SQL里面用到的属性和数据库中属性名相同,SQL语句里面的参数名和接口定义方法的形参保持相同。
SuccessSeckilledDao.xml配置
<!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"> <!-- 使用ignore在主键冲突的时候报错返回0,避免插入数值 --> insert ignore intosuccess_killed(seckill_id,user_phone,state) values(#{seckillId},#{userPhone},0) </insert> <select id="queryByIdWithSeckill"resultType="SuccessKilled"> <!-- 根据Id查询SuccessKilled并携带秒杀产品对象实体。 --> <!-- 如何告诉Mybatis把结果映射到SuccessKilled同时映射seckill属性 --> <!--级联处理:通过别名告诉Mybatis怎么映射到seckill实体里面的属性 --> select sk.seckill_id, sk.user_phone, sk.create_time, sk.state, s.seckill_id"seckill.seckill_id", s.name "seckill.name", s.number "seckill.number", s.start_time"seckill.start_time", s.end_time"seckill.end_time", s.create_time"seckill.create_time" from success_killed sk inner join seckill s onsk.seckill_id=s.seckill_id where sk.seckill_id=#{seckillId} andsk.user_phone=#{userPhone} </select></mapper>
4.2.3整合Mybatis+Spring
将两者整合是为了更好的优化代码,整合目标是为了更少的编码、更少的配置、足够的灵活。
首先,更少的编码:
使用Mybatis只用写接口,不用写接口的实现方法,Mybatis会自动帮忙实现接口。
看下面的一个接口与SQL语句的对应关系:
这样可以更少的编码;
其次,更少的配置:
当有很多Dao的映射文件的时候,则需要配置很多的<mapperresource=’”mapper/SeckillDao.xml”/>……,这样会比较麻烦,对于开发人员来说是一个很重的配置维护成本,这时候使用Mybatis可以自动扫描某一个目录下的所有以xml结尾的文件,这样就有了更少的配置。
另外,当有很多Dao的时候,需要配置对应的 <bean id=”” class=””/>,使用mybatis则不用配置这些东西,因为mybatis可以自动实现Dao接口,自动注入Spring容器。
这体现出了使用Mybatis可以更少的对文件进行配置。
最后,足够的灵活:
使用Mybatis就是因为可以自由定制SQL并且传参,结果可以实现自动赋值,这也是为什么Mybatis为什么如此受欢迎的原因,整合了Spring的Mybatis仍然保持了足够的灵活性,当我们使用这两个技巧的时候:XML提供SQL语句和DAO接口提供Mapper,可以在Spring整合后实现,这也是我们在开发中经常推荐的方式。
整合编码
Spring和Mybatis整合的总体配置:(其中spring配置在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-3.0.xsd" > <!-- 配置整合Mybatis的过程 --> <!-- 1:配置数据库相关参数 properties的属性:${url} --> <context:property-placeholder location="classpath:jdbc.properties"/> <!-- 加载配置参数的文件所在地 --> <!-- 2:数据库连接池 --> <bean id="dataSource"class="com.mchange.v2.c3p0.ComboPooledDataSource"> <!-- 配置连接池属性 --> <!-- c3p0连接池的基本属性 --> <property name="driverClass"value="${driver}"/> <property name="jdbcUrl"value="${url}"/> <property name="user"value="${username}"/> <property name="password"value="${password}"/> <!--连接池的私有属性根据高并发应用场景 --> <property name="maxPoolSize"value="30"/><!--连接池最多保留30个对象 --> <property name="minPoolSize"value="10"/> <!-- 关闭连接后不自动commit --> <property name="autoCommitOnClose"value="false"/> <!-- 获取连接超时时间 --> <property name="checkoutTimeout"value="1000"/> <!-- 当前获取连接失败重试次数 --> <property name="acquireRetryAttempts"value="2"/> </bean> <!-- 使用框架趋势:约定大于配置,将相应的文件放在对应包下,通过已配置项可以自动扫描 --> <!-- 3:配置 SqlSessionFactory对象 真正的整合配置--> <bean id="sqlSessionFactory"class="org.mybatis.spring.SqlSessionFactory"> <!--注入数据库连接池 --> <property name="dataSource"ref="dataSource"/> <!-- 配置Mybatis全局配置文件:mybatis-config.xml --> <property name="configLocation"value="classpath:mybatis-config.xml"/> <!-- 扫描entity包 使用别名org.seckill.entity --> <property name="typeAliasesPackage"value="org.seckill.entity"/> <!-- 扫描Sql配置文件:mapper需要的xml文件 --> <property name="mapperLocations"value="classpath:mapper/*.xml"/> </bean> <!-- 4: 配置扫描Dao接口包,动态实现Dao接口,注入到spring容器中--> <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <!--注入SqlSessionFactory 使用sqlSessionFactoryBeanName可以在用的时候再找sqlSessionFactory,防止提前初始化--> <property name="sqlSessionFactoryBeanName"value="sqlSessionFactory"/> <!-- 给出需要扫描Dao接口包--> <property name="basePackage"value="org.seckill.dao"/> </bean> </beans>
4.3 Junit单元测试
编写两个Dao类接口的测试类,这里使用Junit4来进行单元测试;
4.3.1SeckillDaoTest
/** * 配置Spring和Junit整合,Junit启动时加载SPringIOC容器 * spring-test,junit:spring测试的依赖 * 1:RunWith:Junit本身需要的依赖 * @author Terence * */@RunWith(SpringJUnit4ClassRunner.class)//2:告诉Junit Spring的配置文件@ContextConfiguration({"classpath:spring/spring-dao.xml"})public class SeckillDaoTest { //3:注入Dao实现类依赖 --会自动去Spring容器中查找seckillDao的实现类注入到单元测试类 @Resource private SeckillDao seckillDao; @Test public void testQueryById() throws Exception { long id=1000; Seckillseckill=seckillDao.queryById(id); System.out.println(seckill.getName()); System.out.println(seckill); /* * 1000元秒杀iphone6 * Seckill [seckillId=1000, name=1000元秒杀iphone6, number=100, createTime=Thu Aug 03 09:20:36 CST2017, startTime=Thu Jul 07 00:00:00 CST 2016, endTime=Fri Jul08 00:00:00 CST 2016] */ } /** * java在运行的时候会把List<Seckill> queryAll(int offset,intlimit);中的参数变成这样:queryAll(int arg0,int arg1),这样我们就没有办法去传递多个参数 * * @throws Exception */ @Test public void queryAll() throws Exception { List<Seckill> seckills=seckillDao.queryAll(0, 10); for(Seckill s:seckills) { System.out.println(s); } } @Test public void reduceNumber() throws Exception { long seckillId=1000; Date killTime=new Date(); int updateCount=seckillDao.reduceNumber(seckillId, killTime); System.out.println("updateCount="+updateCount); }}
4.3.2SuccessKilledTest
@RunWith(SpringJUnit4ClassRunner.class)//2:告诉Junit Spring的配置文件@ContextConfiguration({"classpath:spring/spring-dao.xml"})public class SuccessKilledTest { @Resource private SuccessKilledDao successKilledDao; /* *inserCount=0:已经插入相同记录 *inserCount=1:当前执行操作插入了一条记录 */ @Test public void insertSuccessKilledTest() throws Exception { long seckillId=1002; long userPhone=13475191898L; int insertCount=successKilledDao.insertSuccessKilled(seckillId,userPhone); System.out.println("insertCount="+insertCount); } @Test public void queryByIdWithSeckill() throws Exception { long seckillId=1002; long userPhone=13475191898L; SuccessKilled successKilled=successKilledDao.queryByIdWithSeckill(seckillId,userPhone); System.out.println(successKilled); System.out.println(successKilled.getSeckill()); }}
- 二、高并发秒杀API之Dao层设计与实现
- 三、高并发秒杀API之Service层设计与实现
- 四、高并发秒杀API之Web层设计与实现
- Java高并发秒杀API之DAO层实现(一)
- Java高并发秒杀API之业务分析与DAO层
- Java高并发秒杀API之业务分析与DAO层
- Java高并发秒杀API(一)之业务分析与DAO层
- 高并发秒杀API之业务分析与DAO
- Java高并发秒杀API(二)之Service层
- Java高并发秒杀API之service层实现(二)
- 【JAVA高并发秒杀API之DAO层】课程笔记
- JAVA高并发秒杀系统构建之——业务分析与Dao层搭建
- 高并发秒杀系统API之Web层
- Java高并发秒杀API(三)之Web层
- Java高并发秒杀API之web层实现(三)
- 高并发秒杀API之Service
- 慕课网-java高并发秒杀api之web层-总结
- 秒杀系统DAO层设计
- 【Ubuntu】 Ubuntu16.04快速搭建环境
- <线段树系列4> codevs 4927 线段树练习5
- error: invalid application of ‘sizeof’ to incomplete type ‘QStaticAssertFailure
- [笔记分享] [Camera] MTK Camera基础知识一
- 第十章 如果绝对的权力 会造成绝对的腐化 那么不断的竞争 就能造成不断的进步
- 二、高并发秒杀API之Dao层设计与实现
- 简单搞定yarn工作机制
- 笔记------redis
- 树莓派xrdp远程桌面连接出现Problem Connecting错误
- eclipse部署web项目至本地的tomcat但在webapps中找不到
- 基于局部均方差的图像局部对比度增强算法
- Activity的生命周期与启动模式
- 基于腾讯x5内核的精简版浏览器
- 操作系统概述