Java高并发秒杀API之业务分析与DAO层

Posted 德峰

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java高并发秒杀API之业务分析与DAO层相关的知识,希望对你有一定的参考价值。

课程介绍

高并发和秒杀都是当今的热门词汇,如何使用Java框架实现高并发秒杀API是该系列课程要研究的内容。秒杀系列课程分为四门,本门课程是第一门,主要对秒杀业务进行分析设计,以及DAO层的实现。课程中使用了流行的框架组合SpringMVC+Spring+MyBatis,还等什么,赶快来加入吧!

第1章 课程介绍
本章介绍秒杀系统的技术内容,以及系统演示。并介绍不同程度的学员可以学到什么内容。
第2章 梳理所有技术和搭建工程
本章首先介绍秒杀系统所用框架和技术点,然后介绍如何基于maven搭建项目,最后对工程目录包进行了划分。
第3章 秒杀业务分析
本章讲解常见秒杀业务以及如何用最常用的技术实现。分析了秒杀业务的难点,以及本课程要实现哪些秒杀API。
第4章 DAO层设计与开发
本章介绍秒杀系统数据库设计与实现,分析DAO数据持久化层所需接口,并编码实现。以及MyBatis如何与spring进行整合,最后介绍如何测试整合框架,完成DAO层的单元测试。

第1章 课程介绍

1-1 课程介绍

SpringMVC+Spring+MyBatis使用与整合
秒杀类系统需求理解和实现
常用技术解决高并发问题(java web、前端、mysql

为什么使用这三个框架?
互联网公司常用框架(阿里、京东、搜狐、美团…)
框架易于使用和轻量级
底代码侵入性
成熟的社区和用户群

为什么用秒杀系统来讲本课程?
秒杀业务场景具有典型“事务”特性
秒杀/红包类需求越来越常见
面试常问问题(如何设计一个秒杀系统和优化一个秒杀系统)

从本课程学到什么?
初学者:框架的使用与整合 技巧
有经验者:秒杀分析过程和优化思路

秒杀系列将分为四门课程进行,分别是:
Java高并发秒杀API之业务分析与DAO层
Java高并发秒杀API之Service
Java高并发秒杀API之web
Java高并发秒杀API之高并发优化

1-2 项目效果演示

这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述

第2章 梳理所有技术和搭建工程

2-1 相关技术介绍

用到那些技术?
MySQL(表设计 、SQL技巧、事务和行级锁)
MyBatis(DAO层设计与开发、MyBatis合理使用、MyBatis与Spring整合)
Spring(Spring IOC整合Service、声明式事务运用)
SpringMVC(Restful接口设计和使用、框架运作流程、controller开发技巧)
前端(交互设计、Bootstrap、jQuery)
高并发(高并发点和高并发分析、优化思路并实现)

基于maven创建项目

2-2 创建项目和依赖

开始创建项目之前的说明
从零开始创建
从官网获取相关配置
使用Maven创建项目

为什么从官网获取资源
文档更全面权威
避免过时货错误
官网地址:
logback配置:http://logback.qos.ch/manual/configuration.html
spring配置:http://docs.spring.io/spring/docs/
mybatis配置:http://mybatis.github.io/mybatis-3/zh/index.html

开始创建项目
maven命令创建web骨架项目
mvn archetype:generate -DarchetypeCatalog=internal -DgroupId=com.seckill -DartifactId=seckill -DarchetypeArtifactId=maven-archetype-webapp
注:-DgroupId和-DartifactId标注项目的坐标,项目叫com.seckill,-DarchetypeArtifactId=maven-archetype-webapp表示使用maven的原型webapp的原型去创建项目

下面就通过IntelliJ IDEA或Eclipse开发工具Import项目

存在的问题:
1.用maven创建的webapp要修改servlet的版本:
maven 创建项目的web.xml 版本比较低(Servlet2.3,jsp默认的el表达式是不工作的),所以需要把他切换到更高的Servlet版本,可以去tomcat webapps包下面去找到示例项目的web.xml的头copy过来。如果使用的IntelliJ IDEA导入的项目需要把一些没有的目录补全,如下示例(仅供参考):
这里写图片描述

打开maven的pom.xml文件把默认的junit版本改为4.11是应为3.0的junit默认使用编程的方式,4.0是使用注解的方式来运行junit。再接下来就是补全项目的依赖

第3章 秒杀业务分析

3-1 秒杀业务分析

这里写图片描述
秒杀业务的核心–> 库存的处理
这里写图片描述
这里写图片描述
为什么需要事物?
这里写图片描述
这里写图片描述

关于数据落地:MySQL VS NoSQL(事务依然是目前最可靠的落地方案,NoSQL对事物的支持做的不是很好。)

3-2 MySQL实现秒杀难点分析

这里写图片描述

对于MySQL来说竞争反应到背后的技术是怎样的呢?(事物+行级锁)

事物:
Start Transaction
Update库存数量
Insert购买明细
Commit
这里写图片描述

秒杀的难点是如何高效的处理竞争?
知道问题后如何解决?

3-3 实现哪些秒杀功能

这里写图片描述
这里写图片描述

我们只实现秒杀相关的功能
这里写图片描述
这里写图片描述

第4章 DAO层设计与开发

4-1 数据库设计与编码

--数据库初始化

--创建数据库
CREATE DATABASE seckill;
--使用数据库
use seckill;
--创建秒杀库存表
CREATE table seckill(
    `seckill_id` bigint NOT NULL AUTO_INCREMENT COMMENT '商品库存id',
    `name` varchar(120) NOT NULL COMMENT '商品名称',
    `number` int NOT NULL COMMENT '库存数量',
    `start_time` timestamp NOT NULL COMMENT '秒杀开启时间',
    `end_time` timestamp NOT NULL COMMENT '秒杀结束时间',
    `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
    PRIMARY KEY (seckill_id),
    key idx_start_time(start_time),
    key idx_end_time(end_time), 
    key idx_create_time(create_time)
)ENGINE=InnoDB AUTO_INCREMENT=1000 DEFAULT CHARSET=utf8 COMMENT='描述库存表';

--创建索引,用于加速查询
--key idx_start_time(start_time),
--key idx_end_time(end_time),   
--key idx_create_time(create_time)

--默认mysql有多种存储引擎,但是可以支持事物的只有InnoDB,所以在这里通过显示的语法去告诉mysql这个表的ENGINE引擎是InnoDB
--由于数据库采用的是自增作为主键,给出初始的自增id:AUTO_INCREMENT=1000
--默认编码 DEFAULT CHARSET=utf-8 
--表的注释 COMMENT='秒杀库存表'

--初始化数据
insert into 
    seckill(name,number,start_time,end_time)
value
    ('1000元秒杀iphone6',100,'2012-12-12 00:00:00','2012-12-13 00:00:00'),
    ('500元秒杀ipad2',200,'2012-12-12 00:00:00','2012-12-13 00:00:00'),
    ('300元秒杀mi4',300,'2012-12-12 00:00:00','2012-12-13 00:00:00'),
    ('200元秒杀红米note',400,'2012-12-12 00:00:00','2012-12-13 00:00:00');


--秒杀成功明细
--用户登录认证相关的信息
create table success_killed(
    `seckill_id` bigint not null COMMENT '秒杀商品id',
    `user_phone` bigint not null comment '用户手机号',
    `state` tinyint not null default -1 comment '状态标识:-1:无效  0:成功  1:已付款  2:已发货',
    `create_time` timestamp not null comment '创建时间',
    primary key(seckill_id,user_phone),/*联合主键*/
    key idx_create_time(create_time)
)ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='秒杀成功明细表';

--连接数据库控制台
mysql -uroot -p123456

--查看创建表的结构
show create table seckill\\G

--为什么手写DDL
--记录每次上线的DDL修改
--上线V1.1
ALTER TABLE seckill
DROP INDEX idx_create_time,
ADD INDEX idx_c_s(start_time,create_time);


--上线V1.2
--DDL

4-2 DAO实体和接口编码

Table —-> Entity

package org.seckill.entity;

import java.util.Date;
/**
 * 秒杀库存实体
 * @author Administrator
 *
 */

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 + '}';
    }
}
package org.seckill.entity;

import java.util.Date;

/**
 * 秒杀成功明细实体
 */
public class SuccessKilled {

    /**
     * 一个秒杀seckill对应多个成功记录
     */
    private Seckill seckill;

    private long seckillId;

    private long userPhone;

    private short state;

    private Date createTime;

    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{" +
                "seckill=" + seckill +
                ", seckillId=" + seckillId +
                ", userPhone=" + userPhone +
                ", state=" + state +
                ", createTime=" + createTime +
                '}';
    }
}

DAO相关接口编码(DAO针对的是具体实体来操作的“实体的增删改查”)

package org.seckill.dao;

import java.util.Date;
import java.util.List;

import org.seckill.entity.Seckill;

public interface SeckillDao {

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

    /**
     * 根据id查询秒杀对象
     * @param seckillId
     * @return
     */
    Seckill queryById(long seckillId);


    /**
     * 根据偏移量查询描述列表
     * @param offet
     * @param limit
     * @return
     */
    List<Seckill> queyAll(int offet,int limit);

}
package org.seckill.dao;

import org.seckill.entity.SuccessKilled;

public interface SuccessKilledDao {

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

    /**
     * 根据id查询SuccessKilled并携带秒杀产品对象
     * @param seckillId
     * @return
     */
    SuccessKilled queryByIdWithSeckill(long seckillId);



}

4-3 基于myBatis实现DAO理论

MyBatis用来做什么?
这里写图片描述

这里写图片描述

MyBatis怎么用
SQL写在哪?(XML提供SQL、注解提供SQL,注:注解是java5.0之后提供的一个新特性)
对于实际的使用中建议使用XML文件的方式提供SQL,通过注解的方式呢,注解本身还是java源码,修改和调整SQL其实是非常不方便的,一样需要重新编译类,当我们写复杂的SQL尤其拼接逻辑时,注解处理起来就会非常繁琐,那么XML提供了很多的SQL拼接和处理逻辑的标签可以非常方便的帮我们去做封装。
如何DAO接口接口
Mapper自动实现DAO(也就是DAO只需要设计接口,不需要去写实现类,MyBatis知道我们的参数、返回类型是什么同时也有SQL文件,他可以制动帮我们生成接口的实现类来帮我们执行参数的封装,执行SQL,把我们的返回结果集封装成我们想要的类型)
第二种是通过API编程方式实现DAO接口(MyBatis通过给我们提供了非常多的API,跟其他的ORM和JDBC很像)

在实际开发中建议使用Mapper自动实现DAO,这样可以直接只关注SQL如何编写,如何去设计DAO接口,帮我们节省了很多的维护程序,所有的实现都是MyBatis自动完成。

4-4 基于myBatis实现DAO编程(上)

这里写图片描述
打开MyBatis的官方文档
http://www.mybatis.org/mybatis-3/zh/index.html
入门——>找到MyBatis全局配置,里面有XML的规范(XML的标签约束dtd文件)拷入到项目的MyBatis全局配置文件中,开始配置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获取自增主键值,当inert一条记录时我们是不插入id的,id是通过自增去赋值的,当插入完后想得到该插入记录的id时可以调用jdbc的getGeneratekeys -->
        <setting name="useGeneratedKeys" value="true" />

        <!--使用列别名替换别名 默认true select name as title form table; -->
        <setting name="useColumnLabel" value="true" />
        <!--开启驼峰命名转换Table:create_time到 Entity(createTime) -->
        <setting name="mapUnderscoreToCamelCase" value="true" />
    </settings>
</configuration>

使用Mapper自动实现DAO
在mapper目录中创建SeckillDao.xml和SuccessKilledDao.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
    PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
    "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<!-- namespace:指定为那个接口提供配置 --> 
<mapper namespace="org.seckill.dao.SeckillDao">
    <!-- 目的:为DAO接口方法提供sql语句配置  -->  

    <!-- int reduceNumber(long seckillId, Date killTime);-->
    <update id="reduceNumber">
        <!-- 具体sql -->
       UPDATE seckill set number = number -1
       where seckill_id = #{seckillId}
       AND  start_time <![CDATA[ <= ]]> #{killTime}
       AND  end_time >= #{killTime}
       AND number >0;
   </update>

    <!-- parameterType:参数类型,正常情况java表示一个类型的包名+类名,这直接写类名,因为后面有一个配置可以简化写包名的过程 -->
    <!--   Seckill queryById(long seckillId);-->
    <select id="queryById" resultType="Seckill" parameterType="long">
        <!-- 可以通过别名的方式列明到java名的转换,如果开启了驼峰命名法就可以不用这么写了 
        select seckill_id as seckillId
        -->

        SELECT seckill_id,name,number,start_time,end_time,create_time
        FROM seckill
        WHERE seckill_id = #{seckillId}
    </select>

    <!-- List<Seckill> queryAll(int offset,int limit);-->
    <select id="queryAll" resultType="Seckill">
        SELECT seckill_id,name,number,start_time,end_time,create_time
        FROM seckill
        ORDER BY create_time DESC
        limit #{offset},#{limit}
    </select>





</mapper>  

4-5 基于myBatis实现DAO编程(下)

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"  "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.seckill.dao.SuccessKilledDao">

    <!--通过ignore关键字将主键冲突时的报错改为返回0-->
    <!--int insertSuccessKilled(long seckilledId,long userPhone);-->
    <update id="insertSuccessKilled">
       INSERT ignore INTO success_killed(seckill_id,user_phone,state)VALUES (#{seckilledId},#{userPhone},1)
    </update>

    <!-- SuccessKilled queryByIdWithSeckill(@Param("seckilledId") long seckilledId, @Param("userPhone") long userPhone);-->
    <select id="queryByIdWithSeckill" resultType="SuccessKilled">
      SELECT
      sk.seckill_id,sk.user_phone,sk.create_time,sk.state,
      s.seckill_id "seckill.seckill_id",s.name "seckill.name", 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=#{seckilledId} and sk.user_phone=#{userPhone};
    </select>

</mapper>

注:上面的s.seckill_id “seckill.seckill_id”表示将s.seckill_id这一列的数据是seckill属性里的seckill_id属性,是一个级联的过程,使用的就是别名只是忽略了as关键字,别名要加上双引号。

4-6 myBatis整合Spring理论

整合目标
更少的编码(只写接口,不写实现,MyBatis实现DAO)
更少的配置
足够的灵活性
这里写图片描述

这里写图片描述

这里写图片描述

这里写图片描述

这里写图片描述

XML提供SQL,DAO接口Mapper来作为接口实现类时完全可以依赖于Spring去帮我们做好这些事情,当用Spring整合MyBatis时建议使用这两种方式ML提供SQL,DAO接口提供Mapper。
这里写图片描述

4-7 mybatis整合Spring编码

在resource目录下创建一个新的目录Spring(存放所有spring相关的配置)

<?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.数据库连接池-->
    <!--todo java.sql.SQLException: Access denied for user ''@'localhost' (using password: NO)-->
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
                    <!-- 配置连接池属性 -->
                    <property name="driverClass" value="${driverClassName}"></property>
                    <property name="jdbcUrl" value="${jdbc.url}"></property>
                    <property name="user" value="${jdbc.username}"></property>
                    <property name="password" value="${jdbc.password}"></property>
<!-- 
            <property name="driverClass" value="com.mysql.jdbc.Driver"></property>
            <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/test_mysql"></property>
            <property name="jdbcUrl" value="jdbc:mysql://127.0.0.1:3306/test_mysql"></property>
            <property name="user" value="root"></property>
            <property name="password" value="111111"></property>
 -->
            <!-- c3p0连接池的私有属性 -->
            <property name="maxPoolSize" value="30"></property>
            <property name="minPoolSize" value="10"></property>
            <!-- 关闭连接后不自动commit -->
            <property name="autoCommitOnClose" value="false"></property>
            <!--  获取连接超时时间 -->
            <property name="checkoutTimeout" value="10000"></property>
            <!--  获取连接重试次数 -->
            <property name="acquireRetryAttempts" value="3"></property>
        </bean>
<!-- 
    <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">

        <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://localhost:3306/test_mysql"/>
        <property name="username" value="root"/>
        <property name="password" value="111111"/>
        todo
           <property name="driverClassName" value="${driverClassName}"/>
                <property name="url" value="${jdbc.url}"/>
                <property name="username" value="${jdbc.username}"/>
                <property name="password" value="${jdbc.password}"/>
    </bean>
 -->
    <!--3.配置SqlSessionFactory对象-->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <!--注入数据库连接池-->
        <property name="dataSource" ref="dataSource"/>
        <!--配置mybatis全局配置文件:mybatis-config.xml-->
        <property name="configLocation" value="classpath:mybatis-config.xml"/>
        <!--扫描entity包,使用别名,多个用;隔开-->
        <property name="typeAliasesPackage" value="org.seckill.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="org.seckill.dao"/>
    </bean>

    <!--RedisDao-->
    <!-- <bean id="redisDao" class="org.seckill.dao.cache.RedisDao">
        <constructor-arg index="0" value="localhost"/>
        <constructor-arg index="1" value="6379"/>
    </bean> -->

</beans>

4-8 DAO层单元测试编码和问题排查(上)

这里写图片描述

package org.seckill.dao;

import java.util.Date;
import java.util.List;

import javax.annotation.Resource;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.seckill.entity.Seckill;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

/**
 * Created by wchb7 on 16-5-8.
 */

/**
 * 配置Spring和Junit整合,junit启动时加载springIOC容器
 * spring-test,junit
 */

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:spring/spring-dao.xml")
public class SeckillDaoTest {


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

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


    @Test
    public void testQueryAll() throws Exception {
//      Java没有保存形参的记录:QueryAll(int offset,int limit)->QueryAll(arg0,arg1);
//      因为java形参的问题,多个基本类型参数的时候需要用@Param("seckillId")注解区分开来
        List<Seckill> seckills = seckillDao.queryAll(0, 4);
        for (Seckill seckill : seckills) {
            System.out.println(seckill);
        }
    }


    @Test
    public void testReduceNumber() throws Exception {
        Date killTime = new Date();
        int updateCount = seckillDao.reduceNumber(1000L, killTime);
        System.out.println("updateCount:  " + updateCount);
    }
}

4-9 DAO层单元测试编码和问题排查(下)

package org.seckill.dao;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.seckill.entity.SuccessKilled;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import javax.annotation.Resource;

import static org.junit.Assert.*;

/**
 * Created by wchb7 on 16-5-9.
 */


@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:spring/spring-dao.xml")
public class SuccessKilledDaoTest {

    @Resource
    private SuccessKilledDao successKilledDao;

    @Test
    public void testInsertSuccessKilled() throws Exception {
        /*
         第一次:iinsertCount=1
         第一次:iinsertCount=0
         */
        long id = 1000L;
        long phone = 15811112222L;
        int insertCount = successKilledDao.insertSuccessKilled(id, phone);
        System.out.println("insertCount: " + insertCount);
    }

    @Test
    public void testQueryByIdWithSeckill() throws Exception {

        long id = 1000L;
        long phone = 15811112222L;
        SuccessKilled successKilled = successKilledDao.queryByIdWithSeckill(id, phone);
        System.out.println(successKilled);
        if (successKilled != null) {
            System.out.println(successKilled.getSeckill());
        }
    }
}

以上是关于Java高并发秒杀API之业务分析与DAO层的主要内容,如果未能解决你的问题,请参考以下文章

01 整合IDEA+Maven+SSM框架的高并发的商品秒杀项目之业务分析与DAO层

2017.4.26 慕课网--Java 高并发秒杀API

高并发秒杀系统--课程总结与思考

Java高并发秒杀API之web层

Java高并发秒杀系统API之SSM框架集成swagger与AdminLTE

Java高并发秒杀系统API之Web层开发