上節中利用Maven創建了項目,並導入了所有的依賴,這節來進行DAO層的設計與開發
第一步,創建數據庫和表。
首先分析業務,這個SSM框架整合案例是做一個商品的秒殺系統,要存儲的有:1.待秒殺的商品的相關信息。2:秒殺成功的交易記錄。
所以建兩張表:第一張秒殺庫存表,一張秒殺成功明細表,下面是sql腳本
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='秒殺成功明細表';
關於其中的說明:
1-->第12行:CURRENT_TIMESTAMP。當要向數據庫執行insert操作時,如果有個timestamp字段屬性設為CURRENT_TIMESTAMP,則無論這個字段有沒有set值都插入當前系統時間。參考了這篇博文:
http://blog.csdn.net/aitcax/article/details/41849591
2-->這里要將create_time放在start_time和end_time的前面,要不然會報錯,具體原因是什么我也不知道,希望有高手賜教。
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.
第二步,實體和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層的設計與編碼。