SpringBoot实现Java高并发秒杀系统之DAO层开发(一)
SpringBoot實(shí)現(xiàn)Java高并發(fā)秒殺系統(tǒng)之DAO層開發(fā)(一)
秒殺系統(tǒng)在如今電商項(xiàng)目中是很常見的,最近在學(xué)習(xí)電商項(xiàng)目時講到了秒殺系統(tǒng)的實(shí)現(xiàn),于是打算使用SpringBoot框架學(xué)習(xí)一下秒殺系統(tǒng)(本項(xiàng)目基于慕課網(wǎng)的一套免費(fèi)視頻教程:Java高并發(fā)秒殺API,視頻教程中講解的很詳細(xì),非常感謝這位講師)。也是因?yàn)樽罱鼘W(xué)習(xí)了SpringBoot框架(GitHub教程:SpringBoot入門之CRUD?),覺得SpringBoot框架確實(shí)比傳統(tǒng)SSM框架方便了很多,于是更深層次練習(xí)使用SpringBoot框架,注意:SpringBoot不是對Spring功能上的增強(qiáng),而是提供了一種快速使用Spring的方式。?如果你熟悉了SSM框架,學(xué)習(xí)SpringBoot框架也是很Easy的。
本項(xiàng)目的源碼請參看:springboot-seckill?如果覺得不錯可以star一下哦(#^.^#)
本項(xiàng)目一共分為四個模塊來講解,具體的開發(fā)教程請看我的博客文章:
-
SpringBoot實(shí)現(xiàn)Java高并發(fā)秒殺系統(tǒng)之DAO層開發(fā)(一)
-
SpringBoot實(shí)現(xiàn)Java高并發(fā)秒殺系統(tǒng)之Service層開發(fā)(二)
-
SpringBoot實(shí)現(xiàn)Java高并發(fā)秒殺系統(tǒng)之Web層開發(fā)(三)
-
SpringBoot實(shí)現(xiàn)Java高并發(fā)秒殺系統(tǒng)之并發(fā)優(yōu)化(四)
起步
首先我們需要搭建SpringBoot項(xiàng)目開發(fā)環(huán)境,IDEA搭建SpringBoot項(xiàng)目的具體教程請看我的:博文。
如果你對SpringBoot框架或是SSM框架不熟悉,我想推薦一下我的幾個小項(xiàng)目幫助你更好的理解:
-
SpringBoot起步之環(huán)境搭建
-
SpringBoot-Mybatis入門之CRUD
-
手把手教你整合SSM框架
-
SSM框架入門之環(huán)境搭建
項(xiàng)目設(shè)計(jì)
| . ├── README -- Doc文檔 ├── db -- 數(shù)據(jù)庫約束文件 ├── mvnw ├── mvnw.cmd ├── pom.xml -- 項(xiàng)目依賴 └── src ├── main │?? ├── java │?? │?? └── cn │?? │?? └── tycoding │?? │?? ├── SpringbootSeckillApplication.java -- SpringBoot啟動器 │?? │?? ├── controller -- MVC的web層 │?? │?? ├── dto -- 統(tǒng)一封裝的一些結(jié)果屬性,和entity類似 │?? │?? ├── entity -- 實(shí)體類 │?? │?? ├── enums -- 手動定義的字典枚舉參數(shù) │?? │?? ├── exception -- 統(tǒng)一的異常結(jié)果 │?? │?? ├── mapper -- Mybatis-Mapper層映射接口,或稱為DAO層 │?? │?? ├── redis -- redis,jedis 相關(guān)配置 │?? │?? └── service -- 業(yè)務(wù)層 │?? └── resources │?? ├── application.yml -- SpringBoot核心配置 │?? ├── mapper -- Mybatis-Mapper層XML映射文件 │?? ├── static -- 存放頁面靜態(tài)資源,可通過瀏覽器直接訪問 │?? │?? ├── css │?? │?? ├── js │?? │?? └── lib │?? └── templates -- 存放Thymeleaf模板引擎所需的HTML,不能在瀏覽器直接訪問 │?? ├── page │?? └── public -- HTML頁面公共組件(頭部、尾部) └── test -- 測試文件 |
SpringBoot
之前我們在SpringBoot-Mybatis入門之CRUD中已經(jīng)詳細(xì)講解了SpringBoot框架的開發(fā)流程,還是覺得一句話說的特別好:SpringBoot不是對對Spring功能上的增強(qiáng),而是提供了一種快速使用Spring的方式。所以用SSM階段的知識足夠了SpringBoot階段的開發(fā),下面我們強(qiáng)調(diào)一下小技巧:
-
SpringBoot不需要配置注解掃描,之前我們配置<context:component-scan>掃描可能使用注解(@Service,@Component,@Controller等)的包路徑。默認(rèn)創(chuàng)建SpringBoot項(xiàng)目自動生成的Application.java啟動器類會自動掃描其下的所有注解。
-
SpringBoot項(xiàng)目中靜態(tài)資源都放在resources目錄下,其中static目錄中的數(shù)據(jù)可以直接通過瀏覽器訪問,多用來放CSS、JS、img,但是不用來放html頁面;其中templates用來存放HTML頁面,但是需要在SpringBoot的配置文件(application.yml)中配置spring.thymeleaf.prefix標(biāo)識Thymeleaf模板引擎渲染的頁面位置。
-
HTML頁面通過Thymeleaf的加持,為HTML頁面賦予了很多功能,此時的HTML頁面類似于JSP頁面。訪問后端存入域?qū)ο?session,request…)中的數(shù)據(jù),可以通過th:text="${key}"獲得,在JS中也可以通過[[${key}]]獲得。
-
Thymeleaf提供了類似JSP頁面<include>的功能:public-component:<div th:fragment="header">,main-component:<div th:replace="path/header :: header">(其中path表示public-component相對于templates的路徑,/header表示component文件名,最后的header表示th:fragment中定義的名稱)。
pom依賴
| <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.0.5.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>1.3.2</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <!-- alibaba的druid數(shù)據(jù)庫連接池 --> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.1.9</version> </dependency> <!-- redis客戶端 --> <dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency> </dependencies> |
JavaBean實(shí)體類配置
此處源碼請看:GitHub
Seckill.java
| public class Seckill implements Serializable { private long seckillId; //商品ID private String title; //商品標(biāo)題 private String image; //商品圖片 private BigDecimal price; //商品原價格 private BigDecimal costPrice; //商品秒殺價格 @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") private Date createTime; //創(chuàng)建時間 @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") private Date startTime; //秒殺開始時間 @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") private Date endTime; //秒殺結(jié)束時間 private long stockCount; //剩余庫存數(shù)量 } |
SeckillOrder.java
| public class SeckillOrder implements Serializable { private long seckillId; //秒殺到的商品ID private BigDecimal money; //支付金額 private long userPhone; //秒殺用戶的手機(jī)號 @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") private Date createTime; //創(chuàng)建時間 private boolean status; //訂單狀態(tài), -1:無效 0:成功 1:已付款 private Seckill seckill; //秒殺商品,和訂單是一對多的關(guān)系 } |
注意實(shí)體類中Date類型數(shù)據(jù)都用了@DateTimeFormat()(來自springframework)和@JsonFormat()(來自jackson)標(biāo)識可以實(shí)現(xiàn)Controller在返回JSON數(shù)據(jù)(用@ResponseBody標(biāo)識的方法或@RestController標(biāo)識的類)的時候能將Date類型的參數(shù)值(經(jīng)Mybatis查詢得到的數(shù)據(jù)是英文格式的日期,因?yàn)閷?shí)體類中是Date類型)轉(zhuǎn)換為注解中指定的格式返回給頁面(相當(dāng)于經(jīng)過了一層SimpleDateFormate)。
其次要注意在編寫實(shí)體類的時候盡量養(yǎng)成習(xí)慣繼承Serializable接口。在SeckillOrder中我們注入了Seckill類作為一個屬性,目的是為了可以使用多表查詢的方式從seckill_order表中查詢出來對應(yīng)的seckill表數(shù)據(jù)。
表設(shè)計(jì)
創(chuàng)建完成了SpringBoot項(xiàng)目,首先我們需要初始化數(shù)據(jù)庫,秒殺系統(tǒng)的建表SQL如下:
| /* * mysql-v: 5.7.22 */ -- 創(chuàng)建數(shù)據(jù)庫 -- CREATE DATABASE seckill DEFAULT CHARACTER SET utf8; DROP TABLE IF EXISTS `seckill`; DROP TABLE IF EXISTS `seckill_order`; -- 創(chuàng)建秒殺商品表 CREATE TABLE `seckill`( `seckill_id` bigint NOT NULL AUTO_INCREMENT COMMENT '商品ID', `title` varchar (1000) DEFAULT NULL COMMENT '商品標(biāo)題', `image` varchar (1000) DEFAULT NULL COMMENT '商品圖片', `price` decimal (10,2) DEFAULT NULL COMMENT '商品原價格', `cost_price` decimal (10,2) DEFAULT NULL COMMENT '商品秒殺價格', `stock_count` bigint DEFAULT NULL COMMENT '剩余庫存數(shù)量', `start_time` timestamp NOT NULL DEFAULT '1970-02-01 00:00:01' COMMENT '秒殺開始時間', `end_time` timestamp NOT NULL DEFAULT '1970-02-01 00:00:01' COMMENT '秒殺結(jié)束時間', `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '創(chuàng)建時間', PRIMARY KEY (`seckill_id`), KEY `idx_start_time` (`start_time`), KEY `idx_end_time` (`end_time`), KEY `idx_create_time` (`end_time`) ) CHARSET=utf8 ENGINE=InnoDB COMMENT '秒殺商品表'; -- 創(chuàng)建秒殺訂單表 CREATE TABLE `seckill_order`( `seckill_id` bigint NOT NULL COMMENT '秒殺商品ID', `money` decimal (10, 2) DEFAULT NULL COMMENT '支付金額', `user_phone` bigint NOT NULL COMMENT '用戶手機(jī)號', `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '創(chuàng)建時間', `state` tinyint NOT NULL DEFAULT -1 COMMENT '狀態(tài):-1無效 0成功 1已付款', PRIMARY KEY (`seckill_id`, `user_phone`) /*聯(lián)合主鍵,保證一個用戶只能秒殺一件商品*/ ) CHARSET=utf8 ENGINE=InnoDB COMMENT '秒殺訂單表'; |
解釋
秒殺系統(tǒng)的表設(shè)計(jì)還是相對簡單清晰的,這里我們只考慮秒殺系統(tǒng)的業(yè)務(wù)表,不涉及其他的表,所以整個系統(tǒng)主要涉及兩張表:秒殺商品表、訂單表。當(dāng)然實(shí)際情況肯定不止這兩張表(比如付款相關(guān)表,但是我們并未實(shí)現(xiàn)這個功能),也不止表中的這些字段。這里我們需要特別注意以下幾點(diǎn):
注意
-
1.我這里使用的Mysql版本是5.7.22,在Mysql5.7之后timestamp默認(rèn)值不能再是0000 00-00 00:00:00,具體的介紹請看:mysql官方文檔。即 TIMESTAMP has a range of ‘1970-01-01 00:00:01’ UTC to ‘2038-01-19 03:14:07’ UTC.
-
2.timestamp類型用來實(shí)現(xiàn)自動為新增行字段設(shè)置當(dāng)前系統(tǒng)時間;且使用timestamp的字段必須給timestamp設(shè)置默認(rèn)值,而在Mysql中date, datetime等類型都是無法實(shí)現(xiàn)默認(rèn)設(shè)置當(dāng)前系統(tǒng)時間值的功能(DEFAULT CURRENT_TIMESTAMP)的,所以我們必須使用timestamp類型,否則你要給字段傳進(jìn)來系統(tǒng)時間。
-
3.decimal類型用于在數(shù)據(jù)庫中設(shè)置精確的數(shù)值,比如decimal(10,2)表示可以存儲10位且有2位小數(shù)的數(shù)值。
-
4.tinyint類型用于存放int類型的數(shù)值,但是若用Mybatis作為DAO層框架,Mybatis會自動為tinyint類型的數(shù)據(jù)轉(zhuǎn)換成true或false(0:false; 1 or 1+:true)。
-
5.在訂單表seckill_order中我們設(shè)計(jì)了聯(lián)合主鍵:PRIMARY KEY (seckill_id, user_phone),目的是為了避免單個用戶重復(fù)購買同一件商品(一個用戶只能秒殺到一次同一件商品)。
-
6.無論是創(chuàng)建數(shù)據(jù)庫還是創(chuàng)建表我們都應(yīng)該養(yǎng)成一個習(xí)慣就是指定character=utf-8,避免中文數(shù)據(jù)亂碼;其次還應(yīng)該指定表的儲存引擎是InnoDB,MySQL提供了兩種儲存引擎:InnoDB, MyISAM。但是只有InnoDB是支持事務(wù)的,且InnoDB相比MyISAM在并發(fā)上更具有高性能的優(yōu)點(diǎn)。
DAO層開發(fā)
DAO層是我們常說的三層架構(gòu)(Web層-業(yè)務(wù)層-持久層)中與數(shù)據(jù)庫交互的持久層,但是實(shí)際而言,架構(gòu)是這樣設(shè)計(jì)的,但是并不代表著實(shí)際項(xiàng)目中就一定存在一個dao文件夾,特別是現(xiàn)階段我們使用的Spring-Mybatis框架。Mybatis提供了一種接口代理開發(fā)模式,也就是我們需要提供一個interface接口,其他和數(shù)據(jù)庫交互的SQL編寫放到對應(yīng)的XML文件中(但是需要進(jìn)行相關(guān)的數(shù)據(jù)庫參數(shù)配置,并且Mybatis規(guī)定了使用這種開發(fā)模式必須保持接口和XML文件名稱對應(yīng))。于是在本項(xiàng)目中就沒有出現(xiàn)dao整個文件夾,取而代之的是mapper這個文件夾,我感覺更易識別出為Mybatis的映射接口文件。其實(shí)在實(shí)際項(xiàng)目中考慮到項(xiàng)目的大小和復(fù)雜程度,dao和mapper可能是同時存在的,因?yàn)閟ervice可能并不滿足項(xiàng)目的設(shè)計(jì),即為dao接口創(chuàng)建實(shí)現(xiàn)類,在實(shí)現(xiàn)類中再調(diào)用mapper接口來實(shí)現(xiàn)功能模塊的擴(kuò)展。
DAO層開發(fā),即DAO層接口開發(fā),主要設(shè)計(jì)需要和數(shù)據(jù)庫交互的數(shù)據(jù)有哪些?應(yīng)該用什么返回值類型接收查詢到的數(shù)據(jù)?所以包含的方法有哪些?帶著這些問題,我們先看一下秒殺系統(tǒng)的業(yè)務(wù)流程:
由上圖可以看出,相對與本項(xiàng)目而言和數(shù)據(jù)庫打交道的主要涉及兩個操作:1.減庫存(秒殺商品表);2.記錄購買明細(xì)(訂單表)。
-
減庫存,顧名思義就是減少當(dāng)前被秒殺到的商品的庫存數(shù)量,這也是秒殺系統(tǒng)中一個處理難點(diǎn)的地方。實(shí)現(xiàn)減庫存即count-1,但是我們需要考慮Mysql的事務(wù)特性引發(fā)的種種問題、需要考慮如何避免同一用戶重復(fù)秒殺的行為。
-
如果減庫存的業(yè)務(wù)解決了那么記錄購買明細(xì)的業(yè)務(wù)就相對簡單很多了,我們需要記錄購買用戶的姓名、手機(jī)號、購買的商品ID等。因?yàn)楸卷?xiàng)目中不涉及支付功能,所以記錄用戶的購買訂單的業(yè)務(wù)并不復(fù)雜。
分析了上面的功能,下面我們開始DAO層接口的編寫(源碼請看:GitHub):
| /** * 減庫存。 * 對于Mapper映射接口方法中存在多個參數(shù)的要加@Param()注解標(biāo)識字段名稱,不然Mybatis不能識別出來哪個字段相互對應(yīng) * * @param seckillId 秒殺商品ID * @param killTime 秒殺時間 * @return 返回此SQL更新的記錄數(shù),如果>=1表示更新成功 */ int reduceStock(@Param("seckillId") long seckillId, @Param("killTime") Date killTime); /** * 插入購買訂單明細(xì) * * @param seckillId 秒殺到的商品ID * @param money 秒殺的金額 * @param userPhone 秒殺的用戶 * @return 返回該SQL更新的記錄數(shù),如果>=1則更新成功 */ int insertOrder(@Param("seckillId") long seckillId, @Param("money") BigDecimal money, @Param("userPhone") long userPhone); |
但從接口設(shè)計(jì)上我們無非關(guān)注的就是這兩個方法:1.減庫存;2.插入購買明細(xì)。此處需要注意的是:
-
對于SpringBoot系統(tǒng),DAO(Mapper)層的接口需要使用@Mapper注解標(biāo)識。因?yàn)镾pringBoot系統(tǒng)中接口的XML文件不在/java目錄下而是在/resources目錄下。
-
對于Mapper接口方法中存在傳遞多個參數(shù)的情況需要使用@Param()標(biāo)識這個參數(shù)的名稱,目的是為了幫助Mybatis識別傳遞的參數(shù),不然Mybatis的XML中用的#{}不能識別出來你傳遞的參數(shù)名稱是誰和誰對應(yīng)的,類似于Controller層中常用的@RequestParam()注解。
-
小技巧:?之前我們做insert和update操作時直接用void作為方法返回值,實(shí)際上雖然Mybatis的<update>和<select>語句并沒有resultType屬性,但是并不代表其沒有返回值,默認(rèn)返回0或1,表示執(zhí)行該SQL影響的行數(shù)。為此我們可以這樣寫SQL,如:insert ignore into xxx用來避免Mybatis報(bào)錯,而是直接返回0表示當(dāng)前SQL執(zhí)行失敗。
-
小技巧:因?yàn)槲覀儽仨氁苊馔粋€用戶多次搶購?fù)患唐?#xff0c;在SQL中必須限制這一點(diǎn)(因?yàn)榧词骨岸嗽趺纯刂贫紵o法避免用戶多次請求同一個接口,所謂接口防刷)。所以在設(shè)計(jì)訂單表的時候用了聯(lián)合主鍵且不自增的方式,以用戶ID和用戶電話組成聯(lián)合主鍵,這樣當(dāng)同一個用戶(電話相同)多次搶購?fù)患唐窌r插入的SQL就會產(chǎn)生主鍵沖突的問題,這樣就會報(bào)錯。
XML映射
| <update id="reduceStock"> UPDATE seckill SET stock_count = stock_count - 1 WHERE seckill_id = #{seckillId} AND start_time <= #{killTime} AND end_time >= #{killTime} AND stock_count > 0 </update> <insert id="insertOrder"> INSERT ignore INTO seckill_order(seckill_id, money, user_phone) VALUES (#{seckillId}, #{money}, #{userPhone}) </insert> |
SQL語句相對不是很復(fù)雜。減庫存:執(zhí)行update語句,令stock_count字段依次減一,并且當(dāng)前要在一系列where條件的限制下;新增訂單信息:保存訂單數(shù)據(jù),這里為接口防刷用聯(lián)合主鍵seckillId, userPhone,如果同一個用戶多次搶購?fù)患唐穼?dǎo)致主鍵沖突會直接報(bào)錯,為了避免系統(tǒng)不直接報(bào)錯設(shè)計(jì)了ignore實(shí)現(xiàn)主鍵沖突就直接返回0表示該條SQL執(zhí)行失敗。
拓展
上面我使用了<、>的語法其實(shí)代表的是>= <=這種符號,因?yàn)樵贛ybatis中編寫的SQL語句如果直接使用>=或<=這種判斷條件可能會報(bào)錯,我這里提供一種簡單的解決方案就是用這種英文符號代替:
| < | < |
| <= | <= |
| > | > |
| >= | >= |
| & | & |
| ‘ | ' |
| “ | " |
order表中findById方法
之前在SeckillOrder.java實(shí)體類中我們注入了Seckill屬性,用于可以根據(jù)查詢seckill_order表的同時查詢到其對應(yīng)的seckill表數(shù)據(jù),對應(yīng)的接口定義如下:
| /** * 根據(jù)秒殺商品ID查詢訂單明細(xì)數(shù)據(jù)并得到對應(yīng)秒殺商品的數(shù)據(jù),因?yàn)槲覀冊賁eckillOrder中已經(jīng)定義了一個Seckill的屬性 * * @param seckillId * @return */ SeckillOrder findById(long seckillId); |
對應(yīng)的SQL如下:
| <select id="findById" resultType="SeckillOrder"> SELECT so.seckill_id, so.user_phone, so.money, so.create_time, so.state, s.seckill_id "seckill.seckill_id", s.title "seckill.title", s.cost_price "seckill.cost_price", s.create_time "seckill.create_time", s.start_time "seckill.start_time", s.end_time "seckill.end_time", s.stock_count "seckill.stock_count" FROM seckill_order so INNER JOIN seckill s ON so.seckill_id = s.seckill_id WHERE so.seckill_id = #{seckillId} </select> |
這個SQL看似復(fù)雜些,但是就是僅僅的多表(兩張表)查詢語句:根據(jù)seckill_order表中的seckill_id字段查詢seckill表中seckill_id字段值對應(yīng)的數(shù)據(jù)(也就是說:對于多表查詢,其實(shí)兩張表之間必然存在一定的字段關(guān)聯(lián)關(guān)系,不一定是外鍵關(guān)聯(lián),當(dāng)然我們也不建議用外鍵關(guān)聯(lián)兩張表)。
其中findById的SQL中類似s.seckill_id "seckill.seckill_id"語句其實(shí)是s.seckill_id as "seckill.seckill_id",這里省略了as(別名);而INNER JOIN語句正是查詢?nèi)魞蓮埍碇兄杏窒嗤侄蔚钠ヅ渲稻透鶕?jù)兩張表關(guān)聯(lián)字段查詢兩張表的數(shù)據(jù)。這也可以使用<resultMap>中的<association>標(biāo)簽來實(shí)現(xiàn),用于查詢兩張關(guān)聯(lián)表的數(shù)據(jù),如:
| <resultMap id="findById" type="SeckillOrder"> <id column="seckill_id" property="seckillId"/> <result column="user_phone" property="userPhone"/> ... <association property="seckill" javaType="Seckill"> <id column="seckill_id" property="seckillId"/> <result column="title" property="title"/> ... </association> </resultMap> |
如以上也是一種映射另外一張表數(shù)據(jù)的方式(當(dāng)然使用這種方式在寫SQL的時候需要指定限制條件where s.seckill_id = so.seckill_id強(qiáng)調(diào)兩張表中的seckill_id字段值相同)。
測試
在編寫了Mybatis的映射接口和XML映射文件,我們可以編寫一個測試類來測試一下接口和XML配置是否正確。由于我們使用IDEA開發(fā)工具,打開接口文件用快捷鍵Alt + Enter(我這里用的Mac系統(tǒng))顯示一個面板,選擇Create Test快速創(chuàng)建本文件的測試類。
由于使用的SpringBoot框架,新創(chuàng)建的測試類位于/src/test/java/目錄下,我們舉例說明,比如創(chuàng)建SeckillMapper接口的測試文件:SeckillMapperTest.java
| public class SeckillMapperTest { @Autowired private SeckillMapper seckillMapper; @Test public void findAll() { } @Test public void findById() { } @Test public void reduceStock() { } } |
以上就是使用IDEA快捷鍵創(chuàng)建的測試類,我們僅以findAll()方法舉例說明一下如何使用SpringBoot的測試類。如下:
此處的源碼請參看:Github
| @RunWith(SpringJUnit4ClassRunner.class) //@ContextConfiguration("classpath:application.yml") @SpringBootTest public class SeckillMapperTest { @Autowired private SeckillMapper seckillMapper; @Test public void findAll() { List<Seckill> all = seckillMapper.findAll(); for (Seckill seckill : all) { System.out.println(seckill.getTitle()); } } @Test public void findById() { } @Test public void reduceStock() { } } |
SpringBoot的測試類和傳統(tǒng)Spring框架測試類的最大區(qū)別就是不再使用@ContextConfiguration()注解去加載配置文件,取而代之的是使用@SpringBootTest注解。因?yàn)镾pringBoot已經(jīng)嚴(yán)格規(guī)定了配置文件放在resources目錄下,且一般是.properties或.yml結(jié)尾。如果你再使用@ContextConfiguration()注解加載配置文件反而會報(bào)錯。
總結(jié)
以上是生活随笔為你收集整理的SpringBoot实现Java高并发秒杀系统之DAO层开发(一)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 2018-2019-1 20165226
- 下一篇: SFB 项目经验-57-Skype fo