二、高併發秒殺API之Dao層設計實現
一、Idea建立maven
1、File——-》new———》project
2、選擇maven
點選next!
3、輸出專案名,包(表示web專案,以後可以spingMVC連起來用)
點選next!
4、如下圖 maven倉庫可以選擇你自己的
下一步!
點選完成!
5、在main檔案下新建Java檔案 放原始碼
(1)web.xml檔案改變servlet版本,相關問題見
(2)pom.xml檔案引入依賴
二、資料庫設計和實現
資料庫構建主要是兩個表的建立:秒殺表、明細表
CREATE TABLE seckill(
`seckill_id` BIGINT NOT NUll AUTO_INCREMENT COMMENT '商品庫存ID',
`name` VARCHAR(120) NOT NULL COMMENT '商品名稱',
`number` int NOT NULL COMMENT '庫存數量',
`create_time` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '建立時間',
`start_time` TIMESTAMP NOT NULL COMMENT '秒殺開始時間',
`end_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,'2016-01-01 00:00:00','2016-01-02 00:00:00'),
('800元秒殺ipad',200,'2016-01-01 00:00:00','2016-01-02 00:00:00'),
('6600元秒殺mac book pro',300,'2016-01-01 00:00:00','2016-01-02 00:00:00'),
('7000元秒殺iMac',400,'2016-01-01 00:00:00','2016-01-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='秒殺成功明細表';
三、Dao層設計開發
結構:
1、Entity
建立實體類Seckill.java
package entity;
import java.util.Date;
/**
* @Author:peishunwu
* @Description:
* @Date:Created 2018/6/1
*/
public class Seckill {
/**
* 商品庫存ID
*/
private long seckillId;
/**
* 商品名稱
*/
private String name;
/**
* 庫存數量
*/
private int number;
/**
* 秒殺開始時間
*/
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 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;
}
}
建立SuccessKilled.java
package entity;
import java.util.Date;
/**
* @Author:peishunwu
* @Description:
* @Date:Created 2018/6/1
*/
public class SuccessKilled {
/**
* 商品庫存ID
*/
private long seckillId;
/**
* 使用者手機號
*/
private long userPhone;
/**
* 狀態標識:-1:無效 0:成功 1:已付款 2:已發貨
*/
private short state;
/**
* 建立時間
*/
private Date createTime;
/**
* 秒殺庫存表實體
*/
private Seckill seckill;
public long getSeckillId() {
return seckillId;
}
public void setSeckillId(long seckillId) {
this.seckillId = seckillId;
}
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;
}
}
2、DAO
SeckillDao.java
package dao;
import entity.Seckill;
import org.apache.ibatis.annotations.Param;
import java.util.Date;
import java.util.List;
/**
* @Author:peishunwu
* @Description:
* @Date:Created 2018/6/1
*/
public interface SeckillDao {
/**
* 減庫存
* @param seckillId
* @param killTime
* @return 如果影響的行數>1,表示更新行數
*/
int reduceNumber(@Param("seckillId") long seckillId, @Param("killTime")Date killTime);
/**
* 根據id查詢秒殺庫存
* @param seckillId
* @return
*/
Seckill gueryById(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
package dao;
import entity.SuccessKilled;
import org.apache.ibatis.annotations.Param;
/**
* @Author:peishunwu
* @Description:
* @Date:Created 2018/6/1
*/
public interface SuccessKilledDao {
/**
* 插入購買明細,過濾重複(聯合唯一主鍵)
* @param seckillId
* @param userPhone
* @return
*/
int insertSuccessKilled(@Param("seckillId") long seckillId,@Param("userPhone") long userPhone);
/**
* 根據ID查詢SuccessKilled並攜帶秒殺產品物件體
* @param seckillId
* @param userPhone
* @return
*/
SuccessKilled queryByIdWithSeckill(@Param("seckillId") long seckillId,@Param("userPhone") long userPhone);
}
四、基於mybatis的dao層實現
設計好了Database和Dao層,剩下的就是需要一個對映關係來連結兩者,將資料和物件對應起來,這時就需要Mybatis或者Hibernate來完成任務,“關係型物件對映— ORM(Object/RelationshipMapping)”的名稱也是由此而來。
Mybatis特點:
引數需要提供+SQL需要自己寫(比較靈活) = Entity/List
SQL檔案可以寫在xml檔案或者註解當中,實際使用當中提倡使用xml配置檔案來寫SQL,避免修改重新編譯類等。
如何實現DAO介面?
第一種:通過Mapper自動實現Dao層介面(推薦使用),將執行結果集封裝成我們想要的方式;
第二種:通過API程式設計的方式實現Dao介面。
1、Mybatis總配置檔案
配置檔案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的標準配置 -->
2、Dao配置檔案
保證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 namespace="org.seckill.dao.SeckillDao">
<!-- 目的:為Dao層介面方法提供sql語句配置 -->
<!-- 為第一個方法提供sql語句 -->
<update id="reduceNumber">
<!-- 具體sql,返回影響的行數 -->
update seckill set number=number-1
where seckill_id=#{seckillId}
and start_time <![CDATA[<=]]> #{killTime}
and end_time<![CDATA[>=]]> #{killTime}
and number>0;
</update>
<select id="queryById" resultType="Seckill" parameterType="long">
select seckill_id,name,number,create_time,start_time,end_time
from seckill
where seckill_id=#{seckillId}
</select>
<select id="queryAll" resultType="Seckill">
select seckill_id,name,number,create_time,start_time,end_time
from seckill
order by create_time desc
limit #{offset},#{limit} <!-- 在偏移量之後取的行數 -->
</select>
</mapper>
SuccessSeckilledDao.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">
<!-- 使用ignore在主鍵衝突的時候報錯返回0,避免插入數值 -->
insert ignore into success_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 on sk.seckill_id=s.seckill_id
where sk.seckill_id=#{seckillId} and sk.user_phone=#{userPhone}
</select>
</mapper>
五、整合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的官方文件裡面有介紹)
1、pom檔案增加Spring依賴和mybatis依賴mysql依賴
<?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>com.psw</groupId>
<artifactId>seckill</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>war</packaging>
<name>seckill Maven Webapp</name>
<!-- FIXME change it to the project's website -->
<url>http://www.example.com</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.7</maven.compiler.source>
<maven.compiler.target>1.7</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<!-- 3.0版本使用程式設計方式執行,4.0則使用註解方式 -->
<groupId>junit</groupId><!-- 使用單元測試需要的依賴 -->
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<!-- 補全專案依賴 -->
<!-- 1:日誌 java日誌:slf4j,log4j,logback,common-logging
slf4j是規範/介面
日誌實現:log4j,loggback,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>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.21</version>
</dependency>
<dependency>
<groupId>c3p0</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.1.2</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<!-- Dao持久化層框架依賴:Mybatis依賴,即Mybatis自身的依賴和 Mybatis與Spring的整合依賴-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.2.8</version>
</dependency>
<!-- mybatis自身實現的spring整合依賴
因為Mybatis的前身ibatis出現比較早,spring提供了對ibatis的依賴,但是Mybatis出現的時候,並沒有對其提供整合依賴
所以,mybatis特意提供了對Spring的整合依賴
-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>1.3.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><!-- Spring IOC擴充套件依賴 -->
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.1.7.RELEASE</version>
</dependency>
<!-- (2)Spring Dao層的依賴 -->
<dependency><!-- Spring JDBC方面的依賴 -->
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>4.1.7.RELEASE</version>
</dependency>
<dependency><!-- Spring 事務方面的依賴 -->
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>4.1.7.RELEASE</version>
</dependency>
<!-- (3)Spring Web相關依賴 -->
<dependency><!-- Spring web方面的依賴:專案本身是個web工程,需要經過Servlet容器去啟動 -->
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>4.1.7.RELEASE</version>
</dependency>
<dependency><!-- Spring MVC依賴 :MVC框架使用的是Spring MVC-->
<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>
<build>
<finalName>seckill</finalName>
<pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) -->
<plugins>
<plugin>
<artifactId>maven-clean-plugin</artifactId>
<version>3.0.0</version>
</plugin>
<!-- see http://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_war_packaging -->
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<version>3.0.2</version>
</plugin>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.7.0</version>
</plugin>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.20.1</version>
</plugin>
<plugin>
<artifactId>maven-war-plugin</artifactId>
<version>3.2.0</version>
</plugin>
<plugin>
<artifactId>maven-install-plugin</artifactId>
<version>2.5.2</version>
</plugin>
<plugin>
<artifactId>maven-deploy-plugin</artifactId>
<version>2.8.2</version>
</plugin>
</plugins>
</pluginManagement>
</build>
</project>
2、資料庫配置jdbc.properties
driver=com.mysql.jdbc.Driver
url=jdbc:mysql://locahost:3306/seckill?useUnicode=true&characterEncoding=utf8
username=root
password=psw105
2、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="6000"/>
<!-- 當前獲取連線失敗重試次數 -->
<property name="acquireRetryAttempts" value="2"/>
</bean>
<!-- 使用框架趨勢:約定大於配置,將相應的檔案放在對應包下,通過已配置項可以自動掃描 -->
<!-- 3:配置 SqlSessionFactory物件 真正的整合配置-->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<!--注入資料庫連線池 -->
<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="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="dao"/>
</bean>
</beans>
六、單元測試
編寫兩個Dao類介面的測試類,這裡使用Junit4來進行單元測試;
1、SeckillDaoTest
package dao;
import entity.Seckill;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import javax.annotation.Resource;
import java.util.Date;
/**
* @Author:peishunwu
* @Description:
* @Date:Created 2018/6/1
* 配置Spring和Junit整合,Junit啟動時載入SPringIOC容器
* spring-test,junit:spring測試的依賴
* 1:RunWith:Junit本身需要的依賴
*/
@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;
Seckill seckill = seckillDao.queryById(id);
System.out.println(seckill.getName());
System.out.println(seckill);
}
@Test
public void reduceNumber()throws Exception{
long seckillId = 1000;
Date killTime = new Date();
int updateCount = seckillDao.reduceNumber(seckillId,killTime);
System.out.println(updateCount);
}
}
執行:
2、SuccessKilledTest
package dao;
import entity.SuccessKilled;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import com.alibaba.fastjson.JSONObject;
import javax.annotation.Resource;
/**
* @Author:peishunwu
* @Description:
* @Date:Created 2018/6/4
*/
@RunWith(SpringJUnit4ClassRunner.class)
/*告訴Junit Spring的配置檔案*/
@ContextConfiguration("classpath:spring/spring-dao.xml")
public class SuccessSeckillDaoTest {
@Resource
private SuccessKilledDao successKilledDao;
/*
*inserCount=0:已經插入相同記錄
*inserCount=1:當前執行操作插入了一條記錄
*/
@Test
public void insertSuccess