SSM框架學習之高併發秒殺業務--筆記2-- DAO層

上節中利用Maven建立了項目,並導入了全部的依賴,這節來進行DAO層的設計與開發html


第一步,建立數據庫和表。前端

首先分析業務,這個SSM框架整合案例是作一個商品的秒殺系統,要存儲的有:1.待秒殺的商品的相關信息。2:秒殺成功的交易記錄。java

因此建兩張表:第一張秒殺庫存表,一張秒殺成功明細表,下面是sql腳本mysql

 1 -- 數據庫初始化腳本
 2 
 3 -- 建立數據庫
 4 CREATE DATABASE seckill;
 5 -- 使用數據庫
 6 use seckill;
 7 -- 建立秒殺庫存表
 8 CREATE TABLE seckill(
 9   `seckill_id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '商品庫存id',
10   `name` VARCHAR(120) NOT NULL COMMENT '商品名稱',
11   `number`int NOT NULL COMMENT '庫存數量',
12   `create_time` TIMESTAMP NOT NULL DEFAULT current_timestamp COMMENT '建立時間',
13   `start_time` timestamp NOT NULL COMMENT '秒殺開啓時間',
14   `end_time` TIMESTAMP NOT NULL COMMENT '秒殺結束時間',
15   PRIMARY KEY (seckill_id),
16   KEY idx_start_time(start_time),
17   KEY idx_end_time(end_time),
18   KEY idx_create_time(create_time)
19 )ENGINE=InnoDB AUTO_INCREMENT=1000 DEFAULT CHARSET=utf8 COMMENT='秒殺庫存表';
20 
21   -- 初始化數據
22 insert into
23   seckill(name,number,start_time,end_time)
24   values
25     ('1000元秒殺iphone6',100,'2015-11-01 00:00:00','2015-11-02 00:00:00'),
26     ('500元秒殺ipad2',200,'2015-11-01 00:00:00','2015-11-02 00:00:00'),
27     ('300元秒殺小米4',300,'2015-11-01 00:00:00','2015-11-02 00:00:00'),
28     ('200元秒殺紅米note',400,'2015-11-01 00:00:00','2015-11-02 00:00:00');
29 -- 秒殺成功明細表
30 -- 用戶登陸認證相關信息
31 CREATE TABLE success_killed(
32   `seckill_id` BIGINT NOT NULL COMMENT '秒殺商品id',
33   `user_phone` BIGINT NOT NULL COMMENT '用戶手機號',
34   `state` TINYINT NOT NULL DEFAULT -1 COMMENT '狀態標識,-1:無效 0:成功 1:已付款 2:已發貨',
35   `create_time` TIMESTAMP NOT NULL COMMENT '建立時間',
36   PRIMARY KEY (seckill_id,user_phone),/*聯合主鍵*/
37   KEY idx_create_time(create_time)
38 )ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='秒殺成功明細表';

關於其中的說明:web

1-->第12行:CURRENT_TIMESTAMP。當要向數據庫執行insert操做時,若是有個timestamp字段屬性設爲CURRENT_TIMESTAMP,則不管這個字段有沒有set值都插入當前系統時間。參考了這篇博文:redis

  http://blog.csdn.net/aitcax/article/details/41849591spring

2-->這裏要將create_time放在start_time和end_time的前面,要否則會報錯,具體緣由是什麼我也不知道,但願有高手賜教。sql

3-->16行的 「KEY idx_start_time(start_time)」,其中KEY關鍵字是用來建索引的,具體的解釋見這篇博文:http://blog.csdn.net/inaoen/article/details/24108969數據庫

4-->19行的 「ENGINE=InnoDB」,指定存儲引擎是innoDB,MySQl有多種存儲引擎,能支持事務的只有innoDB。innoDB 是 MySQL 上第一個提供外鍵約束的數據存儲引擎,除了提供事務處理外,InnoDB 還支持行鎖,提供和 Oracle 同樣的一致性的不加鎖讀取,能增長併發讀的用戶數量並提升性能,不會增長鎖的數量。InnoDB 的設計目標是處理大容量數據時最大化性能,它的 CPU 利用率是其餘全部基於磁盤的關係數據庫引擎中最有效率的。InnoDB 是一套放在MySQL 後臺的完整數據庫系統,InnoDB 有它本身的緩衝池,能緩衝數據和索引,InnoDB 還把數據和索引存放在表空間裏面,可能包含好幾個文件,這和 MyISAM 表徹底不一樣,在 MyISAM 中,表被存放在單獨的文件中,InnoDB 表的大小隻受限於操做系統文件的大小,通常爲 2GB.apache

 

第二步,實體和DAO接口編碼

說道這裏我想發表一下感慨,感受編程是件很奇怪的事,視頻能看的懂,照着視頻作也能夠,可是一旦讓本身獨立去作的話就會發現本身無從下手。。。,本身打的代碼,項目經驗仍是太少了

首先怎麼進行下一步呢?就按照已有的去推吧,慢慢添磚加瓦,做爲一個大菜鳥也只能這樣了。如今手頭上只有兩張表,一張表對應一個實體,那麼就先把實體類先建好吧。

在項目的java目錄下建立實體包:org.seckill.entity,並在這個包下建兩個實體類Seckill和SuccessKilled

 1 package org.seckill.entity;
 2 
 3 import java.util.Date;
 4 
 5 //待秒殺的商品
 6 public class Seckill {
 7     //1.private int seckill_Id; 數據庫中的id是bigint,對應到java中應該是long類型
 8     //2.java命名法,和數據庫不一樣,不一樣單詞之間不須要加下劃線,首字母大寫便可
 9     private long seckillId; //商品id
10     private String name; //商品名稱
11     private int number; //商品庫存
12 
13     //數據庫中的timestamp型日期類型對應到java中就是Date,應該說只要數據庫中是日期類型,對應到java中都是Date
14     private Date startTime; //秒殺開始時間
15     private Date endTime; //秒殺結束時間
16     private Date createTime; //商品入庫時間
17 
18     public long getSeckillId() {
19         return seckillId;
20     }
21 
22     public void setSeckillId(long seckillId) {
23         this.seckillId = seckillId;
24     }
25 
26     public String getName() {
27         return name;
28     }
29 
30     public void setName(String name) {
31         this.name = name;
32     }
33 
34     public int getNumber() {
35         return number;
36     }
37 
38     public void setNumber(int number) {
39         this.number = number;
40     }
41 
42     public Date getStartTime() {
43         return startTime;
44     }
45 
46     public void setStartTime(Date startTime) {
47         this.startTime = startTime;
48     }
49 
50     public Date getEndTime() {
51         return endTime;
52     }
53 
54     public void setEndTime(Date endTime) {
55         this.endTime = endTime;
56     }
57 
58     public Date getCreateTime() {
59         return createTime;
60     }
61 
62     public void setCreateTime(Date createTime) {
63         this.createTime = createTime;
64     }
65 
66     @Override
67     public String toString() {
68         return "Seckill{" +
69                 "seckillId=" + seckillId +
70                 ", name='" + name + '\'' +
71                 ", number=" + number +
72                 ", startTime=" + startTime +
73                 ", endTime=" + endTime +
74                 ", createTime=" + createTime +
75                 '}';
76     }
77 }
 1 package org.seckill.entity;
 2 
 3 import java.util.Date;
 4 
 5 /**
 6  * 成功秒殺的記錄明細
 7  */
 8 public class SuccessKilled {
 9     private long seckillId; //成功秒殺的商品id
10     private long userPhone; //用戶電話
11     private int state; //用來記錄交易狀態
12     private Date createTime; //記錄建立時間
13 
14     //變通,多對一
15     private Seckill seckill;
16 
17     public long getSeckillId() {
18         return seckillId;
19     }
20 
21     public void setSeckillId(long seckillId) {
22         this.seckillId = seckillId;
23     }
24 
25     public long getUserPhone() {
26         return userPhone;
27     }
28 
29     public void setUserPhone(long userPhone) {
30         this.userPhone = userPhone;
31     }
32 
33     public int getState() {
34         return state;
35     }
36 
37     public void setState(int state) {
38         this.state = state;
39     }
40 
41     public Date getCreateTime() {
42         return createTime;
43     }
44 
45     public void setCreateTime(Date createTime) {
46         this.createTime = createTime;
47     }
48 
49     public Seckill getSeckill() {
50         return seckill;
51     }
52 
53     public void setSeckill(Seckill seckill) {
54         this.seckill = seckill;
55     }
56 
57     @Override
58     public String toString() {
59         return "SuccessKilled{" +
60                 "seckillId=" + seckillId +
61                 ", userPhone=" + userPhone +
62                 ", state=" + state +
63                 ", createTime=" + createTime +
64                 '}';
65     }
66 }

SuccessKilled中要有成員變量Seckill,  憑秒殺記錄能夠拿到被秒殺的商品對象,一個商品可能會有多個秒殺記錄,只有庫存足夠就能被不一樣的用戶秒殺,因此這秒殺記錄和商品是多對一的關係。經過在多方聲明一方的引用來限定這種多對一的關係,這裏若是還有什麼別的東西要注意的話,但願有高手告知,謝謝。

建好實體類後,如今就開始分析這個項目針對於實體類有哪些數據操做從而建DAO層的類,在java目錄下建包:org.seckill.dao. 分析:

咱們這個是實現商品的在線秒殺系統,具體流程是前端網頁上顯示全部的商品列表,並有詳細頁連接,點進去以後若是當前時間在秒殺時間範圍以內的話則顯示秒殺按鈕開始秒殺,未到時間顯示秒殺倒計時,已過了秒殺時間則顯示秒殺一結束。而後是分析秒殺執行的邏輯,若是秒殺商品有庫存的話則對數據庫的商品表進行dao操做,減庫存並添加秒殺記錄秒殺成功不然拋出庫存不足的異常。還要能按照商品Id查商品,由上分析,要有的dao至少功能有

1.查詢全部Seckill表全部記錄,返回一個Seckill的List,傳給前端頁面顯示。

2.秒殺成功時的減Seckill庫存操做。

3.秒殺成功時添加SuccessKilled記錄的操做。

4.按照商品id查找到相應的商品。

如下是視頻中老師設計的接口,有的我沒考慮到。。。。

SeckillDao

 1 package org.seckill.dao;
 2 
 3 import org.apache.ibatis.annotations.Param;
 4 import org.seckill.entity.Seckill;
 5 
 6 import java.util.Date;
 7 import java.util.List;
 8 import java.util.Map;
 9 
10 /**
11  * Created by yuxue on 2016/10/12.
12  */
13 public interface SeckillDao {
14 
15     /**
16      * 減庫存
17      * @param seckillId
18      * @param killTime
19      * @return 若是影響的行數大於1,表示更新記錄行數
20      */
21     int reduceNumber(@Param("seckillId") long seckillId,@Param("killTime") Date killTime);
22 
23     /**
24      * 根據id查詢秒殺對象
25      * @param seckillId
26      * @return
27      */
28     Seckill queryById(long seckillId);
29 
30     /**
31      * 根據偏移量查詢秒殺商品列表
32      * @param offset
33      * @param limit
34      * @return
35      */
36     List<Seckill> queryAll(@Param("offset") int offset, @Param("limit") int limit);
37 
38     /**
39      * 使用存儲過程執行秒殺
40      * @param paramMap
41      */
42     void killByProcedure(Map<String,Object> paramMap);
43 }

 SuccessKilledDao

 1 package org.seckill.dao;
 2 
 3 import org.apache.ibatis.annotations.Param;
 4 import org.seckill.entity.SuccessKilled;
 5 
 6 /**
 7  * Created by yuxue on 2016/10/12.
 8  */
 9 public interface SuccesskilledDao {
10 
11     /**
12      * 插入購買明細,可過濾重複
13      * @return 插入的行數
14      */
15     int insertSucessSeckilled(@Param("seckillId") long seckillId, @Param("userPhone") long userPhone);
16 
17     /**
18      * 根據id查詢SuccessKilled並攜帶秒殺產品實體對象
19      * @param seckillId
20      * @return
21      */
22     SuccessKilled queryByIdWithSeckill(@Param("seckillId") long seckillId, @Param("userPhone") long userPhone);
23 
24 }

這裏要說明的是:

1.存儲過程這個暫且不用管它,後面會分析這個是幹什麼的

2.關於減庫存reduceNumber()這個方法,傳入的參數是秒殺商品的id,以及秒殺時間,若是秒殺時間在設定的秒殺時間範圍以內則執行減庫存。返回影響的行數,根據這個來判斷減庫存有沒有成功。我在這裏開始考慮的時候沒有考慮到killTime這個參數。想把判斷是否在秒殺時間的判斷放在service層,可是對於秒殺時間是否合法的判斷要和商品的開始秒殺時間個結束時間比較,因此對於取得這兩個參數的操做一定是數據庫操做,而對數據庫的操做應放在dao層裏的。

3.關於註解@Param,由於java沒有保存形參的機制,對於queryAll(int offset, int limit)這個方法來講,運行時形參offset和limit會被java解釋成arg0和arg1,由於後面使用Mybatis用xml文件映射DAO接口時,sql語句裏會根據參數名來匹配的,因此當有兩個以上形參時,必定要用@Param註解來指定參數名。spring中@param和mybatis中@param使用區別

 

第三步,Mybatis實現DAO接口

 關於Mybatis:1.和Hibernate同樣,是個對象映射框架,即orm框架

           2.Mybatis的特色:參數+SQL=Entity/List,其中SQL語句是徹底本身去寫的,提供了很大的靈活性

       3.SQL能夠寫在XML文件中,也能夠寫在註解中。

         4.Mybatis如何實現DAO接口:1)API編程的方式    2)Mapper機制自動實現DAO接口

這裏使用Mapper機制實現DAO接口,在resources文件目錄下新建mapper目錄來存放映射文件,映射文件和DAO的接口類是相對應的。這個案例中有兩個DAO:SeckillDao和SuccessKilledDao,因此創建以下映射文件:

SeckillDao.xml

 1 <?xml version="1.0" encoding="UTF-8" ?>
 2 <!DOCTYPE mapper
 3         PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
 4         "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
 5 
 6 <mapper namespace="org.seckill.dao.SeckillDao">
 7 
 8 
 9     <select id="queryById" resultType="Seckill" parameterType="long">
10         select seckill_id,name,number,start_time, end_time, create_time
11         from seckill
12         where seckill_id=#{seckillId}
13     </select>
14 
15     <update id="reduceNumber">
16         update
17           seckill
18         set
19           number=number -1
20         where seckill_id = #{seckillId}
21         and start_time <![CDATA[ <= ]]>   #{killTime}
22         and end_time >= #{killTime}
23         and number > 0;
24     </update>
25 
26     <select id="queryAll" resultType="Seckill">
27         select seckill_id,name,number,start_time,end_time,create_time
28         from seckill
29         order by create_time DESC
30         limit #{offset},#{limit}
31     </select>
32     <!--mybatis調用存儲過程-->
33     <select id="killByProcedure" statementType="CALLABLE">
34         call execute_seckill(
35         #{seckillId,jdbcType=BIGINT,mode=IN},
36         #{phone,jdbcType=BIGINT,mode=IN},
37         #{killTime,jdbcType=TIMESTAMP,mode=IN},
38         #{result,jdbcType=INTEGER ,mode=OUT}
39 40     </select>
41 
42 </mapper>

 

SuccessKilledDao.xml

 1 <?xml version="1.0" encoding="UTF-8" ?>
 2 <!DOCTYPE mapper
 3         PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
 4         "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
 5 <mapper namespace="org.seckill.dao.SuccesskilledDao">
 6     <!--目的:爲Dao接口方法提供sql語句配置-->
 7     <insert id="insertSucessSeckilled">
 8         insert ignore into success_killed(seckill_id,user_phone,state)
 9         values (#{seckillId},#{userPhone},0)
10     </insert>
11 
12     <select id="queryByIdWithSeckill" resultType="Successkilled">
13         <!--根據id查詢SuccessKilled並攜帶Seckill實體-->
14         <!--如何告訴Mybatis把結果映射到SuccessKilled同時映射seckill屬性-->
15         <!--能夠自由控制SQL-->
16         select
17           sk.seckill_id,
18           sk.user_phone,
19           sk.create_time,
20           sk.state,
21         <!--經過別名的方式來將查詢結果封裝到Successkilled的seckill屬性中-->
22           s.seckill_id "seckill.seckill_id",
23           s.name "seckill.name",
24           s.number "seckill.number",
25           s.start_time "seckill.start_time",
26           s.end_time "seckill.end_time",
27           s.create_time "seckill.create_time"
28         from success_killed sk
29         inner join seckill s on sk.seckill_id=s.seckill_id
30         where sk.seckill_id=#{seckillId} and sk.user_phone=#{userPhone}
31     </select>
32 </mapper>

這裏要說明的是:

1. <![CDATA[ <= ]]>,由於小於號 < 在xml文件中會被解讀成一個新的xml標籤的開始,因此在xml的文本節點中不容許直接使用 <  ,  而CDATA 區段(CDATA section)中的文本會被解析器忽略。

2.「insert ignore into success_killed(seckill_id,user_phone,state)」,這裏的sql使用可ignore關鍵字,目的是當插入是若是主鍵衝突了就讓mysql返回0而不是直接報錯,這樣便於咱們處理。

3. 對於SuccessKilledDao中的queryByIdWithSeckill方法,它返回的是SuccessKilled實體,可是它有個Seckill屬性,如今的問題是如何告訴Mybatis把結果映射到SuccessKilled同時映射seckill屬性。 經過別名的方式來將查詢結果封裝到Successkilled的seckill屬性中。

4.關於插入秒殺記錄這個方法,將記錄其狀態的state字段默認爲0,表示交易已成功。由於既然秒殺記錄已加入成功秒殺記錄表,則必定是完成了py交易。

5.關於sql中的各類鏈接:SQL錶鏈接查詢(inner join、full join、left join、right join)

6.從這裏能夠看出Mybatis相對於其它orm框架的特色是:能夠自由控制sql

 

第四步:Mybatis與Spring的整合

第一個框架間的整合來了!整合目標:1.更少的編碼:只寫接口,不寫實現(利用映射機制實現)

                 2.更少的配置:包掃描機制,別名系統,自動掃描配置文件,Mybatis和Spring整合後DAO接口的實現類能夠自動的注入到Spring容器中

                 3.足夠的靈活性:本身定製sql,自由傳參,結果集自動賦值

在resources目錄下新建Mybatis的配置文件mybatis-config.xml,代碼以下:

 1 <?xml version="1.0" encoding="UTF-8" ?>
 2 <!DOCTYPE configuration
 3         PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
 4         "http://mybatis.org/dtd/mybatis-3-config.dtd">
 5 <configuration>
 6     <!--配置全局屬性-->
 7     <settings>
 8         <!--使用jdbc的getGerneratedKeys 獲取數據庫自增主鍵值-->
 9         <setting name="useGeneratedKeys" value="true"/>
10          <!--使用列別名替換列名 默認:true
11              select name as title from table-->
12         <setting name="useColumnLabel" value="true"/>
13         <!--開啓駝峯命名轉換:Table(create_time)->Entity(createTime)-->
14         <setting name="mapUnderscoreToCamelCase" value="true"/>
15     </settings>
16 </configuration>

 

在resources目錄下新建spring目錄用來存放spring配置的xml文件。首先新建spring-dao.xml文件完成dao層的配置

spring-dao.xml

 1 <?xml version="1.0" encoding="UTF-8"?>
 2 <beans xmlns="http://www.springframework.org/schema/beans"
 3        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 4        xmlns:context="http://www.springframework.org/schema/context"
 5        xsi:schemaLocation="http://www.springframework.org/schema/beans
 6       http://www.springframework.org/schema/beans/spring-beans.xsd
 7       http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd">
 8     <!--配置整合mybatis過程-->
 9     <!--1:配置數據庫相關參數properties的屬性:${url}-->
10     <context:property-placeholder location="classpath:jdbc.properties"/>
11     <!--2.數據庫鏈接池-->
12     <bean id="datasource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
13         <!--配置鏈接池屬性-->
14         <property name="driverClass" value="${jdbc.driver}"/>
15         <property name="jdbcUrl" value="${url}"/>
16         <property name="user" value="${username}"/>
17         <property name="password" value="${password}"/>
18 
19         <!--c3p0的私有屬性-->
20         <property name="maxPoolSize" value="30"/>
21         <property name="minPoolSize" value="10"/>
22         <!--關閉鏈接後不自動commit,默認是false-->
23         <property name="autoCommitOnClose" value="false"/>
24         <!--獲取鏈接超時時間-->
25         <property name="checkoutTimeout" value="1000"/>
26         <!--當獲取鏈接失敗後重試次數-->
27         <property name="acquireRetryAttempts" value="2"/>
28     </bean>
29 
30     <!--約定大於配置-->
31     <!--3.配置SqlSessionFactory對象-->
32     <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
33         <!--注入數據庫鏈接池-->
34         <property name="dataSource" ref="datasource"/>
35         <!--配置Mybatis全局配置文件:mybatis-config.xml-->
36         <property name="configLocation" value="classpath:mybatis-config.xml"/>
37         <!--掃描entity包 使用別名-->
38         <property name="typeAliasesPackage" value="org.seckill.entity"/>
39         <!--掃描sql配置文件:mapper須要的xml文件-->
40         <property name="mapperLocations" value="classpath:mapper/*.xml"/>
41     </bean>
42 
43     <!--4.配置掃描Dao接口包,動態實現Dao接口,注入到spring容器中-->
44     <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
45         <!--注入sqlSessionFactory-->
46         <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
47         <!--給出須要掃描的Dao接口包-->
48         <property name="basePackage"  value="org.seckill.dao"/>
49     </bean>
50 
51     <!--RedisDao-->
52     <bean id="redisDao" class="org.seckill.dao.cache.RedisDao">
53         <constructor-arg index="0" value="localhost"/>
54         <constructor-arg index="1" value="6379"/>
55     </bean>
56 </beans>

說明:

1.配置數據庫相關參數,這些參數一般都會寫在一個properties文件中,便於之後修改,<context:property-placeholder location="classpath:jdbc.properties"/>導入properties文件,classpath路徑下的jdbc.properties文件。maven項目中classpath路徑就是java和resources下面的目錄。resources目錄下的jdbc.properties文件爲

1 jdbc.driver=com.mysql.jdbc.Driver
2 url=jdbc:mysql://127.0.0.1:3306/seckill?useUnicode=true&characterEncoding=utf8
3 username=root
4 password=yuxue

 這裏有個坑就是jdbc.properties 裏面 username到了配置文件裏${username}結果變成了系統管理員的名字。可是我這裏並無出現這樣的錯誤啊,不知道鬧哪樣。

2.配置數據庫鏈接池

3.配置SqlSessionFactory對象

4.配置掃描Dao接口包,動態實現Dao接口,注入到spring容器中

5.RedisDao:優化用的,這裏先無論它

     

第五步:測試與問題排查

這步出現了個極爲坑爹的事情,我在test文件目錄下建不了測試用例,也沒法新建任何源代碼文件。。。。摸索了一番後,原來是項目配置文件seckill.iml裏沒有配置此文件目錄爲源代碼目錄,因此就不能往這個目錄裏添加任何源代碼

seckill.iml文件裏各項配置的說明,有些是本身猜的,不對的話請指點指點

 1 <?xml version="1.0" encoding="UTF-8"?>
 2 <module org.jetbrains.idea.maven.project.MavenProjectsManager.isMavenModule="true" type="JAVA_MODULE" version="4">
 3   <component name="FacetManager">
 4     <facet type="web" name="Web">
 5       <configuration>
 6         <descriptors>
 7           <deploymentDescriptor name="web.xml" url="file://$MODULE_DIR$/src/main/webapp/WEB-INF/web.xml" /> <--配置web.xml文件的路徑
 8         </descriptors>
 9         <webroots>
10           <root url="file://$MODULE_DIR$/src/main/webapp" relative="/" /> <--配置web根目錄,relative屬性不知道幹嗎用的
11         </webroots>
12         <sourceRoots>
13           <root url="file://$MODULE_DIR$/src/main/java" />
14           <root url="file://$MODULE_DIR$/src/main/resources" />
15         </sourceRoots>
16       </configuration>
17     </facet>
18     <facet type="Spring" name="Spring">
19       <configuration>
20         <fileset id="fileset" name="Spring Application Context" removed="false"> <--配置Spring配置文件的位置
21           <file>file://$MODULE_DIR$/src/main/resources/spring/spring-web.xml</file>
22           <file>file://$MODULE_DIR$/src/main/resources/spring/spring-service.xml</file>
23           <file>file://$MODULE_DIR$/src/main/resources/spring/spring-dao.xml</file>
24         </fileset>
25       </configuration>
26     </facet>
27   </component>
28   <component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_1_5" inherit-compiler-output="false">
29     <output url="file://$MODULE_DIR$/target/classes" /> <--定義編譯的calss文件的存放位置
30     <output-test url="file://$MODULE_DIR$/target/test-classes" />
31     <content url="file://$MODULE_DIR$">
32       <sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" />  <--聲明 src/main/java目錄是源代碼目錄,而且不是測試test源代碼目錄
33       <sourceFolder url="file://$MODULE_DIR$/src/main/resources" type="java-resource" /> <-- 聲明resources目錄
34       <sourceFolder url="file://$MODULE_DIR$/src/test/java" isTestSource="true" /> <--聲明 src/test/java是test的代碼目錄,這樣的話Ctrl+shift+T快捷鍵生成的測試類就會自動放在這個目錄下
35       <sourceFolder url="file://$MODULE_DIR$/src/test/resources" type="java-test-resource" /> <--聲明test目錄下的resources目錄
36       <excludeFolder url="file://$MODULE_DIR$/target" /> 
37     </content>
38     <orderEntry type="jdk" jdkName="1.7" jdkType="JavaSDK" />
39     <orderEntry type="sourceFolder" forTests="false" />
40   </component>
41 </module>

以上的一些是我我的的理解。。。 無論怎樣總算是解決了這個問題。

 

測試用例SeckillDaoTest

 1 package org.seckill.dao;
 2 
 3 import org.junit.Test;
 4 import org.junit.runner.RunWith;
 5 import org.seckill.entity.Seckill;
 6 import org.springframework.test.context.ContextConfiguration;
 7 import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
 8 import org.springframework.transaction.annotation.Transactional;
 9 
10 import javax.annotation.Resource;
11 import java.util.Date;
12 import java.util.List;
13 
14 /**
15  * 配置spring和junit整合,jnit啓動時加載springIOC容器
16  * spring-test,junit
17  */
18 
19 @RunWith(SpringJUnit4ClassRunner.class)
20 //告訴junit spring配置文件
21 @ContextConfiguration({"classpath:spring/spring-dao.xml"})
22 public class SeckillDaoTest {
23 
24     //注入Dao實現類
25     @Resource
26     private SeckillDao seckillDao;
27 
28     @Test
29     public void reduceNumber() throws Exception {
30         Date killTime=new Date();
31         int updateCount=seckillDao.reduceNumber(1004L,killTime);
32         System.out.println(updateCount);
33     }
34 
35     @Test
36     public void queryById() throws Exception {
37         //long id=1000;
38         long id=1004;
39         Seckill seckill=seckillDao.queryById(id);
40         /*卡在這裏好久,由於個人數據庫中數據的編號是從1004開始
41           因此這裏的查詢結果seckill確定是null*/
42         System.out.println(seckill.getName());
43         System.out.println(seckill);
44     }
45 
46 
47     @Test
48     public void queryAll() throws Exception {
49         /*
50         binding.BindingException: Parameter 'offset' not found. Available parameters are [1, 0, param1, param2]
51         */
52         //java不會保存形參的記錄,queryAll(int offset,int limit)會被解釋成queryAll(int arg0,int arg1)
53         //因此會出現綁定錯誤,須要在接口的形參中使用@Param註解
54         List<Seckill> seckills=seckillDao.queryAll(0,100);
55         for(Seckill seckill:seckills){
56             System.out.println(seckill);
57         }
58     }
59 
60 }

這裏本身第二次作的時候出現了個錯誤,仍是本身對SQL語句不熟悉,select * from 表名 limit  offset, limit; 這個是sql的分頁查詢,offset是數據庫記錄中的偏移量,limit是從偏移量開始一共查幾條記錄。數據庫中的記錄無論主鍵id從幾號開始編號,數據庫中的記錄像數組同樣,都是從0開始算,seckillDao.queryAll(0,100)表示從第一條記錄開始取,一共取100條記錄,我這裏寫成了seckillDao.queryAll(1004,4)。。。。。。汗。

SuccessKilledDaoTest

 1 package org.seckill.dao;
 2 
 3 import org.junit.Test;
 4 import org.junit.runner.RunWith;
 5 import org.seckill.entity.SuccessKilled;
 6 import org.springframework.test.context.ContextConfiguration;
 7 import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
 8 
 9 import javax.annotation.Resource;
10 
11 import static org.junit.Assert.*;
12 
13 /**
14  * Created by yuxue on 2016/10/15.
15  */
16 @RunWith(SpringJUnit4ClassRunner.class)
17 //告訴junit spring配置文件
18 @ContextConfiguration({"classpath:spring/spring-dao.xml"})
19 public class SuccesskilledDaoTest {
20 
21     @Resource
22     private SuccesskilledDao successkilledDao;
23 
24     @Test
25     public void insertSucessSeckilled() throws Exception {
26         long id=1004L;
27         long phone=13225535035L;
28         int insertCount=successkilledDao.insertSucessSeckilled(id,phone );
29         System.out.println("insertCount="+insertCount);
30     }
31 
32     @Test
33     public void queryByIdWithSeckill() throws Exception {
34         long id=1004L;
35         long phone=13225535035L;
36         SuccessKilled successKilled=successkilledDao.queryByIdWithSeckill(id,phone);
37         System.out.println(successKilled);
38         System.out.println(successKilled.getSeckill());
39     }
40 
41 }

 由於秒殺記錄裏一條秒殺商品記錄可能會被不一樣的用戶秒殺,因此秒殺記錄裏要用秒殺商品id和用戶手機號做爲聯合主鍵,那麼查詢單條記錄queryByIdWithSeckill(id,phone)時要按照這個主鍵來查查詢,同時查詢記錄中要攜帶秒殺商品實體

 這裏在 successkilledDao.insertSucessSeckilled(id,phone ) 這個方法上我又出錯了。。。。,查了下我本身寫的代碼 緣由在於映射文件

<insert id="insertSucessSeckilled">
         insert ignore into success_killed(seckill_id,user_phone,state)
         values (#{seckillId},#{userPhone},0)
</insert>

我在這裏寫插入語句的時候忘了寫 (seckill_id,user_phone,state)由於是插入部分字段,因此寫插入語句時要指明要插入的列是哪幾列,不然sql對應不上相應的字段。

而後又一件坑爹的事情來了。。。。 

<select id="queryByIdWithSeckill" resultType="Successkilled">
13         <!--根據id查詢SuccessKilled並攜帶Seckill實體-->
14         <!--如何告訴Mybatis把結果映射到SuccessKilled同時映射seckill屬性-->
15         <!--能夠自由控制SQL-->
16         select
17           sk.seckill_id,
18           sk.user_phone,
19           sk.create_time,
20           sk.state,
21         <!--經過別名的方式來將查詢結果封裝到Successkilled的seckill屬性中-->
22           s.seckill_id "seckill.seckill_id",
23           s.name "seckill.name",
24           s.number "seckill.number",
25           s.start_time "seckill.start_time",
26           s.end_time "seckill.end_time",
27           s.create_time "seckill.create_time"
28         from success_killed sk
29         inner join seckill s on sk.seckill_id=s.seckill_id
30         where sk.seckill_id=#{seckillId} and sk.user_phone=#{userPhone}
31     </select>

這裏我本身寫的時候,測試報了個sql語法錯誤。。。。。 在27行到28行左右,本身仔細對了下上面的代碼,感受和上面寫得同樣但就是報錯,而後終於,終於發現了我在寫

27           s.create_time "seckill.create_time"
28         from success_killed sk

這裏的時候,多加了個逗號。。。,寫成了 s.create_time "seckill.create_time",

                  from success_killed sk

暈死,浪費我幾個小時時間來找這個錯誤,也是醉了。。。。。

本身寫的時候sql語句出現了n個語法錯誤。。。。,代碼這玩意兒必定要本身寫才行

 

第六步:Dao層編碼的總結

菜就一個字,整個Dao層的編碼花費了好長時間,本身不熟啊。這裏的重點是Mybatis與Spring的整合,這裏有些東西沒有配置,好比事務和日誌,後面會詳細說明。在一些細節上出現了問題,主要是sql語句常常寫錯,仍是得多打代碼才行。下一節開始Service層的設計與編碼。

相關文章
相關標籤/搜索