GithubHelp home page GithubHelp logo

codingxiaxw / seckill Goto Github PK

View Code? Open in Web Editor NEW
2.2K 118.0 1.2K 21.04 MB

the source code of `seckill` with development documentation :zap:

Home Page: http://codingxiaxw.cn

Java 85.01% JavaScript 14.99%

seckill's Introduction

Java高并发秒杀系统API

How to play

  1. git clone https://github.com/codingXiaxw/seckill.git
  2. open IDEA --> File --> New --> Open
  3. choose seckill's pom.xml,open it
  4. update the jdbc.properties files about your mysql's username and password
  5. deploy the tomcat,and start up
  6. enter in the browser: http://localhost:8080/seckill/list
  7. enjoy it

Develop environment

IDEA+Maven+SSM框架。

Written in front of words

之前写了一个用SSM框架搭建的商品查询系统,分两篇文章分别记录了自己整合SSM框架的过程以及利用SSM开发的一些基础知识,由于那时候刚学完SSM框架,所以自己觉得整合的过程总结的不够好。如今在有了一定的SSM框架开发经验后针对慕课网上对秒杀系统的讲解视频再写一个用Maven+SSM做的一个秒杀系统的文字讲解,从头到尾记录自己整合SSM框架以及用SS框架M开发这个秒杀系统的过程。maven的强大之处就是你不用再像以前那样,如果在项目中用到spring框架还要到spring官网上去下载一系列的jar包,用了maven对项目进行管理之后你就可以直接在它的pom.xml文件中添加jar包的相应坐标,这样maven就能自动从它的**仓库中为我们将这些jar包下载到其本地仓库中供我们使用。  

用maven对项目进行管理的知识很简单,关于创建maven项目的知识大家看我的这篇文章便可以在几分钟内掌握:Maven安装配置及创建你的第一个Maven项目

秒杀系统搭建环境:IDEA+Maven+SSM框架。详情讲解请点击这里或是下面模块的链接前往我的博客观看。

完成这个秒杀系统,需要完成四个模块的代码编写,分别是:

其实完成这三个模块就可以完成我们的秒杀系统了,但对于我们的秒杀系统中一件秒杀商品,在秒杀的时候肯定会有成千上万的用户参与进来,通过上述三个模块完成的系统无法解决这么多用户的高并发操作,所以我们还需要第四个模块:

  • 4.Java高并发秒杀APi之高并发优化(待更新)。

该系统我将按照上述4个模块通过4篇文章来完成介绍,本篇文章进行第一个模块的讲解及项目的介绍以及Dao层编码的开发。首先看看我们项目的效果图:

列表页:

详情页:

接下来我将从如何用maven创建我们的秒杀系统seckill项目开始到完成我们的秒杀系统,详细介绍自己完成它的过程。

1.相关技术介绍

**MySQL:**1.这里我们采用手写代码创建相关表,掌握这种能力对我们以后的项目二次上线会有很大的帮助;2.SQL技巧;3.事务和行级锁的理解和一些应用。

**MyBatis:**1.DAO层的设计与开发。2.MyBatis的合理使用,使用Mapper动态代理的方式进行数据库的访问。3.MyBatis和Spring框架的整合:如何高效的去整合MyBatis和Spring框架。

**Spring:**1.Spring IOC帮我们整合Service以及Service所有的依赖。2.声明式事务。对Spring声明式事务做一些分析以及它的行为分析。

**Spring MVC:**1.Restful接口设计和使用。Restful现在更多的被应用在一些互联网公司Web层接口的应用上。2.框架运作流程。3.Spring Controller的使用技巧。

**前端:**1.交互设计。2.bootstrap。3.JQuery。设计到前端的页面代码我们直接拷贝即可,毕竟真正开发中这样一个项目是由产品经理、前端工程师、后端工程师一起完成的。

**高并发:**1.高并发点和高并发分析。2.优化思路并实现。

下面开始我们的项目的开发。

2.Java高并发秒杀APi之业务分析与DAO层代码编写

2.1用Maven创建我们的项目seckill

在命令行中输入如下命令:

mvn archetype:generate -DgroupId=cn.codingxiaxw.seckill -DartifactId=seckill -Dpackage=cn.codingxiaxw.seckill -Dversion=1.0-SNAPSHOT -DarchetypeArtifactId=maven-archetype-webapp

然后使用IDEA打开该项目,在IDEA中对项目按照Maven项目的标准骨架补全我们项目的相应文件包,最后的工程结构如下:

main包下进行我们项目的代码编写及相关配置文件,test包下进行我们项目的测试。

打开WEB-INF下的web.xml,它默认为我们创建servlet版本为2.3,需要修改它的根标签为:

<web-app xmlns="http://java.sun.com/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
                      http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
         version="3.0"
         metadata-complete="true">
<!--用maven创建的web-app需要修改servlet的版本为3.0-->


</web-app>

然后打开pom.xml,在里面添加我们需要的第三方jar包的坐标配置信息,如SSM框架、数据库、日志,如下:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>cn.codingxiaxw.seckill</groupId>
  <artifactId>seckill</artifactId>
  <packaging>war</packaging>
  <version>1.0-SNAPSHOT</version>
  <name>seckill Maven Webapp</name>
  <url>http://maven.apache.org</url>
  <dependencies>
    <dependency>
      <!--3.0的junit是使用编程的方式来进行测试,而junit4是使用注解的方式来运行junit-->
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.11</version>
      <scope>test</scope>
    </dependency>


    <!--补全项目依赖-->
    <!--1.日志 java日志有:slf4j,log4j,logback,common-logging
        slf4j:是规范/接口
        日志实现:log4j,logback,common-logging
        使用:slf4j+logback
    -->
    <dependency>
      <groupId>org.slf4j</groupId>
      <artifactId>slf4j-api</artifactId>
      <version>1.7.12</version>
    </dependency>
    <dependency>
      <groupId>ch.qos.logback</groupId>
      <artifactId>logback-core</artifactId>
      <version>1.1.1</version>
    </dependency>
    <!--实现slf4j接口并整合-->
    <dependency>
      <groupId>ch.qos.logback</groupId>
      <artifactId>logback-classic</artifactId>
      <version>1.1.1</version>
    </dependency>


    <!--1.数据库相关依赖-->
    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>5.1.35</version>
      <scope>runtime</scope>
    </dependency>
    <dependency>
      <groupId>c3p0</groupId>
      <artifactId>c3p0</artifactId>
      <version>0.9.1.1</version>
    </dependency>

    <!--2.dao框架:MyBatis依赖-->
    <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis</artifactId>
      <version>3.3.0</version>
    </dependency>
    <!--mybatis自身实现的spring整合依赖-->
    <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis-spring</artifactId>
      <version>1.2.3</version>
    </dependency>

    <!--3.Servlet web相关依赖-->
    <dependency>
      <groupId>taglibs</groupId>
      <artifactId>standard</artifactId>
      <version>1.1.2</version>
    </dependency>
    <dependency>
      <groupId>jstl</groupId>
      <artifactId>jstl</artifactId>
      <version>1.2</version>
    </dependency>
    <dependency>
      <groupId>com.fasterxml.jackson.core</groupId>
      <artifactId>jackson-databind</artifactId>
      <version>2.5.4</version>
    </dependency>
    <dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>javax.servlet-api</artifactId>
      <version>3.1.0</version>
    </dependency>

    <!--4:spring依赖-->
    <!--1)spring核心依赖-->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-core</artifactId>
      <version>4.1.7.RELEASE</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-beans</artifactId>
      <version>4.1.7.RELEASE</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>4.1.7.RELEASE</version>
    </dependency>
    <!--2)spring dao层依赖-->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-jdbc</artifactId>
      <version>4.1.7.RELEASE</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-tx</artifactId>
      <version>4.1.7.RELEASE</version>
    </dependency>
    <!--3)springweb相关依赖-->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-web</artifactId>
      <version>4.1.7.RELEASE</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-webmvc</artifactId>
      <version>4.1.7.RELEASE</version>
    </dependency>
    <!--4)spring test相关依赖-->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-test</artifactId>
      <version>4.1.7.RELEASE</version>
    </dependency>

  </dependencies>

  <build>
    <finalName>seckill</finalName>
  </build>
</project>

到此,我们项目的初始化工作完成。

2.2秒杀系统业务分析

秒杀系统业务流程如下:

由图可以发现,整个系统其实是针对库存做的系统。用户成功秒杀商品,对于我们系统的操作就是:1.减库存。2.记录用户的购买明细。下面看看我们用户对库存的业务分析:

记录用户的秒杀成功信息,我们需要记录:1.谁购买成功了。2.购买成功的时间/有效期。3.付款/发货信息。这些数据组成了用户的秒杀成功信息,也就是用户的购买行为。

为什么我们的系统需要事务?看如下这些故障:1.若是用户成功秒杀商品我们记录了其购买明细却没有减库存。导致商品的超卖。2.减了库存却没有记录用户的购买明细。导致商品的少卖。对于上述两个故障,若是没有事务的支持,损失最大的无疑是我们的用户和商家。在MySQL中,它内置的事务机制,可以准确的帮我们完成减库存和记录用户购买明细的过程。

MySQL实现秒杀的难点分析:当用户A秒杀id为10的商品时,此时MySQL需要进行的操作是:1.开启事务。2.更新商品的库存信息。3.添加用户的购买明细,包括用户秒杀的商品id以及唯一标识用户身份的信息如电话号码等。4.提交事务。若此时有另一个用户B也在秒杀这件id为10的商品,他就需要等待,等待到用户A成功秒杀到这件商品然后MySQL成功的提交了事务他才能拿到这个id为10的商品的锁从而进行秒杀,而同一时间是不可能只有用户B在等待,肯定是有很多很多的用户都在等待拿到这个行级锁。秒杀的难点就在这里,如何高效的处理这些竞争?如何高效的完成事务?在后面第4个模块如何进行高并发的优化为大家讲解。

我们这个系统需要完成秒杀的哪些功能?先来看看天猫的一个秒杀库存系统:

大家看了是不是觉得很复杂?当然不用担心,我们只是实现秒杀的一些功能:1.秒杀接口的暴露。2.执行秒杀的操作。3.相关查询,比如说列表查询,详情页查询。我们实现这三个功能即可。接下来进行具体的编码工作,首先是Dao层的编码。

2.3Dao层设计开发

首先创建数据库,相关表的sql语句我在main包下的sql包中已经给出。

然后创建对应表的实体类,在java包下创建cn.codingxiaxw.entity包,创建一个Seckill.java实体类,代码如下:

public class Seckill
{
    private long seckillId;
    private String name;
    private int number;
    private Date startTime;
    private Date endTime;
    private Date createTime;

    public long getSeckillId() {
        return seckillId;
    }

    public void setSeckillId(long seckillId) {
        this.seckillId = seckillId;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getNumber() {
        return number;
    }

    public void setNumber(int number) {
        this.number = number;
    }

    public Date getStartTime() {
        return startTime;
    }

    public void setStartTime(Date startTime) {
        this.startTime = startTime;
    }

    public Date getEndTime() {
        return endTime;
    }

    public void setEndTime(Date endTime) {
        this.endTime = endTime;
    }

    public Date getCreateTime() {
        return createTime;
    }

    public void setCreateTime(Date createTime) {
        this.createTime = createTime;
    }

    @Override
    public String toString() {
        return "Seckill{" +
                "seckillId=" + seckillId +
                ", name='" + name + '\'' +
                ", number=" + number +
                ", startTime=" + startTime +
                ", endTime=" + endTime +
                ", createTime=" + createTime +
                '}';
    }
}

和一个SuccessKilled.java,代码如下:

public class SuccessKilled
{
    private long seckillId;
    private long userPhone;
    private short state;
    private Date createTime;

    //多对一,因为一件商品在库存中有很多数量,对应的购买明细也有很多。
    private Seckill seckill;

    public long getSeckillId() {
        return seckillId;
    }

    public void setSeckillId(long seckillId) {
        this.seckillId = seckillId;
    }

    public long getUserPhone() {
        return userPhone;
    }

    public void setUserPhone(long userPhone) {
        this.userPhone = userPhone;
    }

    public short getState() {
        return state;
    }

    public void setState(short state) {
        this.state = state;
    }

    public Date getCreateTime() {
        return createTime;
    }

    public void setCreateTime(Date createTime) {
        this.createTime = createTime;
    }

    public Seckill getSeckill() {
        return seckill;
    }

    public void setSeckill(Seckill seckill) {
        this.seckill = seckill;
    }

    @Override
    public String toString() {
        return "SuccessKilled{" +
                "seckillId=" + seckillId +
                ", userPhone=" + userPhone +
                ", state=" + state +
                ", createTime=" + createTime +
                '}';
    }
}

然后针对实体创建出对应dao层的接口,在cn.codingxiaxw.dao包下创建Seckill.java:

public interface SeckillDao
{

    /**
     * 减库存
     * @param seckillId
     * @param killTime
     * @return 如果影响行数>1,表示更新库存的记录行数
     */
    int reduceNumber(long seckillId, Date killTime);

    /**
     * 根据id查询秒杀的商品信息
     * @param seckillId
     * @return
     */
    Seckill queryById(long seckillId);

    /**
     * 根据偏移量查询秒杀商品列表
     * @param off
     * @param limit
     * @return
     */
    List<Seckill> queryAll(int off,int limit);

}

和SuccessKilled.java:

public interface SuccessKilledDao {

    /**
     * 插入购买明细,可过滤重复
     * @param seckillId
     * @param userPhone
     * @return插入的行数
     */
    int insertSuccessKilled(long seckillId,long userPhone);


    /**
     * 根据秒杀商品的id查询明细SuccessKilled对象(该对象携带了Seckill秒杀产品对象)
     * @param seckillId
     * @return
     */
    SuccessKilled queryByIdWithSeckill(long seckillId,long userPhone);
}

接下来基于MyBatis来实现我们之前设计的Dao层接口。首先需要配置我们的MyBatis,在resources包下创建MyBatis全局配置文件mybatis-config.xml文件,在浏览器中输入http://mybatis.github.io/mybatis-3/zh/index.html打开MyBatis的官网文档,点击左边的"入门"栏框,找到mybatis全局配置文件,在这里有xml的一个规范,也就是它的一个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">

到我们的项目mybatis全局配置文件中,然后在全局配置文件中加入如下配置信息:

<?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的getGeneratekeys获取自增主键值-->
        <setting name="useGeneratedKeys" value="true"/>
        <!--使用列别名替换列名  默认值为true
        select name as title(实体中的属性名是title) form table;
        开启后mybatis会自动帮我们把表中name的值赋到对应实体的title属性中
        -->
        <setting name="useColumnLabel" value="true"/>

        <!--开启驼峰命名转换Table:create_time到 Entity(createTime)-->
        <setting name="mapUnderscoreToCamelCase" value="true"/>
    </settings>

</configuration>

配置文件创建好后我们需要关注的是Dao接口该如何实现,mybatis为我们提供了mapper动态代理开发的方式为我们自动实现Dao的接口。在mapper包下创建对应Dao接口的xml映射文件,里面用于编写我们操作数据库的sql语句,SeckillDao.xml和SuccessKilledDao.xml。既然又是一个xml文件,我们肯定需要它的dtd文件,在官方文档中,点击左侧"XML配置",在它的一些事例中,找到它的xml约束:

<!DOCTYPE mapper
    PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
    "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

加入到两个mapper映射xml文件中,然后对照Dao层方法编写我们的映射文件内容如下:

SeckillDao.xml:

<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.codingxiaxw.dao.SeckillDao">
    <!--目的:为dao接口方法提供sql语句配置
    即针对dao接口中的方法编写我们的sql语句-->


    <update id="reduceNumber">
        UPDATE seckill
        SET number = number-1
        WHERE seckill_id=#{seckillId}
        AND start_time <![CDATA[ <= ]]> #{killTime}
        AND end_time >= #{killTime}
        AND number > 0;
    </update>

    <select id="queryById" resultType="Seckill" parameterType="long">
        SELECT *
        FROM seckill
        WHERE seckill_id=#{seckillId}
    </select>

    <select id="queryAll" resultType="Seckill">
        SELECT *
        FROM seckill
        ORDER BY create_time DESC
        limit #{offset},#{limit}
    </select>


</mapper>

SuccessKilledDao.xml:

<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.codingxiaxw.dao.SuccessKilledDao">

    <insert id="insertSuccessKilled">
        <!--当出现主键冲突时(即重复秒杀时),会报错;不想让程序报错,加入ignore-->
        INSERT ignore INTO success_killed(seckill_id,user_phone,state)
        VALUES (#{seckillId},#{userPhone},0)
    </insert>

    <select id="queryByIdWithSeckill" resultType="SuccessKilled">

        <!--根据seckillId查询SuccessKilled对象,并携带Seckill对象-->
        <!--如何告诉mybatis把结果映射到SuccessKill属性同时映射到Seckill属性-->
        <!--可以自由控制SQL语句-->
        SELECT
            sk.seckill_id,
            sk.user_phone,
            sk.create_time,
            sk.state,
            s.seckill_id "seckill.seckill_id",
            s.name "seckill.name",
            s.number "seckill",
            s.start_time "seckill.start_time",
            s.end_time "seckill.end_time",
            s.create_time "seckill.create_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>

接下来我们开始MyBatis和Spring的整合,整合目标:1.更少的编码:只写接口,不写实现类。2.更少的配置:别名、配置扫描映射xml文件、dao实现。3.足够的灵活性:自由定制SQL语句、自由传结果集自动赋值。

在resources包下创建一个spring包,里面放置spring对Dao、Service、transaction的配置文件。在浏览器中输入http://docs.spring.io/spring/docs/进入到Spring的官网中下载其pdf官方文档,在其官方文档中找到它的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">

在spring包下创建一个spring配置dao层对象的配置文件spring-dao.xml,加入上述dtd约束,然后添加二者整合的配置,内容如下:

<?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">

    <!--配置整合mybatis过程
    1.配置数据库相关参数-->
    <context:property-placeholder location="classpath:jdbc.properties"/>

    <!--2.数据库连接池-->
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <!--配置连接池属性-->
        <property name="driverClass" value="${driver}" />

        <!-- 基本属性 url、user、password -->
        <property name="jdbcUrl" value="${url}" />
        <property name="user" value="${username}" />
        <property name="password" value="${password}" />

        <!--c3p0私有属性-->
        <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="2"/>
    </bean>

    <!--约定大于配置-->
    <!--3.配置SqlSessionFactory对象-->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <!--往下才是mybatis和spring真正整合的配置-->
        <!--注入数据库连接池-->
        <property name="dataSource" ref="dataSource"/>
        <!--配置mybatis全局配置文件:mybatis-config.xml-->
        <property name="configLocation" value="classpath:mybatis-config.xml"/>
        <!--扫描entity包,使用别名,多个用;隔开-->
        <property name="typeAliasesPackage" value="cn.codingxiaxw.entity"/>
        <!--扫描sql配置文件:mapper需要的xml文件-->
        <property name="mapperLocations" value="classpath:mapper/*.xml"/>
    </bean>

    <!--4:配置扫描Dao接口包,动态实现DAO接口,注入到spring容器-->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <!--注入SqlSessionFactory-->
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
        <!-- 给出需要扫描的Dao接口-->
        <property name="basePackage" value="cn.codingxiaxw.dao"/>
    </bean>
</beans>

需要我们在resources包下创建jdbc.properties用于配置数据库的连接信息,内容如下:

driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/seckill?useUnicode=true&characterEncoding=utf-8
username=root
password=xiaxunwu1996.

这样我们便完成了Dao层编码的开发,接下来就可以利用junit进行我们Dao层编码的测试了。首先测试SeckillDao.java,利用IDEA快捷键shift+command+T对SeckillDao.java进行测试,然后IDEA会自动在test包的java包下为我们生成对SeckillDao.java中所有方法的测试类SeckillDaoTest.java,内容如下:

public class SeckillDaoTest {
    @Test
    public void reduceNumber() throws Exception {

    }

    @Test
    public void queryById() throws Exception {

    }

    @Test
    public void queryAll() throws Exception {

    }
}

然后便可以在这个测试类中对SeckillDao接口的所有方法进行测试了,先测试queryById()方法,在该方法中添加内容:

/**
 * Created by codingBoy on 16/11/27.
 * 配置spring和junit整合,这样junit在启动时就会加载spring容器
 */
@RunWith(SpringJUnit4ClassRunner.class)
//告诉junit spring的配置文件
@ContextConfiguration({"classpath:spring/spring-dao.xml"})
public class SeckillDaoTest {

    //注入Dao实现类依赖
    @Resource
    private SeckillDao seckillDao;


    @Test
    public void queryById() throws Exception {
        long seckillId=1000;
        Seckill seckill=seckillDao.queryById(seckillId);
        System.out.println(seckill.getName());
        System.out.println(seckill);
    }
}

右键选择"debug queryById()",测试台成功输入该id为1000的商品信息,证明Dao的该方法正确,然后测试queryAll()方法,在该方法中添加如下内容:

 @Test
    public void queryAll() throws Exception {

        List<Seckill> seckills=seckillDao.queryAll(0,100);
        for (Seckill seckill : seckills)
        {
            System.out.println(seckill);
        }
    }

然后运行该方法,程序报错,报错信息如下:

Caused by: org.apache.ibatis.binding.BindingException: Parameter 'offset' not found. Available parameters are [1, 0, param1, param2]

意思就是无法完成offset参数的绑定,这也是我们java编程语言的一个问题,也就是java没有保存行参的记录,java在运行的时候会把List<Seckill> queryAll(int offset,int limit);中的参数变成这样:queryAll(int arg0,int arg1),这样我们就没有办法去传递多个参数。需要在SeckillDao接口中修改方法:

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

这样才能使我们的MyBatis识别offset和limit两个参数,将Dao层方法中的这两个参数与xml映射文件中sql语句的传入参数完成映射。然后重新测试,发现测试通过。然后测试reduceNumber()方法,在该方法中加入如下内容:

  @Test
    public void reduceNumber() throws Exception {

        long seckillId=1000;
        Date date=new Date();
        int updateCount=seckillDao.reduceNumber(seckillId,date);
        System.out.println(updateCount);

    }

运行该方法,报错:

Caused by: org.apache.ibatis.binding.BindingException: Parameter 'seckillId' not found. Available parameters are [1, 0, param1, param2]

发现依然是我们之前那个错误,更改SeckillDao接口的reduceNumber()方法:

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

然后重新运行,测试通过,可是我们查询数据库发现该库存表的商品数量没有减少,是因为我们当前时间没有达到秒杀商品要求的时间,所以不会成功秒杀。接下来进行SuccessKilledDao接口相关方法的测试,依旧使用IDEA快捷键shift+command+T快速生成其方法的相应测试类:

public class SuccessKilledDaoTest {
    @Test
    public void insertSuccessKilled() throws Exception 	{

    }

    @Test
    public void queryByIdWithSeckill() throws Exception 	{

    }
}

依然要在SuccessKilledDao的方法中用@Param注解完成参数的绑定,首先完成insertSuccessKilled()的测试:

@RunWith(SpringJUnit4ClassRunner.class)
//告诉junit spring的配置文件
@ContextConfiguration({"classpath:spring/spring-dao.xml"})
public class SuccessKilledDaoTest {

    @Resource
    private SuccessKilledDao successKilledDao;

    @Test
    public void insertSuccessKilled() throws Exception 	{

        long seckillId=1000;
        long userPhone=13476191877L;
        int insertCount=successKilledDao.insertSuccessKilled(seckillId,userPhone);
        System.out.println("insertCount="+insertCount);
    }
}

运行成功,测试台打印出insertCount=1的信息,即我们修改了表中的一条记录,这时查看秒杀成功明细表,发现该用户的信息已经被插入。然后再次运行该测试方法,程序没有报主键异常的错,是因为我们在编写我们的明细表的时候添加了一个联合主键的字段,它保证我们明细表中的seckillId和userPhone不能重复插入,另外在SuccessDao.xml中写的插入语句的ignore关键字也保证了这点。控制台输出0,表示没有对明细表做插入操作。然后进行queryByIdWithSeckill()方法的测试,需要在Dao层的方法中添加@Param注解:

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

然后进行该方法的测试:

@Test
    public void queryByIdWithSeckill() throws Exception 	{
        long seckillId=1000L;
        long userPhone=13476191877L;
        SuccessKilled successKilled=successKilledDao.queryByIdWithSeckill(seckillId,userPhone);
        System.out.println(successKilled);
        System.out.println(successKilled.getSeckill());


    }

运行,成功查询出我们明细表中id为1000且手机号码为13476191877的用户信息,并将表中对应的信息映射到SuccessKilled对象和Seckill对象的属性中。

到此,我们成功完成了Dao层开发及测试,接下来我们将进行Service层的开发工作,请查看我的下篇文章Java高并发秒杀API之Service层开发

2018.3.19更

欢迎加入我的Java交流群:659957958。群里目前已有1600人,每天都非常活跃,但为了筛选掉那些不怀好意的朋友进来搞破坏,所以目前入群方式已改成了付费方式,你只需要支付9块钱,即可获取到群文件中的所有干货以及群里面各位前辈们的疑惑解答;为了鼓励良好风气的发展,让每个新人提出的问题都得到解决,所以我将得到的入群收费收入都以红包的形式发放到那些主动给新手们解决疑惑的朋友手中。在这里,我们除了谈技术,还谈生活、谈理想;在这里,我们为你的学习方向指明方向,为你以后的求职道路提供指路明灯;在这里,我们把所有好用的干货都与你分享。还在等什么,快加入我们吧!

3.联系

If you have some questions after you see this article,you can tell your doubts in the comments area or you can find some info by clicking these links.

seckill's People

Contributors

chenchuxin avatar codingxiaxw avatar dependabot[bot] avatar ling955 avatar z-beatles avatar zhijuny avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

seckill's Issues

秒杀时的cookie出现问题

如题,前台写入cookie key为“userPhone”,但是在controller execute接口当中传入的却是“killPhone”,导致获取秒杀地址后点击按钮页面假死

Redis缓存中的数据没变化

这个代码最大的问题是,在秒杀成功后,没有更新Redis缓存中商品信息。当有另一名用户再去秒杀时,剩余商品的数量一直是初始数量。
image
image

运行不了

严重 [RMI TCP Connection(3)-127.0.0.1] org.apache.catalina.core.ContainerBase.addChildInternal ContainerBase.addChild: start:
org.apache.catalina.LifecycleException: Failed to start component [StandardEngine[Catalina].StandardHost[localhost].StandardContext[]]

出现了上面这个异常而且google了说是web.xml的问题?然而并没有问题啊web.xml

并发测试

可以使用testng进行并发测试,发现错误

@Test(threadPoolSize = 5001, invocationCount = 5002, timeOut = 1000000)
    public void testExecute() {
        RestTemplate template = new RestTemplate();

        HttpHeaders headers = new HttpHeaders();
        List<String> cookies = new ArrayList<>();
        cookies.add("userPhone=" + "1737467" + Phone++);
        headers.put(HttpHeaders.COOKIE, cookies);
        HttpEntity request = new HttpEntity(null, headers);

        long start = System.currentTimeMillis();
        String s = template.postForObject("http://localhost:8080/seckill/1000/bf204e2683e7452aa7db1a50b5713bae/execution", request, String.class);
        long end = System.currentTimeMillis();

        log.info("{}|{}ms ,res|{}",Thread.currentThread().getId(),(end - start),s);
    }

org.springframework.transaction.CannotCreateTransactionException: Could not open JDBC Connection for transaction; nested exception is java.sql.SQLException: An attempt by a client to checkout a Connection has timed out.

请问下大佬 Protostuff 如何序列化Class<T> ?

/*Protostuff 序列化泛型类*/
Page<User> pageUser = new Page<User>();
private RuntimeSchema<Item> schema = RuntimeSchema.createFrom(pageUser .class);//这里就不行了

byte[] array=ProtostuffIOUtil.toByteArray(item,schema,
LinkedBuffer.allocate(LinkedBuffer.DEFAULT_BUFFER_SIZE));

/*Protostuff 无法序列化泛型类*/
System.out.println("序列化数据为"+array);
/*异常信息*/
Exception in thread "main" java.lang.RuntimeException: Reading from a byte array threw an IOException (should never happen).
	at com.dyuproject.protostuff.IOUtil.mergeFrom(IOUtil.java:52)
	at com.dyuproject.protostuff.ProtobufIOUtil.mergeFrom(ProtobufIOUtil.java:95)
	at com.cityline.shoe.common.utils.SerializationUtils.main(SerializationUtils.java:44)
Caused by: com.dyuproject.protostuff.ProtostuffException: Corrupt input.
	at com.dyuproject.protostuff.runtime.ObjectSchema.readObjectFrom(ObjectSchema.java:641)
	at com.dyuproject.protostuff.runtime.ObjectSchema.mergeFrom(ObjectSchema.java:312)
	at com.dyuproject.protostuff.ByteArrayInput.mergeObject(ByteArrayInput.java:375)
	at com.dyuproject.protostuff.runtime.RuntimeRepeatedFieldFactory$5.mergeFrom(RuntimeRepeatedFieldFactory.java:385)
	at com.dyuproject.protostuff.runtime.MappedSchema.mergeFrom(MappedSchema.java:196)
	at com.dyuproject.protostuff.IOUtil.mergeFrom(IOUtil.java:43)
	... 2 more

秒杀超卖现象

一直存在一个疑问,在商城系统秒杀功能中。

1、假设库存只剩下1件了,A用户成功抢到并成功下单,但是A用户并没有支付

2、假设下单成功后15分钟内未支付,库存会回滚

3、A用户下单后,过了14分钟,发现订单未支付,于是发起微信支付,跳转到微信支付页面

4、进入支付页面后,输入支付密码,发现密码错误, A用户网络条件也不是很好,耽误了2分钟。最终输入正确的密码支付成功了。

5、而此时A的订单明显已经超时了,系统会把库存回滚。

6、回滚后,B用户看见有库存,马上下单,导致商品超卖

不知道我有没有描述清楚。这个问题一直困扰着我。

有想过在用户发起支付前给订单做个标识,让其晚点回滚,但是如果用户在支付页面耽误很久,也无济于事。

连接被拒绝是什么问题

25-Dec-2018 20:24:12.020 信息 [RMI TCP Connection(2)-127.0.0.1] org.apache.catalina.core.ApplicationContext.log No Spring WebApplicationInitializer types detected on classpath
25-Dec-2018 20:24:24.242 信息 [http-nio-8080-exec-5] org.apache.catalina.core.ApplicationContext.log Initializing Spring FrameworkServlet 'seckill-dispatcher'
25-Dec-2018 20:24:33.336 严重 [http-nio-8080-exec-6] org.apache.catalina.core.StandardWrapperValve.invoke Servlet.service() for servlet [seckill-dispatcher] in context with path [] threw exception [Request processing failed; nested exception is redis.clients.jedis.exceptions.JedisConnectionException: Could not get a resource from the pool] with root cause
java.net.ConnectException: Connection refused: connect
at java.net.DualStackPlainSocketImpl.waitForConnect(Native Method)
at java.net.DualStackPlainSocketImpl.socketConnect(DualStackPlainSocketImpl.java:85)
at java.net.AbstractPlainSocketImpl.doConnect(AbstractPlainSocketImpl.java:350)
at java.net.AbstractPlainSocketImpl.connectToAddress(AbstractPlainSocketImpl.java:206)
at java.net.AbstractPlainSocketImpl.connect(AbstractPlainSocketImpl.java:188)
at java.net.PlainSocketImpl.connect(PlainSocketImpl.java:172)
at java.net.SocksSocketImpl.connect(SocksSocketImpl.java:392)
at java.net.Socket.connect(Socket.java:589)
at redis.clients.jedis.Connection.connect(Connection.java:158)
at redis.clients.jedis.BinaryClient.connect(BinaryClient.java:82)
at redis.clients.jedis.BinaryJedis.connect(BinaryJedis.java:1641)
at redis.clients.jedis.JedisFactory.makeObject(JedisFactory.java:85)
at org.apache.commons.pool2.impl.GenericObjectPool.create(GenericObjectPool.java:861)
at org.apache.commons.pool2.impl.GenericObjectPool.borrowObject(GenericObjectPool.java:435)
at org.apache.commons.pool2.impl.GenericObjectPool.borrowObject(GenericObjectPool.java:363)
at redis.clients.util.Pool.getResource(Pool.java:48)
at redis.clients.jedis.JedisPool.getResource(JedisPool.java:99)
at cn.codingxiaxw.dao.cache.RedisDao.getOrPutSeckill(RedisDao.java:80)
at cn.codingxiaxw.service.impl.SeckillServiceImpl.getById(SeckillServiceImpl.java:55)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:317)
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:201)
at com.sun.proxy.$Proxy19.getById(Unknown Source)
at cn.codingxiaxw.web.SeckillController.detail(SeckillController.java:48)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:221)
at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:137)
at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:110)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandleMethod(RequestMappingHandlerAdapter.java:776)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:705)
at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:85)
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:959)
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:893)
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:967)
at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:858)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:635)
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:843)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:742)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:198)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96)
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:493)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:140)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:81)
at org.apache.catalina.valves.AbstractAccessLogValve.invoke(AbstractAccessLogValve.java:650)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:87)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:342)
at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:800)
at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66)
at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:806)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1498)
at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
at java.lang.Thread.run(Thread.java:748)

spring bean 初始化

请问,按照您的ssm项目配置,为什么所有bean都不是默认启动的时候初始化呢,而是在第一次发请求的时候所有bean才初始化.在您的配置文件中,并没有发现什么延迟初始化相关的配置。但是spring项目本身默认是启动的时候就初始化所有bean,实在没找到原因,请老师指点。 @codingXiaxw

数据库连接问题

问题1:
java.sql.SQLException: The server time zone value '???ú±ê×??±??' is unrecognized or represents more than one time zone. You must configure either the server or JDBC driver (via the serverTimezone configuration property) to use a more specifc time zone value if you want to utilize time zone support.
解决办法:
修改数据库连接url,增加serverTimezone=UTC
jdbc.url=jdbc:mysql://localhost:3306/seckill?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC

  1. 问题2
    Sun Jun 02 21:55:58 CST 2019 WARN: Establishing SSL connection without server's identity verification is not recommended. According to MySQL 5.5.45+, 5.6.26+ and 5.7.6+ requirements SSL connection must be established by default if explicit option isn't set. For compliance with existing applications not using SSL the verifyServerCertificate property is set to 'false'. You need either to explicitly disable SSL by setting useSSL=false, or set useSSL=true and provide

解决方法:
修改数据库连接url,增加useSSL=true
jdbc.url=jdbc:mysql://localhost:3306/seckill?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC&useSSL=true

问题3:
23:00:20.370 [http-nio-8080-exec-4] DEBUG org.springframework.web.servlet.DispatcherServlet - Could not complete request
org.springframework.web.util.NestedServletException: Handler dispatch failed; nested exception is java.lang.AbstractMethodError: Method com/mchange/v2/c3p0/impl/NewProxyPreparedStatement.isClosed()Z is abstract
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1006)
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:925)
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:974)
at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:866)

修改依赖。
原来为:

c3p0
c3p0
0.9.1.2

修改后为:

com.mchange
c3p0
0.9.5.2

关于注解映射器和适配器的说法有误?

视频和代码里对于注解映射器和适配器的说好好像有误。

在spring3.1之前使用org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping注解映射器。
在spring3.1之后使用org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping注解映射器。

在spring3.1之前使用org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter注解适配器。
在spring3.1之后使用org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter注解适配器。

视频录制的时候应该指的是spring 3.1之后了

致歉

首先想诚心说声抱歉,因为觉得你的readme写的很棒,所以懒得写readme copy了一份,但是未征求你的同意对此我真诚的道歉。但是我的src源码并非copy你的,而且添加了一些aop日志,存储过程,redis,test目录也没有按照imooc的写法,最后还是想说十分抱歉,抱歉,抱歉,我会移除copy你的部分

存储过程seckill.sql有点问题

存储过程seckill.sql有点问题,导致重复秒杀时仍然秒杀成功,改成下面就ok了。
`DELIMITER $$ -- console中 ; 转换为 $$

CREATE PROCEDURE seckill.execute_seckill
(IN v_seckill_id BIGINT, IN v_phone BIGINT,
IN v_kill_time TIMESTAMP, OUT r_result int)
BEGIN
DECLARE insert_count INT DEFAULT 0;
START TRANSACTION ;
INSERT IGNORE INTO success_killed
(seckill_id, user_phone,create_time)
VALUES (v_seckill_id, v_phone,v_kill_time);
SELECT row_count() INTO insert_count;
IF (insert_count = 0) THEN
ROLLBACK ;
SET r_result = -1;
ELSEIF (insert_count < 0) THEN
ROLLBACK ;
SET r_result = -2;
ELSE
UPDATE seckill
SET number = number -1
WHERE seckill_id = v_seckill_id
AND end_time > v_kill_time
AND start_time < v_kill_time
AND number > 0;
SELECT row_count() into insert_count;
IF (insert_count = 0) THEN
ROLLBACK;
SET r_result = 0;
ELSEIF (insert_count < 0) THEN
ROLLBACK;
SET r_result = -2;
ELSE
COMMIT;
SET r_result = 1;
END IF;
END IF;
END;
$$`

/seckill/list 404

你好,初学Spring,在IDEA生成war后部署到了Tomcat上,localhost:8080/seckill显示Hello World但/seckill/list 404是为什么呢?

下面是log:

29-Nov-2017 19:50:08.875 INFO [http-nio-8080-exec-15] org.apache.catalina.core.StandardContext.reload Reloading Context with name [/seckill] has started
29-Nov-2017 19:50:08.877 INFO [http-nio-8080-exec-15] org.springframework.web.context.support.XmlWebApplicationContext.doClose Closing WebApplicationContext for namespace 'seckill-dispatcher-servlet': startup date [Wed Nov 29 18:12:49 EST 2017]; root of context hierarchy
29-Nov-2017 19:50:10.090 INFO [http-nio-8080-exec-15] org.apache.catalina.core.StandardContext.reload Reloading Context with name [/seckill] is completed
29-Nov-2017 19:50:27.670 INFO [http-nio-8080-exec-19] org.springframework.web.servlet.DispatcherServlet.initServletBean FrameworkServlet 'seckill-dispatcher': initialization started
29-Nov-2017 19:50:27.685 INFO [http-nio-8080-exec-19] org.springframework.web.context.support.XmlWebApplicationContext.prepareRefresh Refreshing WebApplicationContext for namespace 'seckill-dispatcher-servlet': startup date [Wed Nov 29 19:50:27 EST 2017]; root of context hierarchy
29-Nov-2017 19:50:27.720 INFO [http-nio-8080-exec-19] org.springframework.beans.factory.xml.XmlBeanDefinitionReader.loadBeanDefinitions Loading XML bean definitions from file [/usr/local/Cellar/tomcat/8.5.23/libexec/webapps/seckill/WEB-INF/classes/spring/spring-service.xml]
29-Nov-2017 19:50:27.824 INFO [http-nio-8080-exec-19] org.springframework.beans.factory.xml.XmlBeanDefinitionReader.loadBeanDefinitions Loading XML bean definitions from file [/usr/local/Cellar/tomcat/8.5.23/libexec/webapps/seckill/WEB-INF/classes/spring/spring-web.xml]
29-Nov-2017 19:50:27.885 INFO [http-nio-8080-exec-19] org.springframework.beans.factory.xml.XmlBeanDefinitionReader.loadBeanDefinitions Loading XML bean definitions from file [/usr/local/Cellar/tomcat/8.5.23/libexec/webapps/seckill/WEB-INF/classes/spring/spring-dao.xml]
29-Nov-2017 19:50:28.016 INFO [http-nio-8080-exec-19] org.springframework.context.support.PropertySourcesPlaceholderConfigurer.loadProperties Loading properties file from class path resource [jdbc.properties]
19:50:28.155 [http-nio-8080-exec-19] DEBUG org.apache.ibatis.logging.LogFactory - Logging initialized using 'class org.apache.ibatis.logging.slf4j.Slf4jImpl' adapter.
29-Nov-2017 19:50:28.185 INFO [http-nio-8080-exec-19] com.mchange.v2.log.MLog.<clinit> MLog clients using java 1.4+ standard logging.
29-Nov-2017 19:50:28.216 INFO [http-nio-8080-exec-19] com.mchange.v2.c3p0.C3P0Registry.banner Initializing c3p0-0.9.1.1 [built 15-March-2007 01:32:31; debug? true; trace: 10]
29-Nov-2017 19:50:28.217 WARNING [http-nio-8080-exec-19] com.mchange.v2.c3p0.management.ActiveManagementCoordinator.attemptManageC3P0Registry A C3P0Registry mbean is already registered. This probably means that an application using c3p0 was undeployed, but not all PooledDataSources were closed prior to undeployment. This may lead to resource leaks over time. Please take care to close all PooledDataSources.
19:50:28.349 [http-nio-8080-exec-19] DEBUG org.apache.ibatis.io.ResolverUtil - Class not found: org.jboss.vfs.VFS
19:50:28.349 [http-nio-8080-exec-19] DEBUG org.apache.ibatis.io.ResolverUtil - JBoss 6 VFS API is not available in this environment.
19:50:28.350 [http-nio-8080-exec-19] DEBUG org.apache.ibatis.io.ResolverUtil - Class not found: org.jboss.vfs.VirtualFile
19:50:28.351 [http-nio-8080-exec-19] DEBUG org.apache.ibatis.io.ResolverUtil - VFS implementation org.apache.ibatis.io.JBoss6VFS is not valid in this environment.
19:50:28.351 [http-nio-8080-exec-19] DEBUG org.apache.ibatis.io.ResolverUtil - Using VFS adapter org.apache.ibatis.io.DefaultVFS
19:50:28.352 [http-nio-8080-exec-19] DEBUG org.apache.ibatis.io.ResolverUtil - Find JAR URL: file:/usr/local/Cellar/tomcat/8.5.23/libexec/webapps/seckill/WEB-INF/classes/cn/codingxiaxw/entity/
19:50:28.352 [http-nio-8080-exec-19] DEBUG org.apache.ibatis.io.ResolverUtil - Not a JAR: file:/usr/local/Cellar/tomcat/8.5.23/libexec/webapps/seckill/WEB-INF/classes/cn/codingxiaxw/entity/
19:50:28.352 [http-nio-8080-exec-19] DEBUG org.apache.ibatis.io.ResolverUtil - Reader entry: Seckill.class
19:50:28.353 [http-nio-8080-exec-19] DEBUG org.apache.ibatis.io.ResolverUtil - Reader entry: SuccessKilled.class
19:50:28.353 [http-nio-8080-exec-19] DEBUG org.apache.ibatis.io.ResolverUtil - Listing file:/usr/local/Cellar/tomcat/8.5.23/libexec/webapps/seckill/WEB-INF/classes/cn/codingxiaxw/entity/
19:50:28.353 [http-nio-8080-exec-19] DEBUG org.apache.ibatis.io.ResolverUtil - Find JAR URL: file:/usr/local/Cellar/tomcat/8.5.23/libexec/webapps/seckill/WEB-INF/classes/cn/codingxiaxw/entity/Seckill.class
19:50:28.353 [http-nio-8080-exec-19] DEBUG org.apache.ibatis.io.ResolverUtil - Not a JAR: file:/usr/local/Cellar/tomcat/8.5.23/libexec/webapps/seckill/WEB-INF/classes/cn/codingxiaxw/entity/Seckill.class
19:50:28.354 [http-nio-8080-exec-19] DEBUG org.apache.ibatis.io.ResolverUtil - Reader entry: ����1\
19:50:28.354 [http-nio-8080-exec-19] DEBUG org.apache.ibatis.io.ResolverUtil - Find JAR URL: file:/usr/local/Cellar/tomcat/8.5.23/libexec/webapps/seckill/WEB-INF/classes/cn/codingxiaxw/entity/SuccessKilled.class
19:50:28.354 [http-nio-8080-exec-19] DEBUG org.apache.ibatis.io.ResolverUtil - Not a JAR: file:/usr/local/Cellar/tomcat/8.5.23/libexec/webapps/seckill/WEB-INF/classes/cn/codingxiaxw/entity/SuccessKilled.class
19:50:28.354 [http-nio-8080-exec-19] DEBUG org.apache.ibatis.io.ResolverUtil - Reader entry: ����1T
19:50:28.355 [http-nio-8080-exec-19] DEBUG org.apache.ibatis.io.ResolverUtil - Checking to see if class cn.codingxiaxw.entity.Seckill matches criteria [is assignable to Object]
19:50:28.355 [http-nio-8080-exec-19] DEBUG org.apache.ibatis.io.ResolverUtil - Checking to see if class cn.codingxiaxw.entity.SuccessKilled matches criteria [is assignable to Object]
19:50:28.355 [http-nio-8080-exec-19] DEBUG o.m.spring.SqlSessionFactoryBean - Scanned package: 'cn.codingxiaxw.entity' for aliases
19:50:28.377 [http-nio-8080-exec-19] DEBUG o.m.spring.SqlSessionFactoryBean - Parsed configuration file: 'class path resource [mybatis-config.xml]'
19:50:28.444 [http-nio-8080-exec-19] DEBUG o.m.spring.SqlSessionFactoryBean - Parsed mapper file: 'file [/usr/local/Cellar/tomcat/8.5.23/libexec/webapps/seckill/WEB-INF/classes/mapper/SuccessKilledDao.xml]'
19:50:28.451 [http-nio-8080-exec-19] DEBUG o.m.spring.SqlSessionFactoryBean - Parsed mapper file: 'file [/usr/local/Cellar/tomcat/8.5.23/libexec/webapps/seckill/WEB-INF/classes/mapper/SeckillDao.xml]'
29-Nov-2017 19:50:28.759 INFO [http-nio-8080-exec-19] org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping.registerHandlerMethod Mapped "{[/seckill/{seckillId}/{md5}/execution],methods=[POST],produces=[application/json;charset=UTF-8]}" onto public cn.codingxiaxw.dto.SeckillResult<cn.codingxiaxw.dto.SeckillExecution> cn.codingxiaxw.web.SeckillController.execute(java.lang.Long,java.lang.String,java.lang.Long)
29-Nov-2017 19:50:28.760 INFO [http-nio-8080-exec-19] org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping.registerHandlerMethod Mapped "{[/seckill/list],methods=[GET]}" onto public java.lang.String cn.codingxiaxw.web.SeckillController.list(org.springframework.ui.Model)
29-Nov-2017 19:50:28.760 INFO [http-nio-8080-exec-19] org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping.registerHandlerMethod Mapped "{[/seckill/{seckillId}/exposer],methods=[GET],produces=[application/json;charset=UTF-8]}" onto public cn.codingxiaxw.dto.SeckillResult<cn.codingxiaxw.dto.Exposer> cn.codingxiaxw.web.SeckillController.exposer(java.lang.Long)
29-Nov-2017 19:50:28.760 INFO [http-nio-8080-exec-19] org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping.registerHandlerMethod Mapped "{[/seckill/time/now],methods=[GET]}" onto public cn.codingxiaxw.dto.SeckillResult<java.lang.Long> cn.codingxiaxw.web.SeckillController.time()
29-Nov-2017 19:50:28.760 INFO [http-nio-8080-exec-19] org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping.registerHandlerMethod Mapped "{[/seckill/{seckillId}/detail],methods=[GET]}" onto public java.lang.String cn.codingxiaxw.web.SeckillController.detail(java.lang.Long,org.springframework.ui.Model)
29-Nov-2017 19:50:28.957 INFO [http-nio-8080-exec-19] org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.initControllerAdviceCache Looking for @ControllerAdvice: WebApplicationContext for namespace 'seckill-dispatcher-servlet': startup date [Wed Nov 29 19:50:27 EST 2017]; root of context hierarchy
29-Nov-2017 19:50:28.991 INFO [http-nio-8080-exec-19] org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.initControllerAdviceCache Looking for @ControllerAdvice: WebApplicationContext for namespace 'seckill-dispatcher-servlet': startup date [Wed Nov 29 19:50:27 EST 2017]; root of context hierarchy
29-Nov-2017 19:50:29.019 INFO [http-nio-8080-exec-19] org.springframework.web.servlet.handler.SimpleUrlHandlerMapping.registerHandler Mapped URL path [/**] onto handler 'org.springframework.web.servlet.resource.DefaultServletHttpRequestHandler#0'
29-Nov-2017 19:50:29.050 INFO [http-nio-8080-exec-19] org.springframework.web.servlet.DispatcherServlet.initServletBean FrameworkServlet 'seckill-dispatcher': initialization completed in 1380 ms

DDL issue

CREATE TABLE seckill(
seckill_id BIGINT NOT NUll AUTO_INCREMENT COMMENT '商品库存ID',
name VARCHAR(120) NOT NULL COMMENT '商品名称',
number int NOT NULL COMMENT '库存数量',
create_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
start_time TIMESTAMP NOT NULL COMMENT '秒杀开始时间',
end_time TIMESTAMP NOT NULL 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='秒杀库存表';
--把 create_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP 这条语句放在start_time 前面,
--原因是mysql会默认为表中的第一个timestamp字段(且设置了NOT NULL)隐式设置DEFAULAT CURRENT_TIMESTAMP。
-- 所以默认的顺序设置实际上等同于设置了两个CURRENT_TIMESTAMP,因此而报错,调整之后就可以了。

创建success_killed表没有设置create_time自动更新

``create_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间'
要设置create_time自动更新,因为insertSuccessKilled()没有插入create_time,create_time为默认值00-00-00 00:00:00。不然在测试queryByIdWithSeckill()时程序会报错:Caused by: com.mysql.cj.exceptions.DataReadException: Zero date value prohibited

TIM截图20200518200101

有几个值得思考的问题

1.如何解决超卖的问题?场景描述:若某件商品库存只有5件,如何保证第六个客户不会秒杀到第6件该商品。

2.后端采用Redis作为中间层缓存seckill对象,seckill对象包括的字段有ID、时间、库存量。那么问题来了,当Redis内部缓存的Seckill对象库存量发生变化时,此时Redis如何将这种变化的状态(即库存量的减少)缓存到自身中?

希望大家在利用这两个项目做练习项目时,带着这两个问题进行思考,知道解决方案的人可以在底下留下自己的思路。

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.