Java 实现高并发秒杀

1 需求分析和技术难点:

(1) 分析:

     秒杀的时候:减少库存和购买记录明细两个事件保持在同一个事物中。

     使用联合查询避免同一用户多次秒杀同一商品(利用在插入购物明细表中的秒杀id和用户的唯一标识来避免)。

(2) 秒杀难点:事务和行级锁的处理

(3) 实现那些秒杀系统(以天猫的秒杀系统为例)

(4) 我们如何实现秒杀功能?

     ① 秒杀接口暴漏

     ② 执行秒杀

     ③ 相关查询

     下面我们以主要代码实现秒杀系统:

 

2.数据库设计和DAO层

(1) 数据库设计

 
  1. -- 数据库初始化脚本

  2.  
  3. -- 创建数据库

  4. CREATE DATABASE seckill;

  5. -- 使用数据库

  6. use seckill;

  7. CREATE TABLE seckill(

  8. `seckill_id` BIGINT NOT NUll AUTO_INCREMENT COMMENT '商品库存ID',

  9. `name` VARCHAR(120) NOT NULL COMMENT '商品名称',

  10. `number` int NOT NULL COMMENT '库存数量',

  11. `start_time` TIMESTAMP NOT NULL COMMENT '秒杀开始时间',

  12. `end_time` TIMESTAMP NOT NULL COMMENT '秒杀结束时间',

  13. `create_time` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',

  14. PRIMARY KEY (seckill_id),

  15. key idx_start_time(start_time),

  16. key idx_end_time(end_time),

  17. key idx_create_time(create_time)

  18. )ENGINE=INNODB AUTO_INCREMENT=1000 DEFAULT CHARSET=utf8 COMMENT='秒杀库存表';

  19.  
  20. -- 初始化数据

  21. INSERT into seckill(name,number,start_time,end_time)

  22. VALUES

  23. ('1000元秒杀iphone6',100,'2016-01-01 00:00:00','2016-01-02 00:00:00'),

  24. ('800元秒杀ipad',200,'2016-01-01 00:00:00','2016-01-02 00:00:00'),

  25. ('6600元秒杀mac book pro',300,'2016-01-01 00:00:00','2016-01-02 00:00:00'),

  26. ('7000元秒杀iMac',400,'2016-01-01 00:00:00','2016-01-02 00:00:00');

  27.  
  28. -- 秒杀成功明细表

  29. -- 用户登录认证相关信息(简化为手机号)

  30. CREATE TABLE success_killed(

  31. `seckill_id` BIGINT NOT NULL COMMENT '秒杀商品ID',

  32. `user_phone` BIGINT NOT NULL COMMENT '用户手机号',

  33. `state` TINYINT NOT NULL DEFAULT -1 COMMENT '状态标识:-1:无效 0:成功 1:已付款 2:已发货',

  34. `create_time` TIMESTAMP NOT NULL COMMENT '创建时间',

  35. PRIMARY KEY(seckill_id,user_phone),/*联合主键*/

  36. KEY idx_create_time(create_time)

  37. )ENGINE=INNODB DEFAULT CHARSET=utf8 COMMENT='秒杀成功明细表';

  38.  
  39. -- SHOW CREATE TABLE seckill;#显示表的创建信息

 

(2) Dao层和对应的实体

① Seckill.java

 
  1. package com.force4us.entity;

  2. import org.springframework.stereotype.Component;

  3. import java.util.Date;

  4.  
  5. public class Seckill {

  6. private long seckillId;

  7. private String name;

  8. private int number;

  9. private Date startTime;

  10. private Date endTime;

  11. private Date createTime;

  12. public long getSeckillId() {

  13. return seckillId;

  14. }

  15. public void setSeckillId(long seckillId) {

  16. this.seckillId = seckillId;

  17. }

  18. public String getName() {

  19. return name;

  20. }

  21. public void setName(String name) {

  22. this.name = name;

  23. }

  24. public int getNumber() {

  25. return number;

  26. }

  27. public void setNumber(int number) {

  28. this.number = number;

  29. }

  30. public Date getStartTime() {

  31. return startTime;

  32. }

  33. public void setStartTime(Date startTime) {

  34. this.startTime = startTime;

  35. }

  36. public Date getEndTime() {

  37. return endTime;

  38. }

  39. public void setEndTime(Date endTime) {

  40. this.endTime = endTime;

  41. }

  42.  
  43. public Date getCreateTime() {

  44. return createTime;

  45. }

  46. public void setCreateTime(Date createTime) {

  47. this.createTime = createTime;

  48. }

  49. @Override

  50. public String toString() {

  51. return "Seckill{" +

  52. "seckillId=" + seckillId +

  53. ", name='" + name + '\'' +

  54. ", number=" + number +

  55. ", startTime=" + startTime +

  56. ", endTime=" + endTime +

  57. ", createTime=" + createTime +

  58. '}';

  59. }

  60. }

②  SuccessKilled.java

 
  1. package com.force4us.entity;

  2. import org.springframework.stereotype.Component;

  3. import java.util.Date;

  4. public class SuccessKilled {

  5. private long seckillId;

  6. private long userPhone;

  7. private short state;

  8. private Date createTime;

  9. private Seckill seckill;

  10. public long getSeckillId() {

  11. return seckillId;

  12. }

  13. public void setSeckillId(long seckillId) {

  14. this.seckillId = seckillId;

  15. }

  16. public long getUserPhone() {

  17. return userPhone;

  18. }

  19. public void setUserPhone(long userPhone) {

  20. this.userPhone = userPhone;

  21. }

  22. public short getState() {

  23. return state;

  24. }

  25. public void setState(short state) {

  26. this.state = state;

  27. }

  28. public Date getCreateTime() {

  29. return createTime;

  30. }

  31. public void setCreateTime(Date createTime) {

  32. this.createTime = createTime;

  33. }

  34. public Seckill getSeckill() {

  35. return seckill;

  36. }

  37. public void setSeckill(Seckill seckill) {

  38. this.seckill = seckill;

  39. }

  40. @Override

  41. public String toString() {

  42. return "SuccessKilled{" +

  43. "seckillId=" + seckillId +

  44. ", userPhone=" + userPhone +

  45. ", state=" + state +

  46. ", createTime=" + createTime +

  47. ", seckill=" + seckill +

  48. '}';

  49. }

  50. }

③  SeckillDao

 
  1. package com.force4us.dao;

  2. import com.force4us.entity.Seckill;

  3. import org.apache.ibatis.annotations.Param;

  4. import java.util.Date;

  5. import java.util.List;

  6. import java.util.Map;

  7.  
  8. public interface SeckillDao {

  9. /**

  10. * 减库存

  11. * @param seckillId

  12. * @param killTime

  13. * @return 如果影响行数>1,表示更新库存的记录行数

  14. */

  15. int reduceNumber(@Param("seckillId") long seckillId, @Param("killTime") Date killTime);

  16. /**

  17. * 根据id查询秒杀的商品信息

  18. * @param seckillId

  19. * @return

  20. */

  21. Seckill queryById(@Param("seckillId") long seckillId);

  22. /**

  23. * 根据偏移量查询秒杀商品列表

  24. * @param offset

  25. * @param limit

  26. * @return

  27. */

  28. List<Seckill> queryAll(@Param("offset") int offset, @Param("limit") int limit);

  29. void killByProcedure(Map<String,Object> paramMap);

  30. }

④  SuccessKilledDao

 
  1. package com.force4us.dao;

  2. import com.force4us.entity.SuccessKilled;

  3. import org.apache.ibatis.annotations.Param;

  4. public interface SuccessKilledDao {

  5. /**

  6. * 插入购买明细,可过滤重复

  7. * @param seckillId

  8. * @param userPhone

  9. * @return 插入的行数

  10. */

  11. int insertSuccessKilled(@Param("seckillId") long seckillId, @Param("userPhone") long userPhone);

  12. /**

  13. * 根据秒杀商品ID查询明细SuccessKilled对象, 携带了Seckill秒杀产品对象

  14. * @param seckillId

  15. * @param userPhone

  16. * @return

  17. */

  18. SuccessKilled queryByIdWithSeckill(@Param("seckillId") long , @Param("userPhone") long userPhone);

  19. }

⑤ mybatis配置文件:

 
  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.  
  6. <configuration>

  7. <!-- 配置全局属性 -->

  8. <settings>

  9. <!-- 使用jdbc的getGeneratekeys获取自增主键值 -->

  10. <setting name="useGeneratedKeys" value="true"/>

  11.  
  12. <!--使用列别名替换列名  默认值为true

  13. select name as title(实体中的属性名是title) form table;

  14. 开启后mybatis会自动帮我们把表中name的值赋到对应实体的title属性中

  15. -->

  16. <setting name="useColumnLabel" value="true"/>

  17.  
  18. <!--开启驼峰命名转换Table:create_time到 Entity(createTime)-->

  19. <setting name="mapUnderscoreToCamelCase" value="true"/>

  20. </settings>

  21.  
  22. </configuration>

⑥ 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="com.force4us.dao.SeckillDao">

  7. <update id="reduceNumber">

  8. UPDATE seckill

  9. SET number = number - 1

  10. WHERE seckill_id = #{seckillId}

  11. AND start_time <![CDATA[ <= ]]> #{killTime}

  12. AND end_time >= #{killTime}

  13. AND number > 0

  14. </update>

  15.  
  16. <select id="queryById" resultType="Seckill" parameterType="long">

  17. SELECT *

  18. FROM seckill

  19. WHERE seckill_id = #{seckillId}

  20. </select>

  21.  
  22. <select id="queryAll" resultType="Seckill">

  23. SELECT *

  24. FROM seckill

  25. ORDER BY create_time DESC

  26. limit #{offset},#{limit}

  27. </select>

  28.  
  29. <select id="killByProcedure" statementType="CALLABLE">

  30. CALL excuteSeckill(

  31. #{seckillId, jdbcType=BIGINT, mode=IN},

  32. #{phone, jdbcType=BIGINT, mode=IN},

  33. #{killTime, jdbcType=TIMESTAMP, mode=IN},

  34. #{result, jdbcType=INTEGER, mode=OUT}

  35. )

  36. </select>

  37. </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.  
  6. <mapper namespace="com.force4us.dao.SuccessKilledDao">

  7. <insert id="insertSuccessKilled">

  8. <!--当出现主键冲突时(即重复秒杀时),会报错;不想让程序报错,加入ignore-->

  9. INSERT ignore INTO success_killed(seckill_id,user_phone,state)

  10. VALUES (#{seckillId},#{userPhone},0)

  11. </insert>

  12.  
  13. <select id="queryByIdWithSeckill" resultType="SuccessKilled">

  14. <!--根据seckillId查询SuccessKilled对象,并携带Seckill对象-->

  15. <!--如何告诉mybatis把结果映射到SuccessKill属性同时映射到Seckill属性-->

  16. <!--可以自由控制SQL语句-->

  17. SELECT

  18. sk.seckill_id,

  19. sk.user_phone,

  20. sk.create_time,

  21. sk.state,

  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.  
  32. </select>

  33. </mapper>

⑧ Mybatis整合Service: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:contex="http://www.springframework.org/schema/context"

  5. 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">

  6.  
  7.  
  8. <!-- 配置整合mybatis过程-->

  9. <!-- 1、配置数据库相关参数-->

  10. <contex:property-placeholder location="classpath:jdbc.properties"/>

  11.  
  12. <!-- 2、配置数据库连接池-->

  13. <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">

  14. <!-- 配置链接属性-->

  15. <property name="driverClass" value="${jdbc.driver}"/>

  16. <property name="user" value="${jdbc.username}"/>

  17. <property name="password" value="${jdbc.password}"/>

  18. <property name="jdbcUrl" value="${jdbc.url}"/>

  19.  
  20. <!-- 配置c3p0私有属性-->

  21. <property name="maxPoolSize" value="30"/>

  22. <property name="minPoolSize" value="10"/>

  23. <!--关闭连接后不自动commit-->

  24. <property name="autoCommitOnClose" value="false"/>

  25.  
  26. <!--获取连接超时时间-->

  27. <property name="checkoutTimeout" value="1000"/>

  28. <!--当获取连接失败重试次数-->

  29. <property name="acquireRetryAttempts" value="2"/>

  30. </bean>

  31.  
  32. <!-- 3、配置sqlSessionFactory对象-->

  33. <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">

  34. <!--注入数据库连接池-->

  35. <property name="dataSource" ref="dataSource"/>

  36. <!-- 配置mybatis全局配置文件:mybatis-config.xml-->

  37. <property name="configLocation" value="classpath:mybatis-config.xml"/>

  38. <!-- 扫描entity包,使用别名,多个用;隔开-->

  39. <property name="typeAliasesPackage" value="com.force4us.entity"/>

  40. <!-- 扫描sql配置文件:mapper需要的xml文件-->

  41. <property name="mapperLocations" value="classpath:mapper/*.xml"/>

  42. </bean>

  43.  
  44.  
  45. <!-- 4.配置扫描Dao接口包,动态实现Dao接口,注入到spring容器-->

  46. <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">

  47. <!-- 注入sqlSessionFactory-->

  48. <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>

  49. <!--给出需要扫描的Dao接口-->

  50. <property name="basePackage" value="com.force4us.dao"/>

  51. </bean>

  52.  
  53.  
  54. <!--redisDao-->

  55. <bean id="redisDao" class="com.force4us.dao.cache.RedisDao">

  56. <constructor-arg index="0" value="localhost"/>

  57. <constructor-arg index="1" value="6379"/>

  58. </bean>

  59. </beans>

3 Service层

① SeckillService

 
  1. package com.force4us.service;

  2. import com.force4us.dto.Exposer;

  3. import com.force4us.dto.SeckillExecution;

  4. import com.force4us.entity.Seckill;

  5. import com.force4us.exception.RepeatKillException;

  6. import com.force4us.exception.SeckillCloseException;

  7. import com.force4us.exception.SeckillException;

  8. import java.util.List;

  9.  
  10. /**业务接口:站在使用者(程序员)的角度设计接口

  11. * 三个方面:1.方法定义粒度,方法定义的要非常清楚2.参数,要越简练越好

  12. * 3.返回类型(return 类型一定要友好/或者return异常,我们允许的异常)

  13. */

  14. public interface SeckillService {

  15.  
  16. /**

  17. * 查询全部秒杀记录

  18. * @return

  19. */

  20. List<Seckill> getSeckillList();

  21.  
  22. /**

  23. * 查询单个秒杀记录

  24. * @param seckillId

  25. * @return

  26. */

  27. Seckill getById(long seckillId);

  28.  
  29. /**

  30. * 在秒杀开启时输出秒杀接口的地址,否则输出系统时间和秒杀时间

  31. */

  32. Exposer exportSeckillUrl(long seckillId);

  33.  
  34. /**

  35. * 执行秒杀操作,有可能失败,有可能成功,所以要抛出我们允许的异常

  36. * @param seckillId

  37. * @param userPhone

  38. * @param md5

  39. * @return

  40. * @throws SeckillException

  41. * @throws RepeatKillException

  42. * @throws SeckillCloseException

  43. */

  44. SeckillExecution executeSeckill(long seckillId, long userPhone, String md5)

  45. throws SeckillException, RepeatKillException, SeckillCloseException;

  46.  
  47. SeckillExecution executeSeckillProcedure(long seckillId,long userPhone,String md5)

  48. throws SeckillException,RepeatKillException,SeckillCloseException;

  49. }

② SeckillServiceImpl

 
  1. package com.force4us.service.impl;

  2. import com.force4us.dao.SeckillDao;

  3. import com.force4us.dao.SuccessKilledDao;

  4. import com.force4us.dao.cache.RedisDao;

  5. import com.force4us.dto.Exposer;

  6. import com.force4us.dto.SeckillExecution;

  7. import com.force4us.entity.Seckill;

  8. import com.force4us.entity.SuccessKilled;

  9. import com.force4us.enums.SeckillStatEnum;

  10. import com.force4us.exception.RepeatKillException;

  11. import com.force4us.exception.SeckillCloseException;

  12. import com.force4us.exception.SeckillException;

  13. import com.force4us.service.SeckillService;

  14. import org.apache.commons.collections4.MapUtils;

  15. import org.slf4j.Logger;

  16. import org.slf4j.LoggerFactory;

  17. import org.springframework.beans.factory.annotation.Autowired;

  18. import org.springframework.stereotype.Service;

  19. import org.springframework.transaction.annotation.Transactional;

  20. import org.springframework.util.DigestUtils;

  21. import javax.annotation.Resource;

  22. import java.util.Date;

  23. import java.util.HashMap;

  24. import java.util.List;

  25. import java.util.Map;

  26.  
  27. @Service

  28. public class SeckillServiceImpl implements SeckillService {

  29. //日志对象

  30. private Logger logger = LoggerFactory.getLogger(this.getClass());

  31.  
  32. @Autowired

  33. private SeckillDao seckillDao;

  34.  
  35. @Autowired

  36. private SuccessKilledDao successKilledDao;

  37.  
  38. @Autowired

  39. private RedisDao redisDao;

  40.  
  41. //加入一个混淆字符串(秒杀接口)的salt,为了我避免用户猜出我们的md5值,值任意给,越复杂越好

  42. private final String salt = "sadjgioqwelrhaljflutoiu293480523*&%*&*#";

  43.  
  44. public List<Seckill> getSeckillList() {

  45. return seckillDao.queryAll(0, 4);

  46. }

  47.  
  48. public Seckill getById(long seckillId) {

  49. return seckillDao.queryById(seckillId);

  50. }

  51.  
  52. public Exposer exportSeckillUrl(long seckillId) {

  53. //缓存优化

  54. //1。访问redi

  55.  
  56.  
  57. Seckill seckill = redisDao.getSeckill(seckillId);

  58. if (seckill == null) {

  59. //2.访问数据库

  60. seckill = seckillDao.queryById(seckillId);

  61. if (seckill == null) {//说明查不到这个秒杀产品的记录

  62. return new Exposer(false, seckillId);

  63. } else {

  64. //3,放入redis

  65. redisDao.putSeckill(seckill);

  66. }

  67.  
  68. }

  69. Date startTime = seckill.getStartTime();

  70. Date endTime = seckill.getEndTime();

  71. Date nowTime = new Date();

  72. //若是秒杀未开启

  73. if (nowTime.getTime() < startTime.getTime() || nowTime.getTime() > endTime.getTime()) {

  74. return new Exposer(false, seckillId, nowTime.getTime(), startTime.getTime(), endTime.getTime());

  75. }

  76.  
  77. //秒杀开启,返回秒杀商品的id、用给接口加密的md5

  78. String md5 = getMD5(seckillId);

  79. return new Exposer(true, md5, seckillId);

  80. }

  81.  
  82. private String getMD5(long seckillId) {

  83. String base = seckillId + "/" + salt;

  84. String md5 = DigestUtils.md5DigestAsHex(base.getBytes());

  85. return md5;

  86. }

  87.  
  88. @Transactional

  89. /**

  90. * 使用注解控制事务方法的优点:

  91. * 1.开发团队达成一致约定,明确标注事务方法的编程风格

  92. * 2.保证事务方法的执行时间尽可能短,不要穿插其他网络操作RPC/HTTP请求或者剥离到事务方法外部

  93. * 3.不是所有的方法都需要事务,如只有一条修改操作、只读操作不要事务控制

  94. */

  95. public SeckillExecution executeSeckill(long seckillId, long userPhone, String md5) throws SeckillException, RepeatKillException, SeckillCloseException {

  96. if (md5 == null || !md5.equals(getMD5(seckillId))) {

  97. throw new SeckillException("seckill data rewrite");

  98. }

  99. //执行秒杀逻辑:减库存+记录购买行为

  100. Date nowTime = new Date();

  101. try {

  102. //否则更新了库存,秒杀成功,增加明细

  103. int insertCount = successKilledDao.insertSuccessKilled(seckillId, userPhone);

  104. //看是否该明细被重复插入,即用户是否重复秒杀

  105. if (insertCount <= 0) {

  106. throw new RepeatKillException("seckill repeated");

  107. } else {

  108.  
  109. //减库存,热点商品竞争,update方法会拿到行级锁

  110. int updateCount = seckillDao.reduceNumber(seckillId, nowTime);

  111. if (updateCount <= 0) {

  112. //没有更新库存记录,说明秒杀结束 rollback

  113. throw new SeckillCloseException("seckill is closed");

  114. } else {

  115. //秒杀成功,得到成功插入的明细记录,并返回成功秒杀的信息 commit

  116. SuccessKilled successKilled = successKilledDao.queryByIdWithSeckill(seckillId, userPhone);

  117. return new SeckillExecution(seckillId, SeckillStatEnum.SUCCESS, successKilled);

  118. }

  119.  
  120. }

  121. } catch (SeckillCloseException e1) {

  122. throw e1;

  123. } catch (RepeatKillException e2) {

  124. throw e2;

  125. } catch (Exception e) {

  126. logger.error(e.getMessage(), e);

  127. //所有编译器异常,转化成运行期异常

  128. throw new SeckillException("seckill inner error:" + e.getMessage());

  129. }

  130. }

  131.  
  132. public SeckillExecution executeSeckillProcedure(long seckillId, long userPhone, String md5) throws SeckillException, RepeatKillException, SeckillCloseException {

  133. if (md5 == null || !md5.equals(getMD5(seckillId))) {

  134. return new SeckillExecution(seckillId, SeckillStatEnum.DATE_REWRITE);

  135. }

  136. Date time = new Date();

  137. Map<String, Object> map = new HashMap<String, Object>();

  138. map.put("seckillId", seckillId);

  139. map.put("phone", userPhone);

  140. map.put("killTime", time);

  141. map.put("result", null);

  142. try {

  143. seckillDao.killByProcedure(map);

  144. int result = MapUtils.getInteger(map, "result", -2);

  145. if (result == 1) {

  146. SuccessKilled successKill = successKilledDao.queryByIdWithSeckill(seckillId, userPhone);

  147. return new SeckillExecution(seckillId, SeckillStatEnum.SUCCESS, successKill);

  148. } else {

  149. return new SeckillExecution(seckillId, SeckillStatEnum.stateOf(result));

  150. }

  151. } catch (Exception e) {

  152. logger.error(e.getMessage(), e);

  153. return new SeckillExecution(seckillId, SeckillStatEnum.INNER_ERROR);

  154. }

  155. }

  156. }

③ 异常的处理:

a.SeckillCloseException

 
  1. package com.force4us.exception;

  2.  
  3. public class SeckillCloseException extends SeckillException{

  4. public SeckillCloseException(String message) {

  5. super(message);

  6. }

  7. public SeckillCloseException(String message, Throwable cause) {

  8. super(message, cause);

  9. }

  10. }

b. SeckillException

 
  1. package com.force4us.exception;

  2.  
  3. public class RepeatKillException extends SeckillException{

  4. public RepeatKillException(String message) {

  5. super(message);

  6. }

  7. public RepeatKillException(String message, Throwable cause) {

  8. super(message, cause);

  9. }

  10. }

c. RepeatKillException

 
  1. package com.force4us.exception;

  2.  
  3. public class SeckillException extends RuntimeException{

  4. public SeckillException(String message) {

  5. super(message);

  6. }

  7. public SeckillException(String message, Throwable cause) {

  8. super(message, cause);

  9. }

  10. }

④ 枚举SeckillStatEnum

 
  1. package com.force4us.enums;

  2. public enum SeckillStatEnum {

  3. SUCCESS(1,"秒杀成功"),

  4. END(0,"秒杀结束"),

  5. REPEAT_KILL(-1,"重复秒杀"),

  6. INNER_ERROR(-2,"系统异常"),

  7. DATE_REWRITE(-3,"数据篡改");

  8. private int state;

  9. private String stateInfo;

  10. SeckillStatEnum(int state, String stateInfo){

  11. this.state = state;

  12. this.stateInfo = stateInfo;

  13. }

  14. public int getState() {

  15. return state;

  16. }

  17. public String getStateInfo() {

  18. return stateInfo;

  19. }

  20. public static SeckillStatEnum stateOf(int index){

  21. for(SeckillStatEnum state : values()){

  22. if(state.getState() == index){

  23. return state;

  24. }

  25. }

  26. return null;

  27. }

  28. }

⑤ spring_spring.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" xmlns:tx="http://www.springframework.org/schema/tx"

       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 http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">

<!-- 扫描service包下所有使用注解的类型--> <context:component-scan base-package="com.force4us.service"/> <!-- 配置事务管理器 --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <!-- 注入数据库连接池 --> <property name="dataSource" ref="dataSource" /> </bean> <!-- 配置基于注解的声明式事务 --> <tx:annotation-driven transaction-manager="transactionManager" /> </beans>

 

4.Web层,JSP页面和JS

(1) 详情页流程逻辑逻辑

(2) 配置web.xml[html] view plain copy

  1. <code class="language-html"><?xml version="1.0" encoding="UTF-8"?>  
  2. <!--  
  3.   Licensed to the Apache Software Foundation (ASF) under one or more  
  4.   contributor license agreements.  See the NOTICE file distributed with  
  5.   this work for additional information regarding copyright ownership.  
  6.   The ASF licenses this file to You under the Apache License, Version 2.0  
  7.   (the "License"); you may not use this file except in compliance with  
  8.   the License.  You may obtain a copy of the License at  
  9.   
  10.       http://www.apache.org/licenses/LICENSE-2.0  
  11.   
  12.   Unless required by applicable law or agreed to in writing, software  
  13.   distributed under the License is distributed on an "AS IS" BASIS,  
  14.   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  
  15.   See the License for the specific language governing permissions and  
  16.   limitations under the License.  
  17. -->  
  18.   
  19. <!--  
  20.   - This is the Cocoon web-app configurations file  
  21.   -  
  22.   - $Id$  
  23.   -->  
  24. <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"  
  25.          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
  26.          xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee  
  27.                       http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"  
  28.          version="3.1"  
  29.          metadata-complete="true">  
  30.     <!--用maven创建的web-app需要修改servlet的版本为3.1-->  
  31.     <!--配置DispatcherServlet-->  
  32.     <servlet>  
  33.         <servlet-name>seckill-dispatcher</servlet-name>  
  34.         <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>  
  35.         <!--  
  36.                     配置SpringMVC 需要配置的文件  
  37.                     spring-dao.xml,spring-service.xml,spring-web.xml  
  38.                     Mybites -> spring -> springMvc  
  39.                 -->  
  40.         <init-param>  
  41.             <param-name>contextConfigLocation</param-name>  
  42.             <param-value>classpath:spring/spring-*.xml</param-value>  
  43.         </init-param>  
  44.     </servlet>  
  45.   
  46.     <servlet-mapping>  
  47.         <servlet-name>seckill-dispatcher</servlet-name>  
  48.         <url-pattern>/</url-pattern>  
  49.     </servlet-mapping>  
  50.   
  51. </web-app></code>  

(3) SeckillResult

 
  1. package com.force4us.dto;

  2. //将所有的ajax请求返回类型,全部封装成json数据

  3. public class SeckillResult<T> {

  4. private boolean success;

  5. private T data;

  6. private String error;

  7. public SeckillResult(boolean success, T data) {

  8. this.success = success;

  9. this.data = data;

  10. }

  11. public SeckillResult(boolean success, String error) {

  12. this.success = success;

  13. this.error = error;

  14. }

  15. public boolean isSuccess() {

  16. return success;

  17. }

  18. public void setSuccess(boolean success) {

  19. this.success = success;

  20. }

  21. public T getData() {

  22. return data;

  23. }

  24. public void setData(T data) {

  25. this.data = data;

  26. }

  27. public String getError() {

  28. return error;

  29. }

  30. public void setError(String error) {

  31. this.error = error;

  32. }

  33. }

(4) spring-web.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" xmlns:mvc="http://www.springframework.org/schema/mvc"

  4. xmlns:context="http://www.springframework.org/schema/context"

  5. xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

  6.  
  7. <!--配置spring mvc-->

  8. <!--1,开启springmvc注解模式

  9. a.自动注册DefaultAnnotationHandlerMapping,AnnotationMethodHandlerAdapter

  10. b.默认提供一系列的功能:数据绑定,数字和日期的format@NumberFormat,@DateTimeFormat

  11. c:xml,json的默认读写支持-->

  12. <mvc:annotation-driven/>

  13.  
  14. <!--2.静态资源默认servlet配置-->

  15. <!--

  16. 1).加入对静态资源处理:js,gif,png

  17. 2).允许使用 "/" 做整体映射

  18. -->

  19. <mvc:default-servlet-handler/>

  20.  
  21. <!--3:配置JSP 显示ViewResolver-->

  22. <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">

  23. <property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/>

  24. <property name="prefix" value="/WEB-INF/jsp/"/>

  25. <property name="suffix" value=".jsp"/>

  26. </bean>

  27.  
  28. <!--4:扫描web相关的controller-->

  29. <context:component-scan base-package="com.force4us.web"/>

  30. </beans>

(5) SeckillController中:

 
  1. package com.force4us.web;

  2. import com.force4us.dto.Exposer;

  3. import com.force4us.dto.SeckillExecution;

  4. import com.force4us.dto.SeckillResult;

  5. import com.force4us.entity.Seckill;

  6. import com.force4us.enums.SeckillStatEnum;

  7. import com.force4us.exception.RepeatKillException;

  8. import com.force4us.exception.SeckillCloseException;

  9. import com.force4us.exception.SeckillException;

  10. import com.force4us.service.SeckillService;

  11. import org.springframework.beans.factory.annotation.Autowired;

  12. import org.springframework.stereotype.Controller;

  13. import org.springframework.test.annotation.Repeat;

  14. import org.springframework.ui.Model;

  15. import org.springframework.web.bind.annotation.*;

  16. import java.util.Date;

  17. import java.util.List;

  18.  
  19. @Controller

  20. @RequestMapping("/seckill")

  21. public class SeckillController {

  22.  
  23. @Autowired

  24. private SeckillService seckillService;

  25.  
  26.  
  27.  
  28. @RequestMapping(value = "/list",method= RequestMethod.GET)

  29. public String list(Model model) {

  30. List<Seckill> list = seckillService.getSeckillList();

  31. model.addAttribute("list",list);

  32. return "list";

  33. }

  34.  
  35. @RequestMapping(value = "/{seckillId}/detail",method = RequestMethod.GET)

  36. public String detail(@PathVariable("seckillId") Long seckillId, Model model){

  37. if(seckillId == null){

  38. return "redirect:/seckill/list";

  39. }

  40. Seckill seckill = seckillService.getById(seckillId);

  41. if(seckill == null){

  42. return "forward:/seckill/list";

  43. }

  44. model.addAttribute("seckill", seckill);

  45. return "detail";

  46. }

  47.  
  48. //ajax ,json暴露秒杀接口的方法

  49. @RequestMapping(value="/{seckillId}/exposer",method = RequestMethod.POST,produces = {"application/json;charset=UTF-8"})

  50. @ResponseBody

  51. public SeckillResult<Exposer> exposer(@PathVariable("seckillId") Long seckillId){

  52. SeckillResult<Exposer> result;

  53.  
  54. try {

  55. Exposer exposer = seckillService.exportSeckillUrl(seckillId);

  56. result = new SeckillResult<Exposer>(true,exposer);

  57. } catch (Exception e) {

  58. e.printStackTrace();

  59. result = new SeckillResult<Exposer>(false,e.getMessage());

  60. }

  61.  
  62. return result;

  63. }

  64.  
  65.  
  66. @RequestMapping(value="/{seckillId}/{md5}/execution", method = RequestMethod.POST,

  67. produces = {"application/json;charset=UTF-8"})

  68. @ResponseBody

  69. public SeckillResult<SeckillExecution> execute(@PathVariable("seckillId") Long seckillId,

  70. @PathVariable("md5") String md5,

  71. @CookieValue(value="killPhone", required = false) Long phone){

  72. if(phone == null){

  73. return new SeckillResult<SeckillExecution>(false,"未注册");

  74. }

  75.  
  76. SeckillResult<SeckillExecution> result;

  77.  
  78. try {

  79. SeckillExecution execution = seckillService.executeSeckillProcedure(seckillId,phone, md5);

  80. return new SeckillResult<SeckillExecution>(true,execution);

  81. } catch (RepeatKillException e1) {

  82. SeckillExecution execution = new SeckillExecution(seckillId, SeckillStatEnum.REPEAT_KILL);

  83. return new SeckillResult<SeckillExecution>(true,execution);

  84. } catch(SeckillCloseException e2){

  85. SeckillExecution execution = new SeckillExecution(seckillId, SeckillStatEnum.END);

  86. return new SeckillResult<SeckillExecution>(true,execution);

  87. }catch(Exception e){

  88. SeckillExecution execution = new SeckillExecution(seckillId, SeckillStatEnum.INNER_ERROR);

  89. return new SeckillResult<SeckillExecution>(true,execution);

  90. }

  91. }

  92.  
  93. @RequestMapping(value = "/time/now", method = RequestMethod.GET)

  94. @ResponseBody

  95. public SeckillResult<Long> time(){

  96. Date now = new Date();

  97. return new SeckillResult<Long>(true,now.getTime());

  98. }

  99.  
  100.  
  101. @RequestMapping("/test")

  102. public String test(){

  103. return "helloworld";

  104. }

  105. }

(6) list.jsp

 
  1. <%@ page contentType="text/html;charset=UTF-8" language="java" %>

  2. <%@include file="common/tag.jsp"%>

  3. <!DOCTYPE html>

  4. <html lang="zh-CN">

  5. <head>

  6. <meta charset="utf-8">

  7. <meta http-equiv="X-UA-Compatible" content="IE=edge">

  8. <meta name="viewport" content="width=device-width, initial-scale=1">

  9. <!-- 上述3个meta标签*必须*放在最前面,任何其他内容都*必须*跟随其后! -->

  10. <title>秒杀列表页</title>

  11. <%@include file="/WEB-INF/jsp/common/head.jsp"%>

  12.  
  13. </head>

  14. <body>

  15.  
  16. <div class="container">

  17. <div class="panel panel-default">

  18. <div class="panel-heading text-center">

  19. <h2>秒杀列表</h2>

  20. </div>

  21. <div class="panel-body">

  22. <table class="table table-hover">

  23. <thead>

  24. <tr>

  25. <th>名称</th>

  26. <th>库存</th>

  27. <th>开始时间</th>

  28. <th>结束时间</th>

  29. <th>创建时间</th>

  30. <th>详情页</th>

  31. </tr>

  32. </thead>

  33. <tbody>

  34. <c:forEach items="${list}" var="sk">

  35. <tr>

  36. <td>${sk.name}</td>

  37. <td>${sk.number}</td>

  38. <td>

  39. <fmt:formatDate value="${sk.startTime}" pattern="yyyy-MM-dd HH:mm:ss" />

  40. </td>

  41. <td>

  42. <fmt:formatDate value="${sk.endTime}" pattern="yyyy-MM-dd HH:mm:ss" />

  43. </td>

  44. <td>

  45. <fmt:formatDate value="${sk.createTime}" pattern="yyyy-MM-dd HH:mm:ss" />

  46. </td>

  47. <td><a class="btn btn-info" href="/seckill/${sk.seckillId}/detail" target="_blank">详情</a></td>

  48. </tr>

  49. </c:forEach>

  50. </tbody>

  51. </table>

  52.  
  53. </div>

  54. </div>

  55. </div>

  56.  
  57. </body>

  58. <!-- jQuery (necessary for Bootstrap's JavaScript plugins) -->

  59. <script src="https://cdn.bootcss.com/jquery/1.12.4/jquery.min.js"></script>

  60.  
  61. <!-- 最新的 Bootstrap 核心 JavaScript 文件 -->

  62. <script src="https://cdn.bootcss.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>

  63.  
  64. </html>

(7) details.jsp

 
  1. <%@ page contentType="text/html;charset=UTF-8" language="java" %>

  2. <%@include file="common/tag.jsp"%>

  3. <!DOCTYPE html>

  4. <html lang="zh-CN">

  5. <head>

  6. <meta charset="utf-8">

  7. <meta http-equiv="X-UA-Compatible" content="IE=edge">

  8. <meta name="viewport" content="width=device-width, initial-scale=1">

  9. <!-- 上述3个meta标签*必须*放在最前面,任何其他内容都*必须*跟随其后! -->

  10. <title>秒杀详情页</title>

  11. <%@include file="common/head.jsp"%>

  12.  
  13. </head>

  14. <body>

  15.  
  16. <div class="container">

  17. <div class="panel panel-default text-center">

  18. <div class="pannel-heading">

  19. <h1>${seckill.name}</h1>

  20. </div>

  21.  
  22. <div class="panel-body">

  23. <h2 class="text-danger">

  24. <%--显示time图标--%>

  25. <span class="glyphicon glyphicon-time"></span>

  26. <%--展示倒计时--%>

  27. <span class="glyphicon" id="seckill-box"></span>

  28. </h2>

  29. </div>

  30. </div>

  31. </div>

  32.  
  33. <%--登录弹出层 输入电话--%>

  34. <div id="killPhoneModal" class="modal fade">

  35.  
  36. <div class="modal-dialog">

  37.  
  38. <div class="modal-content">

  39. <div class="modal-header">

  40. <h3 class="modal-title text-center">

  41. <span class="glyphicon glyphicon-phone"> </span>秒杀电话:

  42. </h3>

  43. </div>

  44.  
  45. <div class="modal-body">

  46. <div class="row">

  47. <div class="col-xs-8 col-xs-offset-2">

  48. <input type="text" name="killPhone" id="killPhoneKey"

  49. placeholder="填写手机号^o^" class="form-control">

  50. </div>

  51. </div>

  52. </div>

  53.  
  54. <div class="modal-footer">

  55. <%--验证信息--%>

  56. <span id="killPhoneMessage" class="glyphicon"> </span>

  57. <button type="button" id="killPhoneBtn" class="btn btn-success">

  58. <span class="glyphicon glyphicon-phone"></span>

  59. Submit

  60. </button>

  61. </div>

  62.  
  63. </div>

  64. </div>

  65.  
  66. </div>

  67.  
  68. </body>

  69. <!-- jQuery (necessary for Bootstrap's JavaScript plugins) -->

  70. <script src="https://cdn.bootcss.com/jquery/1.12.4/jquery.min.js"></script>

  71.  
  72. <!-- 最新的 Bootstrap 核心 JavaScript 文件 -->

  73. <script src="https://cdn.bootcss.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>

  74. <%--jQuery Cookie操作插件--%>

  75. <script src="https://cdn.bootcss.com/jquery-cookie/1.4.1/jquery.cookie.min.js"></script>

  76. <%--jQuery countDown倒计时插件--%>

  77. <script src="https://cdn.bootcss.com/jquery.countdown/2.2.0/jquery.countdown.min.js"></script>

  78.  
  79. <script src="/resource/script/seckill.js" typ="text/javascript"></script>

  80.  
  81. <script type="text/javascript">

  82. $(function(){

  83. seckill.detail.init({

  84. seckillId:${seckill.seckillId},

  85. startTime:${seckill.startTime.time},

  86. endTime:${seckill.endTime.time}

  87. });

  88. })

  89.  
  90. </script>

  91. </html>

(8) seckill.js

 
  1. //存放主要交互逻辑的js代码

  2. // javascript 模块化(package.类.方法)

  3.  
  4. var seckill = {

  5. //封装秒杀相关ajax的url

  6. URL: {

  7. now: function () {

  8. return '/seckill/time/now';

  9. },

  10. exposer: function (seckillId) {

  11. return '/seckill/' + seckillId + '/exposer';

  12. },

  13. execution: function (seckillId, md5) {

  14. return '/seckill/' + seckillId + '/' + md5 + '/execution';

  15. }

  16. },

  17.  
  18. //验证手机号

  19. validatePhone: function(phone){

  20. if(phone && phone.length == 11 && !isNaN(phone)){

  21. return true;

  22. }else{

  23. return false;

  24. }

  25.  
  26. },

  27.  
  28. //详情页秒杀逻辑

  29. detail:{

  30. //详情页初始化

  31. init:function (params) {

  32. //手机验证和登录,计时交互

  33. //规划我们的交互流程

  34. //在cookie中查找手机号

  35. var killPhone = $.cookie('killPhone');

  36. //验证手机号

  37. if(!seckill.validatePhone(killPhone)){

  38. //绑定手机,控制输出

  39. var killPhoneModal = $('#killPhoneModal');

  40. killPhoneModal.modal({

  41. show:true,//显示弹出层

  42. backdrop:'static',//禁止位置关闭

  43. keyboard:false//关闭键盘事件

  44. });

  45.  
  46. $('#killPhoneBtn').click(function () {

  47. var inputPhone = $('#killPhoneKey').val();

  48. console.log("inputPhone" + inputPhone);

  49. if(seckill.validatePhone(inputPhone)){

  50. //电话写入cookie,7天过期

  51. $.cookie('killPhone',inputPhone,{expires:7, path:'/seckill'});

  52. //验证通过,刷新页面

  53. window.location.reload();

  54. }else{

  55. $('#killPhoneMessage').hide().html('<label class="label label-danger">手机号错误</label>').show(300);

  56. }

  57. });

  58. }

  59. //已经登录

  60. //计时交互

  61.  
  62. var startTime = params['startTime'];

  63. var endTime = params['endTime'];

  64. var seckillId = params['seckillId'];

  65. $.get(seckill.URL.now(), {}, function (result) {

  66. if (result && result['success']) {

  67. var nowTime = result['data'];

  68. //时间判断 计时交互

  69. seckill.countDown(seckillId, nowTime, startTime, endTime);

  70. } else {

  71. console.log('result: ' + result);

  72. alert('result: ' + result);

  73. }

  74. });

  75. }

  76. },

  77.  
  78. handlerSeckill: function (seckillId, node) {

  79. //获取秒杀地址,控制显示器,执行秒杀

  80. node.hide().html('<button class="btn btn-primary btn-lg" id="killBtn">开始秒杀</button>');

  81.  
  82. $.post(seckill.URL.exposer(seckillId), {}, function (result) {

  83. //在回调函数种执行交互流程

  84. if (result && result['success']) {

  85. var exposer = result['data'];

  86. if (exposer['exposed']) {

  87. //开启秒杀

  88. //获取秒杀地址

  89. var md5 = exposer['md5'];

  90. var killUrl = seckill.URL.execution(seckillId, md5);

  91. console.log("killUrl: " + killUrl);

  92. //绑定一次点击事件

  93. $('#killBtn').one('click', function () {

  94. //执行秒杀请求

  95. //1.先禁用按钮

  96. $(this).addClass('disabled');//,<-$(this)===('#killBtn')->

  97. //2.发送秒杀请求执行秒杀

  98. $.post(killUrl, {}, function (result) {

  99. if (result && result['success']) {

  100. var killResult = result['data'];

  101. var state = killResult['state'];

  102. var stateInfo = killResult['stateInfo'];

  103. //显示秒杀结果

  104. node.html('<span class="label label-success">' + stateInfo + '</span>');

  105. }

  106. });

  107. });

  108. node.show();

  109. } else {

  110. //未开启秒杀(浏览器计时偏差)

  111. var now = exposer['now'];

  112. var start = exposer['start'];

  113. var end = exposer['end'];

  114. seckill.countDown(seckillId, now, start, end);

  115. }

  116. } else {

  117. console.log('result: ' + result);

  118. }

  119. });

  120.  
  121. },

  122.  
  123.  
  124. countDown: function (seckillId, nowTime, startTime, endTime) {

  125. console.log(seckillId + '_' + nowTime + '_' + startTime + '_' + endTime);

  126. var seckillBox = $('#seckill-box');

  127. if (nowTime > endTime) {

  128. //秒杀结束

  129. seckillBox.html('秒杀结束!');

  130. } else if (nowTime < startTime) {

  131. //秒杀未开始,计时事件绑定

  132. var killTime = new Date(startTime + 1000);//todo 防止时间偏移

  133. seckillBox.countdown(killTime, function (event) {

  134. //时间格式

  135. var format = event.strftime('秒杀倒计时: %D天 %H时 %M分 %S秒 ');

  136. seckillBox.html(format);

  137. }).on('finish.countdown', function () {

  138. //时间完成后回调事件

  139. //获取秒杀地址,控制现实逻辑,执行秒杀

  140. console.log('______fininsh.countdown');

  141. seckill.handlerSeckill(seckillId, seckillBox);

  142. });

  143. } else {

  144. //秒杀开始

  145. seckill.handlerSeckill(seckillId, seckillBox);

  146. }

  147. }

  148. }

5.优化:

     由于减少库存和购买明细需要在同一事物当中,在次中间会出现网络延迟,GC,缓存,数据库的并发等,所以需要进行优化。

(1) 使用Redis优化:具体代码看上面。

(2) 调整业务逻辑:先进行insert,插入购买明细,然后进行减少库存数量,具体代码看上面。

(3) 调用存储过程seckill.sql

 
  1. -- 秒杀执行存储过程

  2. DELIMITER $$ -- console ;转换为$$

  3. --定义存储参数

  4. --参数:in 输入参数;out输出参数

  5. -- rowCount():返回上一条修改类型sql(delete,insert,update)的影响行数

  6. -- rowCount: 0:未修改数据 >0:表示修改的行数 <0:sql错误/未执行修改sql

  7. CREATE PROCEDURE excuteSeckill(IN fadeSeckillId INT,IN fadeUserPhone VARCHAR (15),IN fadeKillTime TIMESTAMP ,OUT fadeResult INT)

  8. BEGIN

  9. DECLARE insert_count INT DEFAULT 0;

  10. START TRANSACTION ;

  11. INSERT ignore success_kill(seckill_id,user_phone,status,create_time) VALUES(fadeSeckillId,fadeUserPhone,0,fadeKillTime); --先插入购买明细

  12. SELECT ROW_COUNT() INTO insert_count;

  13. IF(insert_count = 0) THEN

  14. ROLLBACK ;

  15. SET fadeResult = -1; --重复秒杀

  16. ELSEIF(insert_count < 0) THEN

  17. ROLLBACK ;

  18. SET fadeResult = -2; --内部错误

  19. ELSE --已经插入购买明细,接下来要减少库存

  20. UPDATE seckill SET number = number -1 WHERE seckill_id = fadeSeckillId AND start_time < fadeKillTime AND end_time > fadeKillTime AND number > 0;

  21. SELECT ROW_COUNT() INTO insert_count;

  22. IF (insert_count = 0) THEN

  23. ROLLBACK ;

  24. SET fadeResult = 0; --库存没有了,代表秒杀已经关闭

  25. ELSEIF (insert_count < 0) THEN

  26. ROLLBACK ;

  27. SET fadeResult = -2; --内部错误

  28. ELSE

  29. COMMIT ; --秒杀成功,事务提交

  30. SET fadeResult = 1; --秒杀成功返回值为1

  31. END IF;

  32. END IF;

  33. END

  34. $$

  35.  
  36. DELIMITER ;

  37.  
  38. SET @fadeResult = -3;

  39. -- 执行存储过程

  40. CALL excuteSeckill(1003,18810464493,NOW(),@fadeResult);

  41. -- 获取结果

  42. SELECT @fadeResult;

  43.  
  44. --存储过程

  45. -- 1、存储过程优化:事务行级锁持有的时间

  46. -- 2、不要过度依赖存储过程

6.系统部署:

 

PS:若想通过源码更好的理解Java实现高并发秒杀,请:https://github.com/luomingkui/seckill

 

 

  • 0
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值