前言
該篇教程主要關注MyBatis實現底層的接口,把MyBatis交給Spring來托管。數據庫連接池用的c3p0。數據庫用的MySQL。主要有2個大類:秒殺商品的查詢、秒殺明細的插入。
准備工作
1、數據庫腳本。先初始化數據庫,這里主要有2張表:seckill【秒殺商品表】、success_killed【秒殺記錄明細表】。success_killed采用雙主鍵seckill_id、user_phone。同一個商品同一個手機號只能秒殺一次,如果通過非法手段通過業務接口的話,則重復插入秒殺記錄明細時會返回0。
-- 創建數據庫
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 '庫存數量',
`start_time` TIMESTAMP NOT NULL COMMENT '秒殺開始時間',
`end_time` TIMESTAMP NOT NULL COMMENT '秒殺結束時間',
`create_time` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP 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元秒殺iphone6',100,'2016-01-01 00:00:00','2016-01-02 00:00:00'),
('800元秒殺ipad',200,'2016-01-01 00:00:00','2016-01-02 00:00:00'),
('6600元秒殺mac book pro',300,'2016-01-01 00:00:00','2016-01-02 00:00:00'),
('7000元秒殺iMac',400,'2016-01-01 00:00:00','2016-01-02 00: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 COMMENT '創建時間',
PRIMARY KEY(seckill_id,user_phone),/*聯合主鍵*/
KEY idx_create_time(create_time)
)ENGINE=INNODB DEFAULT CHARSET=utf8 COMMENT='秒殺成功明細表';
2、實現MyBatis配置文件並且交由Spring托管
mybatis-config.xml
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration> <settings> <!--使用jdbc的getGeneratedKeys獲取自增主鍵值--> <setting name="useGeneratedKeys" value="true"/> <!--使用列別名替換列名,默認值true--> <setting name="useColumnLabel" value="true"/> <!--開啟駝峰命名法 字段名seckill_Id 對應屬性名seckillId --> <setting name="mapUnderscoreToCamelCase" value="true"/> </settings> </configuration>
spring-dao.xml
這里關鍵是sqlsessionfactory的配置,需要指定mybatis全局配置文件、mapper.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 http://www.springframework.org/schema/context/spring-context.xsd"> <!--1、配置數據庫相關參數--> <context:property-placeholder location="classpath:jdbc.properties"/> <!--2、配置數據庫連接池--> <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <property name="driverClass" value="${jdbc.driver}"/> <property name="jdbcUrl" value="${jdbc.url}"/> <property name="user" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> <!--數據庫連接池最大連接數--> <property name="maxPoolSize" value="30" /> <!--數據庫連接池最小連接數--> <property name="minPoolSize" value="10"/> <!--關閉連接后不自動commit--> <property name="autoCommitOnClose" value="false"/> <!--獲取連接超時時間--> <property name="checkoutTimeout" value="1000"/> <!--當獲取連接失敗時重試次數--> <property name="acquireRetryAttempts" value="3"/> </bean> <!--3、配置SqlSessionFactory--> <bean id="sessionfactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dataSource"/> <!--mybatis全局配置文件--> <property name="configLocation" value="classpath:mybatis-config.xml"/> <!--掃描entity包--> <property name="typeAliasesPackage" value="com.seckill.entity"/> <!--掃描sql xml文件--> <property name="mapperLocations" value="classpath:mapper/*.xml"/> </bean> <!--4、配置掃描Dao接口包,動態實現dao接口注入到spring容器--> <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <!--需要掃描的dao接口--> <property name="basePackage" value="com.seckill.dao"/> <!--注入sqlsessionfactory--> <property name="sqlSessionFactoryBeanName" value="sessionfactory"/> </bean> </beans>
秒殺底層接口實現
1、實現接口。這里注意Param注解,當方法只有一個參數時不用指定,如果你需要給mapper里傳多個參數則指定,也可以用HashMap傳參。
public interface SeckillDao {
/**減庫存**/
int reduceNumber(@Param("seckillId") long seckillId,@Param("killTime") Date killTime);
/**查詢秒殺商品詳情**/
Seckill queryById(long seckillId);
/**查詢所有秒殺商品**/
List<Seckill> queryAll(@Param("offset") int offset,@Param("limit") int limit);
}
public interface SuccessKillDao {
/**
* 插入秒殺商品明細
* **/
int insertSuccessKilled(@Param("seckillId") long seckillId,@Param("userPhone") long userPhone);
/**
* 根據商品id查詢商品秒殺明細,並且同時返回商品明細
* **/
SuccessKilled queryByIdWithSeckill(@Param("seckillId") long seckillId,@Param("userPhone") long userPhone);
}
2、秒殺實現。也就是mapper目錄下的*.xml文件。
SeckillDao.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.seckill.dao.SeckillDao">
<update id="reduceNumber">
update seckill set number=number-1
where seckill_Id=#{seckillId}
and start_time <![CDATA[ <= ]]> #{killTime}
and end_time <![CDATA[ >= ]]> #{killTime}
and number>0
</update>
<select id="queryById" resultType="Seckill">
select seckill_id,name,number,start_time,end_time,create_time
from seckill
where seckill_id=#{seckillId}
</select>
<select id="queryAll" resultType="Seckill">
select seckill_id,name,number,start_time,end_time,create_time
from seckill
order by create_time
limit #{offset},#{limit}
</select>
</mapper>
SuccessKillDao.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.seckill.dao.SuccessKillDao">
<!--主鍵重復時不再插入數據-->
<insert id="insertSuccessKilled">
insert ignore into success_killed (seckill_id,user_Phone,state) values (#{seckillId},#{userPhone},0)
</insert>
<select id="queryByIdWithSeckill" resultType="SuccessKilled">
select
sk.seckill_id,
sk.user_Phone,
sk.state,
sk.create_time,
s.seckill_id "seckill.seckill_id",
s.name "seckill.name",
s.number "seckill.number",
s.create_time "seckill.create_time",
s.start_time "seckill.start_time",
s.end_time "seckill.end_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>
3、OK,准備工作就緒,可以實現單元測試的方法了。
