目錄本文為Java高并發秒殺API之業務分析與DAO層課程筆記,
- 介紹
- 創建專案
- 秒殺業務分析
- 秒殺系統業務流程
- MySQL實作秒殺難點分析
- 秒殺功能
- DAO設計編碼
- DAO物體和介面編碼
- MyBatis
- 實作DAO編程
- mybatis整合spring
- XML SQL
- junit單元測驗
- SecKillDaoTest.java
編輯器:IDEA
java版本:java8
介紹
學習目標:
秒殺業務:
-
具有典型的“事務”特性
事務的四大特性主要是:原子性(Atomicity)、一致性(Consistency)、隔離性(Isolation)、持久性(Durability), 原子性是指事務是一個不可分割的作業單位,事務中的操作要么全部成功,要么全部失敗
-
需求越來越常見
-
面試常問
前提內容(鏈接可點):J2EE、spring一、模擬一個簡單的tomcat、SpringMVC和SSM、動態代理、IoC、AOP、SpringBoot一、SpringBoot二、Thymeleaf、Java反射三四例,
具體學習:
MySQL:表設計、SQL技巧、事務和行級鎖,
MyBatis:DAO層設計開發、合理使用、與Spring整合,
Spring:IOC整合Service、宣告式事務運用,
SpringMVC:Restful介面設計和使用、框架運作流程、Controller技巧,
前端:互動設計、Bootstrap、jQuery,
高并發:高并發點和分析、優化思路,
創建專案

創建得到目錄如下:

下一步,右鍵專案名,添加java和resources、test檔案夾,IDEA會給相應的提示:

之后目錄為:

web.xml修改servlet版本:
<!--?xml version="1.0" encoding="UTF-8"?-->
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemalocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" version="3.1" metadata-complete="true">
</web-app>
pom.xml依賴配置,詳見注釋:
<dependencies>
<dependency>
<!--測驗:junit4-->
<groupid>junit</groupid>
<artifactid>junit</artifactid>
<version>4.11</version>
<scope>test</scope>
</dependency>
<!--日志:slf4j,log4j,logback,common-logging-->
<!--slf4j是規范/介面-->
<!--日志實作:log4j,logback,common-logging-->
<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.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.5.2</version>
</dependency>
<!--mybatis 自身實作的spring整合依賴-->
<dependency>
<groupid>org.mybatis</groupid>
<artifactid>mybatis-spring</artifactid>
<version>1.3.0</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>
<!--Jackson 是當前用的比較廣泛的,用來序列化和反序列化 json 的 Java 的開源框架-->
<dependency>
<groupid>com.fasterxml.jackson.core</groupid>
<artifactid>jackson-databind</artifactid>
<version>2.8.10</version>
</dependency>
<dependency>
<groupid>javax.servlet</groupid>
<artifactid>javax.servlet-api</artifactid>
<version>3.1.0</version>
</dependency>
<!--spring相關依賴-->
<!--spring核心依賴-->
<dependency>
<groupid>org.springframework</groupid>
<artifactid>spring-core</artifactid>
<version>5.3.9</version>
</dependency>
<dependency>
<groupid>org.springframework</groupid>
<artifactid>spring-beans</artifactid>
<version>5.3.9</version>
</dependency>
<!--包掃描-->
<dependency>
<groupid>org.springframework</groupid>
<artifactid>spring-context</artifactid>
<version>5.3.9</version>
</dependency>
<!--dao依賴-->
<dependency>
<groupid>org.springframework</groupid>
<artifactid>spring-jdbc</artifactid>
<version>5.1.4.RELEASE</version>
</dependency>
<!--事務-->
<dependency>
<groupid>org.springframework</groupid>
<artifactid>spring-tx</artifactid>
<version>5.1.4.RELEASE</version>
</dependency>
<!--spring web-->
<dependency>
<groupid>org.springframework</groupid>
<artifactid>spring-web</artifactid>
<version>5.3.9</version>
</dependency>
<dependency>
<groupid>org.springframework</groupid>
<artifactid>spring-webmvc</artifactid>
<version>5.3.9</version>
</dependency>
<!--spring test相關依賴-->
<dependency>
<groupid>org.springframework</groupid>
<artifactid>spring-test</artifactid>
<version>5.3.9</version>
</dependency>
</dependencies>
秒殺業務分析
秒殺系統業務流程
核心在于對庫存的處理,
用戶針對庫存業務分析:
購買行為:誰買、成功的時間、付款和發貨資訊,
事務:就像轉賬一樣,要么扣錢加錢同時成功要么同時失敗,
資料落地:MySQL VS NoSQL(關系型資料庫和非關系型資料庫)
MySQL實作秒殺難點分析
競爭!
解決競爭背后技術:事務+行級鎖,
行級鎖:
一個用戶A利用SQL陳述句:
update table set num=num-1 where id=10 and num>1
去修改庫存:id=10,name=xxx的項,
同時另一個用戶B也想用該陳述句修改庫存,就需要等待,直到A成功了,
難點:高效的處理競爭,
秒殺功能
- 秒殺介面暴露
- 執行秒殺
- 相關查詢
代碼開發階段:DAO設計編碼、Service設計編碼、Web設計編碼
DAO設計編碼
創建資料庫,兩張表:
mysql> show tables;
+-------------------+
| Tables_in_seckill |
+-------------------+
| seckill |
| success_killed |
+-------------------+
詳細見下面的注釋:
-- 資料庫初始化腳本
-- 創建資料庫
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 '庫存數量',
`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秒殺iphone13',100,'2021-10-04 18:00:00','2021-10-05 18:00:00'),
('500秒殺iphone12',200,'2021-10-04 18:00:00','2021-10-05 18:00:00'),
('300秒殺iphone11',300,'2021-10-04 18:00:00','2021-10-05 18:00:00'),
('100秒殺iphone6',400,'2021-10-04 18:00:00','2021-10-05 18: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 DEFAULT CURRENT_TIMESTAMP COMMENT '創建時間',
PRIMARY KEY(seckill_id,user_phone),/*聯合主鍵*/
KEY idx_create_time(create_time)
)ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT="秒殺成功明細表"
-- 連接資料庫控制臺
-- mysql -uroot -p
加COMMENT是為了能方便地查看創建時的想法:
SHOW CREATE TABLE 展示的內容更加豐富,它可以查看表的存盤引擎和字符編碼;另外,還可以通過 \g 或者 \G 引數來控制展示格式,
mysql> show create table seckill\G
*************************** 1. row ***************************
Table: seckill
Create Table: CREATE TABLE `seckill` (
`seckill_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '商品庫存id',
`name` varchar(120) NOT NULL COMMENT '商品名稱',
`number` int(11) NOT NULL COMMENT '庫存數量',
`create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '創建時間',
`start_time` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00' COMMENT '秒殺開啟時間',
`end_time` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00' 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=1004 DEFAULT CHARSET=utf8 COMMENT='秒殺庫存表'
DAO物體和介面編碼
目錄如下:

entity:對應資料庫,常規操作,具體看注釋
SecKill.java - 對應seckill中的秒殺商品:
/**
* 對應資料庫中的欄位
* mysql> select * from seckill;
* +------------+------------------+--------+---------------------+---------------------+---------------------+
* | seckill_id | name | number | create_time | start_time | end_time |
* +------------+------------------+--------+---------------------+---------------------+---------------------+
* | 1000 | 1000秒殺iphone13 | 100 | 2021-10-04 19:22:42 | 2021-10-04 18:00:00 | 2021-10-05 18:00:00 |
* | 1001 | 500秒殺iphone12 | 200 | 2021-10-04 19:22:42 | 2021-10-04 18:00:00 | 2021-10-05 18:00:00 |
* | 1002 | 300秒殺iphone11 | 300 | 2021-10-04 19:22:42 | 2021-10-04 18:00:00 | 2021-10-05 18:00:00 |
* | 1003 | 100秒殺iphone6 | 400 | 2021-10-04 19:22:42 | 2021-10-04 18:00:00 | 2021-10-05 18:00:00 |
* +------------+------------------+--------+---------------------+---------------------+---------------------+
*/
package cn.orzlinux.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;
// getter setter toString
}
successkilled.java
/**
* mysql> show columns from success_killed;
* +-------------+------------+------+-----+-------------------+-------+
* | Field | Type | Null | Key | Default | Extra |
* +-------------+------------+------+-----+-------------------+-------+
* | seckill_id | bigint(20) | NO | PRI | NULL | |
* | user_phone | bigint(20) | NO | PRI | NULL | |
* | state | tinyint(4) | NO | | -1 | |
* | create_time | timestamp | NO | MUL | CURRENT_TIMESTAMP | |
* +-------------+------------+------+-----+-------------------+-------+
*/
package cn.orzlinux.entity;
import java.util.Date;
public class SuccessKilled {
private long seckillId;
private long userPhone;
private short state;
private Date createTime;
// 為了能直接獲取秒殺成功的商品物件
private SecKill secKill;
// getter setter toString
}
DAO:資料庫要實作的方法介面
SecKillDao.java
public interface SecKillDao {
/***
* 減庫存
* @param seckillId
* @param killTime
* @return 如果影響行數>1,表示更新的記錄行數
*/
int reduceNumber(long seckillId, Date killTime);
/**
* 根據Id查秒殺物件
* @param seckillId
* @return
*/
SecKill queryById(long seckillId);
List<seckill> queryAll(int offset,int limit);
}
SuccessKilledDao.java
public interface SuccessKilledDao {
// 插入購買明細,可過濾重復
// 通過聯合唯一主鍵
// 回傳插入的行數
int insertSuccessKilled(long seckillId,long userPhone);
// 根據商品id和用戶查詢秒殺成功物件物體(帶秒殺商品物體)
// 原視頻應該有誤,這里僅憑一個商品Id得不到唯一的秒殺成功物件
SuccessKilled queryByIdWithSeckill(long seckillId,long userPhone);
}
MyBatis
MyBatis特點:引數+SQL=Entity/List
SQL可以寫在xml或者注解中,一般應該用xml
DAO介面:Mapper自動實作DAO介面(推薦)或者API方式
官方檔案鏈接:mybatis檔案
實作DAO編程
在resources檔案夾下創建mybatis組態檔和mapper檔案夾:

mybatis-config.xml配置如下:
DTD(Document Type Definition),全稱為檔案型別定義,具體頭可以在官方檔案例子中找到,
<!--?xml version="1.0" encoding="UTF-8" ?-->
<configuration>
<!--配置全域屬性-->
<settings>
<!--使用jdbc的getGeneratedKeys獲取資料庫自增主鍵-->
<setting name="useGeneratedKeys" value="https://www.cnblogs.com/hqinglau-orzlinux/p/true">
<!--使用列別名替換列名:默認true-->
<!--select name as title from table-->
<setting name="userColumn" value="https://www.cnblogs.com/hqinglau-orzlinux/p/true">
<!--開啟駝峰命名轉換:Table(create_time) -> Entity(createTime)-->
<setting name="namUnderscoreCamelCase" value="https://www.cnblogs.com/hqinglau-orzlinux/p/true">
</setting></setting></setting></settings>
</configuration>
mybatis整合spring
mybatis整合spring可以實作更少的配置:
-
別名,如
resultType="cn.orzlinux.entity.SecKill"可以簡寫為SecKill, -
配置掃描,如:
<mapper resource="mapper/SecKillDao.xml"> <mapper resource="mapper/SuccessKilledDao.xml"> ...可以簡化為自動配置掃描,
-
DAO實作
<bean id="ClubDao" > <bean id="Club2Dao" > ...簡化為自動實作DAO介面,自動注入spring容器,
-
依然具有足夠的靈活性、定制SQL、自由傳參、結果集自動賦值,
新建組態檔:

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 https://www.springframework.org/schema/context/spring-context.xsd">
<!--配置整合mybatis-->
<!--配置資料庫相關引數,一般在另一個properties檔案配置:${url}-->
<context:property-placeholder location="classpath:jdbc.properties">
<!-- classpath:jdbc.properties具體內容:
driver=com.mysql.jdbc.Driver
usrl=jdbc:mysql://127.0.0.1:3306/seckill?userUnicode=true&characterEncoding=utf8
username=root
password= -->
<!--資料庫連接池-->
<bean id="dataSource" >
<!--配置連接池屬性-->
<property name="driverClass" value="https://www.cnblogs.com/hqinglau-orzlinux/p/${driver}">
<property name="jdbcUrl" value="https://www.cnblogs.com/hqinglau-orzlinux/p/${url}">
<property name="user" value="https://www.cnblogs.com/hqinglau-orzlinux/p/${username}">
<property name="password" value="https://www.cnblogs.com/hqinglau-orzlinux/p/${password}">
<!--c3p0連接池私有屬性-->
<property name="maxPoolSize" value="https://www.cnblogs.com/hqinglau-orzlinux/p/30">
<property name="minPoolSize" value="https://www.cnblogs.com/hqinglau-orzlinux/p/10">
<!--連接池在回收資料庫連接時是否自動提交事務,如果為false,則會回滾未提交的事務,-->
<!--如果為true,則會自動提交事務,(不建議使用)-->
<property name="autoCommitOnClose" value="https://www.cnblogs.com/hqinglau-orzlinux/p/false">
<!--獲取連接超時時間-->
<property name="checkoutTimeout" value="https://www.cnblogs.com/hqinglau-orzlinux/p/1000">
<!--獲取連接失敗重試次數-->
<property name="acquireRetryAttempts" value="https://www.cnblogs.com/hqinglau-orzlinux/p/2">
</property></property></property></property></property></property></property></property></property></bean>
<!--配置sqlSessionFactory物件-->
<bean id="sqlSessionFactory" >
<!--注入資料庫連接池-->
<property name="dataSource" ref="dataSource">
<!--配置mybatis全域組態檔-->
<property name="configLocation" value="https://www.cnblogs.com/hqinglau-orzlinux/p/classpath:mybatis-config.xml">
<!--掃描entity包,使用別名 cn.orzlinux.entity.xxx -> xxx -->
<property name="typeAliasesPackage" value="https://www.cnblogs.com/hqinglau-orzlinux/p/cn.orzlinux.entity;">
<!--掃描sql組態檔,mapper需要的xml檔案-->
<property name="mapperLocations" value="https://www.cnblogs.com/hqinglau-orzlinux/p/classpath:mapper/*.xml">
</property></property></property></property></bean>
<!--配置掃描DAO介面包,動態實作DAO介面,并注入spring容器中-->
<bean >
<!--注入sqlSessionFactory-->
<property name="sqlSessionFactoryBeanName" value="https://www.cnblogs.com/hqinglau-orzlinux/p/sqlSessionFactory">
<!--給出需要掃描DAO介面包-->
<property name="basePackage" value="https://www.cnblogs.com/hqinglau-orzlinux/p/cn.orzlinux.dao">
</property></property></bean>
</context:property-placeholder></beans>
XML SQL
在使用mybatis 時我們sql是寫在xml 映射檔案中,如果寫的sql中有一些特殊的字符的話,在決議xml檔案的時候會被轉義,但我們不希望他被轉義,所以我們要使用<!--[CDATA[ ]]-->來解決,
<!--[CDATA[ ]]--> 是XML語法,在CDATA內部的所有內容都會被決議器忽略,
SecKillDao.xml:
<!--?xml version="1.0" encoding="UTF-8" ?-->
<mapper namespace="cn.orzlinux.dao.SecKillDao">
<!--為DAO介面方法提供sql陳述句配置-->
<!--int reduceNumber(long seckillId, Date killTime);-->
<update id="reduceNumber">
update
seckill
set
number = number - 1
where seckill_id = #{seckillId}
and start_time <!--[CDATA[ <= ]]--> #{killTime}
and end_time >= #{killTime}
and number > 0;
</update>
<!--SecKill queryById(long seckillId);-->
<select id="queryById" parametertype="long" resulttype="SecKill">
select seckill_id,name,number,start_time,end_time,create_time
from seckill where seckill_id=#{seckillId};
</select>
<!--List<SecKill> queryAll(int offset,int limit);-->
<select id="queryAll" resulttype="SecKill">
select seckill_id,name,number,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" ?-->
<mapper namespace="cn.orzlinux.dao.SuccessKilledDao">
<!--為DAO介面方法提供sql陳述句配置-->
<!--int insertSuccessKilled(long seckillId,long userPhone);-->
<insert id="insertSuccessKilled">
<!-- 主鍵沖突,報錯
INSERT IGNORE 與INSERT INTO的區別就是INSERT IGNORE會忽略數
據庫中已經存在 的資料,如果資料庫沒有資料,就插入新的資料,如果有數
據的話就跳過這條資料,-->
insert ignore into success_killed(seckill_id,user_phone)
values (#{seckillId,userPhone})
</insert>
<!--SuccessKilled queryByIdWithSeckill(long seckillId,long userPhone);-->
<select id="queryByIdWithSeckill" resulttype="SuccessKilled">
<!-- 根據查詢攜帶seckill物體
如何告訴mybatis把結果映射到SuccessKilled同時映射seckill
可以自由控制SQL-->
select
sk.seckill_id,
sk.user_phone,
sk.create_time,
sk.state,
s.seckill_id as "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>
junit單元測驗
SecKillDaoTest.java
package cn.orzlinux.dao;
import cn.orzlinux.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.List;
import static org.junit.Assert.*;
/**
* 配置spring和junit整合,junit啟動時加載spring ioc容器
* spring-test,junit
*/
@RunWith(SpringJUnit4ClassRunner.class)
//告訴junit spring組態檔
@ContextConfiguration({"classpath:spring/spring-dao.xml"})
public class SecKillDaoTest {
// 注入DAO實作類依賴,resource注解去spring容器找其實作類
@Resource
private SecKillDao secKillDao;
...
}
在queryById的測驗中一切正常:
@Test
public void queryByIdTest() {
long id =1000;
SecKill secKill = secKillDao.queryById(id);
System.out.println(secKill.getName());
System.out.println(secKill);
// output:1000秒殺iphone13
//SecKill{seckillId=1000, name='1000秒殺iphone13',
// number=100, startTime=Tue Oct 05 02:00:00 CST 2021,
// endTime=Wed Oct 06 02:00:00 CST 2021,
// createTime=Tue Oct 05 10:01:47 CST 2021}
}
但是在queryAllTest中出現了問題:
@Test
public void queryAllTest() {
List<seckill> secKillList = secKillDao.queryAll(0,100);
for(SecKill secKill:secKillList) {
System.out.println(secKill);
}
}
問題的原因在于:
<!--List<SecKill> queryAll(int offset,int limit);-->
<!--java沒有保存形參的記錄 queryAll(int offset,int limit); -> queryAll(arg0,arg1);-->
<!--byId可以是因為只有一個引數-->
<select id="queryAll" resulttype="SecKill">
select seckill_id,name,number,start_time,end_time,create_time
from seckill
order by create_time DESC
limit ${offset},#{limit};
</select>
所以需要在SecKillDao.java里的函式那里配置引數Param:
List<seckill> queryAll(@Param("offset") int offset, @Param("limit") int limit);
問題解決,結果如下:
SecKill{seckillId=1000, name='1000秒殺iphone13', number=100, startTime=Tue Oct 05 02:00:00 CST 2021, endTime=Wed Oct 06 02:00:00 CST 2021, createTime=Tue Oct 05 10:01:47 CST 2021}
SecKill{seckillId=1001, name='500秒殺iphone12', number=200, startTime=Tue Oct 05 02:00:00 CST 2021, endTime=Wed Oct 06 02:00:00 CST 2021, createTime=Tue Oct 05 10:01:47 CST 2021}
SecKill{seckillId=1002, name='300秒殺iphone11', number=300, startTime=Tue Oct 05 02:00:00 CST 2021, endTime=Wed Oct 06 02:00:00 CST 2021, createTime=Tue Oct 05 10:01:47 CST 2021}
SecKill{seckillId=1003, name='100秒殺iphone6', number=400, startTime=Tue Oct 05 02:00:00 CST 2021, endTime=Wed Oct 06 02:00:00 CST 2021, createTime=Tue Oct 05 10:01:47 CST 2021}
減庫存測驗:
@Test
public void reduceNumberTest() {
Date killTime = new Date();
int updateCount = secKillDao.reduceNumber(1000L,killTime);
System.out.println(updateCount);
// 1:表示更改了一條記錄
}
測驗記錄,查看這里的確少了一件:

分析一下mybatis的行為:
# 從c3p0連接池拿到鏈接,沒有被spring托管
02:18:42.793 [main] DEBUG o.m.s.t.SpringManagedTransaction - JDBC Connection [com.mchange.v2.c3p0.impl.NewProxyConnection@1cd629b3] will not be managed by Spring
# SQL
02:18:42.818 [main] DEBUG c.o.dao.SecKillDao.reduceNumber - ==> Preparing: update seckill set number = number - 1 where seckill_id = ? and start_time <= ? and end_time >= ? and number > 0;
# 引數傳遞
02:18:42.882 [main] DEBUG c.o.dao.SecKillDao.reduceNumber - ==> Parameters: 1000(Long), 2021-10-05 02:18:42.442(Timestamp), 2021-10-05 02:18:42.442(Timestamp)
# 引數回傳
02:18:42.897 [main] DEBUG c.o.dao.SecKillDao.reduceNumber - <== Updates: 1
同理另一個dao SuccessKilledDao也進行測驗,
package cn.orzlinux.dao;
import cn.orzlinux.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 javax.annotation.Resource;
import static org.junit.Assert.*;
@RunWith(SpringJUnit4ClassRunner.class)
//告訴junit spring組態檔
@ContextConfiguration({"classpath:spring/spring-dao.xml"})
public class SuccessKilledDaoTest {
// 注入DAO實作類依賴,resource注解去spring容器找其實作類
@Resource
private SuccessKilledDao successKilledDao;
@Test
public void insertSuccessKilled() {
long id = 1000L;
long phone = 12345678901L;
int insertCount = successKilledDao.insertSuccessKilled(id,phone);
System.out.println(insertCount);
// 輸出1
// 再運行一次輸出 0,聯合主鍵的效果
}
@Test
public void queryByIdWithSeckill() {
long id = 1000L;
long phone = 12345678901L;
SuccessKilled successKilled = successKilledDao.queryByIdWithSeckill(id,phone);
System.out.println(successKilled);
System.out.println(successKilled.getSecKill());
// SuccessKilled{seckillId=1000, userPhone=12345678901, state=-1,
// createTime=Tue Oct 05 10:33:43 CST 2021}
// SecKill{seckillId=1000, name='1000秒殺iphone13', number=99,
// startTime=Tue Oct 05 02:00:00 CST 2021, endTime=Wed Oct 06 02:00:00 CST 2021, createTime=Tue Oct 05 10:01:47 CST 2021}
}
}

還有一點就是更改秒殺成功的狀態,更改為0,也就是秒殺成功:
狀態標識:-1無效,0成功,1已付款,2已發
<insert id="insertSuccessKilled">
<!-- 主鍵沖突,報錯
INSERT IGNORE 與INSERT INTO的區別就是INSERT IGNORE會忽略數
據庫中已經存在 的資料,如果資料庫沒有資料,就插入新的資料,如果有數
據的話就跳過這條資料,-->
insert ignore into success_killed(seckill_id,user_phone,state)
values (#{seckillId},#{userPhone},0)
</insert>
本文同步發布于orzlinux.cn
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/305574.html
標籤:Java
上一篇:??假如面試官讓你聊聊Sentinel(哨兵),看完這篇文章足矣!??
下一篇:java統一回傳標準型別
