SSM框架学习之高并发秒杀业务--笔记2-- DAO层

时间:2022-06-24 18:01:32

上节中利用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层的设计与编码。