ssm实现一个秒杀系统

DAO

理论
流程
  1. idea建好maven项目。
  2. 配置好pom.xml,主要是:配置1个junit(测试类),3个slf4j+logback(用于日记实现),2个数据库依赖(mysql和c3p0引入),2个mybatis依赖(实现mybatis,还有mybatis和spring的整合),4个Servlet web依赖(servlet的类引入),3个spring的核心依赖,4个spring dao依赖(依靠它实spring和mybatis的整合),1个spring test依赖(测试类引入spring框架),一个redis依赖和3个protostuff序列化依赖(实现redis缓存)。
    代码如下:
<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>com.wentjiang</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>
			<groupId>junit</groupId>
			<artifactId>junit</artifactId>
			<version>4.11</version>
			<scope>test</scope>
		</dependency>
		
		<!-- 1.日志   slf4j,log4j,logback,common-logging 
			slf4j是规范/接口
			日志实现:log4j,logback,common-logging
			使用:sl4j+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>
		<!-- 2.数据库相关依赖  -->	
		<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.2</version>
		</dependency>
		
		<!-- 3.DAO框架依赖:mybatis依赖 -->
		<dependency>
			<groupId>org.mybatis</groupId>
			<artifactId>mybatis</artifactId>
			<version>3.3.0</version>
		</dependency>
		<dependency>
			<groupId>org.mybatis</groupId>
			<artifactId>mybatis-spring</artifactId>
			<version>1.2.3</version>
		</dependency>
		
		<!-- 4.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>
		
		<!-- 5.spring依赖 -->
		<!-- 核心依赖 -->
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-core</artifactId>
			<version>4.1.6.RELEASE</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-beans</artifactId>
			<version>4.1.6.RELEASE</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-context</artifactId>
			<version>4.1.6.RELEASE</version>
		</dependency>
		<!-- spring dao依赖 -->
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-tx</artifactId>
			<version>4.1.6.RELEASE</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-jdbc</artifactId>
			<version>4.1.6.RELEASE</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-web</artifactId>
			<version>4.1.6.RELEASE</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-webmvc</artifactId>
			<version>4.1.6.RELEASE</version>
		</dependency>
		
		<!-- spring test -->
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-test</artifactId>
			<version>4.1.6.RELEASE</version>
		</dependency>
		
		<!-- 引入redis客户端 :jedis -->
		<dependency>
			<groupId>redis.clients</groupId>
			<artifactId>jedis</artifactId>
			<version>2.7.3</version>
		</dependency>
		<!-- protostuff序列化依赖  -->
		<dependency>
			<groupId>com.dyuproject.protostuff</groupId>
			<artifactId>protostuff-core</artifactId>
			<version>1.0.8</version>
		</dependency>
		<dependency>
			<groupId>com.dyuproject.protostuff</groupId>
			<artifactId>protostuff-runtime</artifactId>
			<version>1.0.8</version>
		</dependency>
		
		<dependency>
			<groupId>commons-collections</groupId>
			<artifactId>commons-collections</artifactId>
			<version>3.2</version>
		</dependency>
		
	</dependencies>

	<build>
		<finalName>seckill</finalName>
		<plugins>
			<!-- define the project compile level -->
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-compiler-plugin</artifactId>
				<version>2.3.2</version>
				<configuration>
					<source>1.7</source>
					<target>1.7</target>
				</configuration>
			</plugin>
		</plugins>
	</build>
</project>

  1. 创建数据库和表:
-- 数据库初始化脚本
-- 创建数据库

create database seckill;
-- 使用数据库
use seckill;
-- 创建秒杀库存表
create table seckill(
	`seckill_id` bitint not null auto_increment comment '商品库存id',
	`name` varchar(120) not null comment '商品名称',
	`number` int not null comment '库存数量',
	`start_time` timestamp not null comment '秒杀开始时间',
	`end_time` timestamp not null comment '秒杀结束时间',
	`create_time` timestamp not null default current_timestamp comment '创建时间',
	primary_key(seckill_id),
	key idx_start_time(start_time),
	key idx_end_time(end_time),
	key idx_create_time(create_time)
	)engine=InnoDB auto_increment=1000 default charset=utf8 comment='秒杀数据库';

-- 初始化数据
	insert into seckill(name,number,start_time,end_time)
	values
		("1000元秒杀iphone",100,"2015-11-01 00:00:00","2015-11-02 00:00:00"),
		("1元秒杀ipad",100,"2015-11-01 00:00:00","2015-11-02 00:00:00"),
		("200元秒杀三星g7",100,"2015-11-01 00:00:00","2015-11-02 00:00:00"),
		("20元秒杀iwatch",100,"2015-11-01 00:00:00","2015-11-02 00:00:00"),
		("300元秒杀红米note2",100,"2015-11-01 00:00:00","2015-11-02 00:00:00"),
	
-- 秒杀成功明细表
-- 用户登录认证相关信息
create table success_killed(
	`seckill_id` bitint not null comment '秒杀商品id',
	`user_phone` bitint 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 auto_increment=1000 default charset=utf8 comment='秒杀成功明细';
	
  1. 创建entity层:
package com.wentjiang.entity;

import java.util.Date;

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 com.wentjiang.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 +
                '}';
    }
}
  1. 创建dao层接口
package com.wentjiang.dao;

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

import org.apache.ibatis.annotations.Param;

import com.wentjiang.entity.Seckill;

public interface SeckillDao {

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


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


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

    /**
     * 使用存储过程执行秒杀
     *
     * @param paramMap
     */
    void killByProcedure(Map<String, Object> paramMap);
}

package com.wentjiang.dao;

import org.apache.ibatis.annotations.Param;

import com.wentjiang.entity.SuccessKilled;

public interface SuccessKilledDao {
	 /**
     * 插入购买明细,可过滤重复(数据库有联合主键)
     *
     * @param seckilledId
     * @param userPhone
     * @return
     */
    int insertSuccessKilled(@Param("seckillId") long seckillId, @Param("userPhone") long userPhone);

    /**
     * 根据ID查询SuccessKilled并携带秒杀产品对象实体
     *
     * @param seckilledId
     * @param userPhone
     * @return
     */
    SuccessKilled queryByIdWithSeckill(@Param("seckillId") long seckillId, @Param("userPhone") long userPhone);

}

  1. 在resource包中新建mapper包,并且根据规范创建同名xml文件(不同名可以吗)
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
    PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
    "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.wentjiang.dao.SeckillDao">
	<!-- 目的:为dao接口方法提供sql语句配置 -->
	<update id="reduceNumber" >
		<!-- 具体的sql -->
		update
			seckill
		set
			number = number -1
		where seckill_id = #{seckillId}
		and start_time  <![CDATA[<=]]> #{killTime}
		and end_time <![CDATA[>=]]> #{killTime}
		and number >0;
	</update>
	<select id="queryById" resultType="Seckill" parameterType="long">
		select seckill_id,name,number,start_time,end_time,create_time
		from seckill
		where seckill_id = #{seckillId}
	</select>	
	
	<select id="queryAll" resultType="Seckill">
		select seckill_id,name,number,start_time,end_time,create_time
		from seckill
		order by create_time desc
		limit #{offset},#{limit}
	</select>
	<!-- mybatis调用存储过程 -->		
	<select id="killByProcedure" statementType="CALLABLE">
		call execute_seckill(
			#{seckillId,jdbcType=BIGINT,mode=IN},
			#{phone,jdbcType=BIGINT,mode=IN},
			#{killTime,jdbcType=TIMESTAMP,mode=IN},
			#{result,jdbcType=INTEGER,mode=OUT}
		)
		
		
	</select>
</mapper>
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
    PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
    "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.wentjiang.dao.SuccessKilledDao">
	<insert id="insertSuccessKilled">
		<!-- 忽略主键冲突,报错 -->
		insert ignore into success_killed(seckill_id,user_phone,state)
		values (#{seckillId},#{userPhone},0)
	</insert>
	<!-- 用到了ognl表达式 -->
	<select id="queryByIdWithSeckill" resultType="SuccessKilled">
		<!-- 可以自由控制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.number",
		  	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>
  1. 配置mybatis,新建一个mybatis-config.xml文件:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
  PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
  "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
 	<!-- 设置全局属性 -->
 	<settings>
 		<!-- 使用jdbc的getGeneratedkeys获取数据库自增主键值 -->
 		<setting name="useGeneratedKeys" value="true"></setting>
 		<!-- 使用列别名替换列名 默认为:true -->		
 		<setting name="useColumnLabel" value="true"></setting>
 		<!-- 开启驼峰命名转换 -->	
 		<setting name="mapUnderscoreToCamelCase" value="true"></setting>
 	</settings>
 	
</configuration>


  1. 配置数据库文件:jdbc.properties。这个文件主要是存放jdbc连接数据库的一些属性的,通过nameplace方式导入文件:
    将会在spring-dao.xml中用下面这句话导入使用:

<context:property-placeholder location=“classpath:jdbc.properties”/>

可以在xml中通过el表达式获取属性值,如下:

${password}

driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql://127.0.0.1:3306/seckill?useUnicode=true&characterEncoding=utf8
jdbc.username=root
password=123456
  1. 将mybatis整合到spring中,通过在resource下新建一个spring包,专门存放spring的相关配置文件。我们先新建spring-dao.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/context
    http://www.springframework.org/schema/context/spring-context-4.0.xsd
	http://www.springframework.org/schema/beans 
	http://www.springframework.org/schema/beans/spring-beans.xsd">
	<!--配置整合mybatis过程-->
	<!--1.配置数据库相关参数-->
	<context:property-placeholder location="classpath:jdbc.properties"/>
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
	<property name="driverClass" value="${driverClassName}"/>
	<property name="jdbcUrl" value="${url}"/>
	<property name="user" value="${jdbc.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="10000"/>
	<!-- 获取连接重试次数 -->
	<property name="acquireRetryAttempts" value="3"/>
</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="com.wentjiang.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="com.wentjiang.dao"/>
    </bean>
    
	<!-- 注入redisdao -->
	<bean id="redisDao" class="com.wentjiang.dao.cache.RedisDao">
		<constructor-arg index="0" value="localhost"/>
		<constructor-arg index="1" value="6379"/>
	</bean>
	
	
</beans>

配置方面的话,dao层的配置结束了,接下来是service的功能实现:

Service

流程:
  1. 创建业务接口包及定义好接口。创建接口时注意:创建接口考虑使用者角度,即:使用者怎么才可以很方便的使用我定义出的这个接口。同时,注意创建的接口
  • 参数要简洁,
  • 粒度要够明确,
  • 返回类型要友好(不要是一个map,或者一个entity这样的数据,里面有很多数据其实前端用不到的,所以直接定义一个dto最好。)
    如下:
    在这里插入图片描述
    代码实现:
package com.wentjiang.service;

import java.util.List;

import com.wentjiang.dto.Exposer;
import com.wentjiang.dto.SeckillExecution;
import com.wentjiang.entity.Seckill;
import com.wentjiang.exception.RepeatKillexception;
import com.wentjiang.exception.SeckillException;

/**
 * 业务接口:站在"使用者"的角度设计接口
 * 三个方面:方法定义粒度,参数,返回类型(return 类型/异常)
 * @author jiangwentao
 * @date
 */
public interface SeckillService {
	/**
	 * 查询所有秒杀记录
	 * @return
	 * List<Seckill>
	 *
	 */
	List<Seckill> getSeckillList();
	/**
	 * 查询单个秒杀记录
	 * @param seckillId
	 * @return
	 * Seckill
	 *
	 */
	Seckill getById(long seckillId);
	
	/**
	 * 秒杀开启时,输出秒杀接口地址,否则输出系统时间和秒杀时间
	 * @param seckillId
	 * void
	 */
	Exposer exportSeckillUrl(long seckillId);
	/**
	 * 执行秒杀操作
	 * @param seckillId
	 * @param userPhone
	 * @param md5
	 * void
	 *
	 */
	SeckillExecution executeSeckill(long seckillId,long userPhone,String md5) 
	throws SeckillException,RepeatKillexception,SeckillException;
	/**
	 * 存储过程执行秒杀
	 * @param seckillId
	 * @param userPhone
	 * @param md5
	 * @return
	 * SeckillExecution
	 *
	 */
	SeckillExecution executeSeckillProcedure(long seckillId,long userPhone,String md5);
}

  1. dto的包创建和类的定义,与前端交互时使用。现在service其实只要用到两个,但是之后的SeckillResult也要用上,所以代码先给出来:如图:
    在这里插入图片描述
package com.wentjiang.dto;
/**
 * 暴露秒杀接口
 * @author jiangwentao
 * @date
 */
public class Exposer {
	//是否开启秒杀
	private boolean exposed;
	//一种加密措施
	private String md5;
	
	private long seckillId;
	//系统当前时间
	private long now;
	//开启时间
	private long start;
	//结束时间
	private long end;

	public Exposer(boolean exposed, String md5, long seckillId) {
		super();
		this.exposed = exposed;
		this.md5 = md5;
		this.seckillId = seckillId;
	}

	public Exposer(boolean exposed,long seckillId, long now, long start, long end) {
		super();
		this.exposed = exposed;
		this.seckillId = seckillId;
		this.now = now;
		this.start = start;
		this.end = end;
	}

	public Exposer(boolean exposed, long seckillId) {
		super();
		this.exposed = exposed;
		this.seckillId = seckillId;
	}

	public boolean isExposed() {
		return exposed;
	}

	public void setExposed(boolean exposed) {
		this.exposed = exposed;
	}

	public String getMd5() {
		return md5;
	}

	public void setMd5(String md5) {
		this.md5 = md5;
	}

	public long getSeckillId() {
		return seckillId;
	}

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

	public long getNow() {
		return now;
	}

	public void setNow(long now) {
		this.now = now;
	}

	public long getStart() {
		return start;
	}

	public void setStart(long start) {
		this.start = start;
	}

	public long getEnd() {
		return end;
	}

	public void setEnd(long end) {
		this.end = end;
	}

	@Override
	public String toString() {
		return "Exposer [exposed=" + exposed + ", md5=" + md5 + ", seckillId=" + seckillId + ", now=" + now + ", start="
				+ start + ", end=" + end + "]";
	}
	
	
}

package com.wentjiang.dto;

import com.wentjiang.entity.SuccessKilled;
import com.wentjiang.enums.SeckillStateEnum;

/**
 * 封装秒杀执行后的结果
 * @author jiangwentao
 * @date
 */
public class SeckillExecution {
	private long seckillId;
	//秒杀执行结果
	private int state;
	//状态标识
	private String stateInfo;
	//秒杀成功对象
	private SuccessKilled successKilled;
	
	
	public SeckillExecution(long seckillId, SeckillStateEnum stateEnum, SuccessKilled successKilled) {
		super();
		this.seckillId = seckillId;
		this.state = stateEnum.getState();
		this.stateInfo = stateEnum.getStateInfo();
		this.successKilled = successKilled;
	}
	
	
	public SeckillExecution(long seckillId, SeckillStateEnum stateEnum) {
		super();
		this.seckillId = seckillId;
		this.state = stateEnum.getState();
		this.stateInfo = stateEnum.getStateInfo();
	}


	public long getSeckillId() {
		return seckillId;
	}
	public void setSeckillId(long seckillId) {
		this.seckillId = seckillId;
	}
	public int getState() {
		return state;
	}
	public void setState(int state) {
		this.state = state;
	}
	public String getStateInfo() {
		return stateInfo;
	}
	public void setStateInfo(String stateInfo) {
		this.stateInfo = stateInfo;
	}
	public SuccessKilled getSuccessKilled() {
		return successKilled;
	}
	public void setSuccessKilled(SuccessKilled successKilled) {
		this.successKilled = successKilled;
	}
	
	
}

package com.wentjiang.dto;
/**
 * 封装json结果
 * @author jiangwentao
 * @date
 * @param <T>
 */
// 所有的ajax请求返回结果
public class SeckillResult<T> {
	private boolean success;
	private T data;
	private String error;
	public SeckillResult(boolean success, T data, String error) {
		super();
		this.success = success;
		this.data = data;
		this.error = error;
	}
	public SeckillResult(boolean success, String error) {
		super();
		this.success = success;
		this.error = error;
	}
	public SeckillResult(boolean success, T data) {
		super();
		this.success = success;
		this.data = data;
	}
	public boolean isSuccess() {
		return success;
	}
	public void setSuccess(boolean success) {
		this.success = success;
	}
	public T getData() {
		return data;
	}
	public void setData(T data) {
		this.data = data;
	}
	public String getError() {
		return error;
	}
	public void setError(String error) {
		this.error = error;
	}
	
}

  1. exception包的创建和定义。错误类也要特别创建而不是直接用exception的原因是为了让使用者知道,我用这个接口可能会抛出什么错误,方便使用者debug。如图:
    在这里插入图片描述
    代码如下:
package com.wentjiang.exception;
/**
 * 重复秒杀异常(运行时异常)
 * @author jiangwentao
 * @date
 */
public class RepeatKillexception extends SeckillException{

	/**
	 * 
	 */
	private static final long serialVersionUID = 2748866953891675190L;

	public RepeatKillexception(String message, Throwable cause) {
		super(message, cause);
		// TODO Auto-generated constructor stub
	}

	public RepeatKillexception(String message) {
		super(message);
		// TODO Auto-generated constructor stub
	}
	 
}

package com.wentjiang.exception;
/**
 * 秒杀关闭异常
 * @author jiangwentao
 * @date
 */
public class SeckillCloseException extends SeckillException{

	/**
	 * 
	 */
	private static final long serialVersionUID = 1599701448918339432L;

	public SeckillCloseException(String message, Throwable cause) {
		super(message, cause);
	}

	public SeckillCloseException(String message) {
		super(message);
	}
	
	
}

package com.wentjiang.exception;
/**
 * 秒杀相关异常
 * @author jiangwentao
 * @date
 */
public class SeckillException  extends RuntimeException{

	/**
	 * 
	 */
	private static final long serialVersionUID = 2662954426207673975L;

	public SeckillException(String message, Throwable cause) {
		super(message, cause);
	}

	public SeckillException(String message) {
		super(message);
	}
	
}

  1. 接口的实现,创建impl包,并且实现service接口。
    实现类
package com.wentjiang.service.impl;

import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.collections.MapUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.DigestUtils;
import org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor;

import com.wentjiang.dao.SeckillDao;
import com.wentjiang.dao.SuccessKilledDao;
import com.wentjiang.dao.cache.RedisDao;
import com.wentjiang.dto.Exposer;
import com.wentjiang.dto.SeckillExecution;
import com.wentjiang.entity.Seckill;
import com.wentjiang.entity.SuccessKilled;
import com.wentjiang.enums.SeckillStateEnum;
import com.wentjiang.exception.RepeatKillexception;
import com.wentjiang.exception.SeckillCloseException;
import com.wentjiang.exception.SeckillException;
import com.wentjiang.service.SeckillService;

@Service
public class SeckillServiceImpl implements SeckillService {
	private Logger logger = LoggerFactory.getLogger(this.getClass());
	@Autowired
	private RedisDao redisDao;
	@Autowired // @resource,@inject
	private SeckillDao seckillDao;
	@Autowired
	private SuccessKilledDao successKilledDao;
	// md5盐值字符串,用语混淆
	private final String slat = "jiangwentao";

	@Override
	public List<Seckill> getSeckillList() {
		return seckillDao.queryAll(0, 10);
	}

	@Override
	public Seckill getById(long seckillId) {
		return seckillDao.queryById(seckillId);
	}

	@Override
	public Exposer exportSeckillUrl(long seckillId) {
		// 优化点:缓存优化:超时的基础上维护一致性

		// 1:访问redis
		Seckill seckill = redisDao.getSeckill(seckillId);
		if (seckill == null) {
			// 2.访问数据库
			seckill = seckillDao.queryById(seckillId);
			if (seckill == null) {
				return new Exposer(false, seckillId);
			} else {
				// 3.放入redis
				redisDao.putSeckill(seckill);
			}
		}

		Date startTime = seckill.getStartTime();
		Date endTime = seckill.getEndTime();
		// 获取系统当前时间
		Date nowTime = new Date();

		if (nowTime.getTime() < startTime.getTime() || nowTime.getTime() > endTime.getTime()) {
			return new Exposer(false, seckillId, nowTime.getTime(), startTime.getTime(), endTime.getTime());
		}
		// 转化特定字符串的过程,不可逆
		String md5 = getMD5(seckillId);

		return new Exposer(true, md5, seckillId);
	}

	@Override
	@Transactional
	/**
	 * 使用注解控制事务方法的优点 1:开发团队达成一致的约定,明确标注事务方法的编程风格
	 * 2:保证事务方法的执行时间尽可能短,不要穿插其他的网络操作rpc/http请求,需要的话剥离到方法外
	 * 3:不是所有的方法都需要事务,如只有一条修改操作,或者只读操作,不需要事务控制
	 */
	public SeckillExecution executeSeckill(long seckillId, long userPhone, String md5)
			throws SeckillException, RepeatKillexception, SeckillException {
		if (md5 == null || !md5.equals(getMD5(seckillId))) {
			throw new SeckillException("seckill data rewrite");
		}
		try {
			// 执行秒杀逻辑:减库存+记录购买行为
			Date nowTime = new Date();
			// 记录购买行为
			int insertCount = successKilledDao.insertSuccessKilled(seckillId, userPhone);
			// 唯一:主键
			if (insertCount <= 0) {
				// 重复秒杀
				throw new RepeatKillexception("seckill repeated");
			} else {
				// 减库存,热点商品竞争
				int updateCount = seckillDao.reduceNumber(seckillId, nowTime);
				if (updateCount <= 0) {
					// 没有更新到记录,秒杀结束 rollback
					throw new SeckillCloseException("seckill is closed");
				} else {
					// 秒杀成功 commit
					SuccessKilled successKilled = successKilledDao.queryByIdWithSeckill(seckillId, userPhone);
					return new SeckillExecution(seckillId, SeckillStateEnum.SUCCESS, successKilled);
				}
			}

		} catch (SeckillCloseException e1) {
			throw e1;
		} catch (RepeatKillexception e2) {
			throw e2;
		} catch (Exception e) {
			logger.error(e.getMessage(), e);
			// 所有编译起异常,转化为运行期异常
			throw new SeckillException("seckill inner error:" + e.getMessage());
		}
	}

	private String getMD5(long seckillId) {
		String base = seckillId + "/" + slat;
		String md5 = DigestUtils.md5DigestAsHex(base.getBytes());
		return md5;
	}

	@Override
	public SeckillExecution executeSeckillProcedure(long seckillId, long userPhone, String md5) {
		if (md5 == null || !md5.equals(getMD5(seckillId))) {
			return new SeckillExecution(seckillId,SeckillStateEnum.DATA_REWRITE);
		}
		Date killTime = new Date();
		Map<String, Object> map = new HashMap<>();
		map.put("seckillId", seckillId);
		map.put("phone", userPhone);
		map.put("killTime", killTime);
		map.put("result", null);
		//存储过程执行完之后result被赋值
		try {
			seckillDao.killByProcedure(map);
			//获取result
			int result = MapUtils.getInteger(map, "result",-2);
			if (result==1) {
				SuccessKilled sk = successKilledDao.
						queryByIdWithSeckill(seckillId, userPhone);
				return new SeckillExecution(seckillId,SeckillStateEnum.SUCCESS,sk);
			}else {
				return new SeckillExecution(seckillId, SeckillStateEnum.stateOf(result));
			}
		} catch (Exception e) {
			logger.error(e.getMessage(), e);
			return new SeckillExecution(seckillId, SeckillStateEnum.INNER_ERROR);
		}
	}

}

  1. 上述1-3是准备工作,4是主要工作。这个流程,基本实现了业务逻辑。之后是配置了,用spring去管理service层的类,实现控制反转(ioc)。
配置spring来托管service层
  1. 为什么要用spring ioc?
    spring ioc的四个优点:
  • 对象统一托管,不用再一个一个new出来了。
  • 生命周期的控制,可以再init()或者destroy()阶段对于已经生成的实例进行操控管理,比如修改属性值之类的
  • 可以通过注解@service来灵活注入
  • 一致获得获取对象。(这句话,说实话有些不太可以理解)
  1. 配置方式:
  • 在classpath:spring(就是resource的spring下)下创建一个spring-service.xml文件,该文件中配置package-scan和事务管理器注入数据库连接池。如图:
    在这里插入图片描述
    配置文件代码:
<?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/context
    http://www.springframework.org/schema/context/spring-context-4.0.xsd
	http://www.springframework.org/schema/beans 
	http://www.springframework.org/schema/beans/spring-beans.xsd
	http://www.springframework.org/schema/tx 
	http://www.springframework.org/schema/tx/spring-tx.xsd
	"> 
		<!-- 扫描service包下所有使用注解的类型 -->
		<context:component-scan base-package="com.wentjiang.service"></context:component-scan>
		
		<!-- 配置事务管理器 -->
		<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
			<!-- 注入数据库的连接池 -->
			<property name="dataSource" ref="dataSource"></property>
		</bean>
		<!-- 配置基于注解的声明式事务  默认使用注解来管理事务行为-->
		<tx:annotation-driven transaction-manager="transactionManager" />
</beans>
  • 同时,我们用到了日志功能,所以配置一下日记:logback.xml。因为logback的配置文件会被servlet自动加载,所以就不用配置在什么目录下了。其他的,比如spring的配置文件,我们都放在了spring文件夹下,就是为了后续servlet扫描的时候可以快速找到位置。如图:
    在这里插入图片描述
    代码:
<?xml version="1.0" encoding="UTF-8"?>
<configuration>

  <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
    <layout class="ch.qos.logback.classic.PatternLayout">
      <Pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</Pattern>
    </layout>
  </appender>
  
  <root level="debug">
    <appender-ref ref="STDOUT" />
  </root>
</configuration>
  • 至此,service的功能也基本实现了。
声明式事务
  1. 定义:科普一下声明式事务:就是对数据库更改的操作(会调用dao层方法的,当然,dao层我们一般不会去添加事务管理,而是在service层中添加事务管理),我们封装起来,然后对里面的操作进行管理,这一功能我们叫做事务管理。
  2. 作用:操作数据库的时候,万一产生了bug,系统执行sql语句到了一半,此时数据库中的数据一半被改,一半没有。那么,这肯定是不行的。我们此时可以通过事务管理,将刚刚的修改撤销,而这个功能就叫做”回滚“。这就是事务管理的意义。当然,对于执行顺利,没有出bug的修改数据库行为,事务管理会允许这个行为发生,不再监控,那就叫”提交“。如此一来,spring容器管理之下,事务的执行会大大增加程序的鲁棒性。
  3. 用法:
  • xml+proxyFactoryBean
  • 命名空间tx:advice aop.(这个命名空间是在spring-service.xml中配置的一个属性,我们叫这种属性叫做命名空间)
  • 注解:@transaction。这个注解用在哪个方法上,那么那个方法就会被事务管理器监视。
  1. 配置:(采用注解方式)
  • spring-service.xml中配置事务管理器,jdbc事务管理器(因为用的是mybatis,mybatis的默认事务管理器是jdbc的),代码如下:在这里插入代码片
  • @transaction。用在方法上,那么那个方法就会被事务管理器监视。注意:注解所标注的方法尽量叫做事务方法,事务方法应该尽可能短,才可以提高效率。还有,一条修改语句的话,就不用事务管理控制了,因为没有回滚的必要。
  1. 注意点:
  • spring-service.xml中的事务管理器所注入的数据库连接池是在spring-dao.xml中配置的,所以,整合了mybatis和spring。虽然两个文件之间没有直接的连接导入语句,但是,当spring容器会自动扫描配置文件,所以,最后这两个文件还是会产生关联,不会出现调用不到的情况。
  • 日志的实现,用来观察程序执行情况(和我们的println观察输出来了解程序有没有出bug是异曲同工的)。日志的配置需要新建固定名称的xml文件,在classpath下新建一个logback.xml,然后配置就好了。配置代码:在这里插入代码片
  • try catch包围的部分不会影响程序的正常流程,所以,这个try catch在junit中还是很有用的。

springMVC(web层)

理论
实现
  1. 先写配置:创建spring-web.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:mvc="http://www.springframework.org/schema/mvc"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.0.xsd
http://www.springframework.org/schema/mvc 
	http://www.springframework.org/schema/mvc/spring-mvc.xsd">
	<!-- 配置springmvc -->
	<!-- 1:开启springmvc注解模式 -->
	<!-- 简化配置: (1)自动注册DefaultAnnotationHandlerMapping AnnotationMethodHandlerAdapter (2)提供一系列:数据绑定,数字和日期的format
		@NumberFormat,@DataTimeFormat xml,json默认读写支持 -->
	<mvc:annotation-driven/>
	<!-- servlet-mapping 映射路径:"/" -->
	<!-- 静态资源配置 :
		(1):加入对静态资源的处理:js,gif,png
		(2):允许使用"/"做整体映射
	-->
	<mvc:default-servlet-handler/>
	
	<!--3:配置JSP 显示ViewResolver-->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/>
        <property name="prefix" value="/WEB-INF/jsp/"/>
        <property name="suffix" value=".jsp"/>
    </bean>
	
	<!-- 4:扫描web相关的bean -->
	<context:component-scan base-package="com.wentjiang.web"/>
</beans>
  1. 配置web.xml,配置这个文件是为了对servlet进行配置,前面写的这么多配置文件,其实整合到一起都是通过servlet容器读取了这些文件,然后实现整合的。即,读取了配置文件后,这个容器具有了xml配置中的所有属性,所以可以直接互相调用,实现了ssm的整合。
<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">
	<display-name>Archetype Created Web Application</display-name>
	<servlet>
		<servlet-name>seckill-dispatcher</servlet-name>
		<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
		<!-- 配置springmvc需要加载的文件 dao service web 加载顺序 mybatis->spring->springMVC -->
		<init-param>
			<param-name>contextConfigLocation</param-name>
			<param-value>classpath:spring/spring-*.xml</param-value>
		</init-param>
	</servlet>
	<servlet-mapping>
		<servlet-name>seckill-dispatcher</servlet-name>
		<!-- 默认匹配所有请求 -->
		<url-pattern>/</url-pattern>
	</servlet-mapping>

	<filter>
		<filter-name>encodingFilter</filter-name>
		<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
		<init-param>
			<param-name>encoding</param-name>
			<param-value>UTF-8</param-value>
		</init-param>
		<init-param>
			<param-name>forceEncoding</param-name>
			<param-value>true</param-value>
		</init-param>
	</filter>
	<filter-mapping>
		<filter-name>encodingFilter</filter-name>
		<url-pattern>/*</url-pattern>
	</filter-mapping>
</web-app>

  1. 接下来是写代码,新建controller层,当然,这里叫做web层:
package com.wentjiang.web;

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

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.CookieValue;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;

import com.wentjiang.dto.Exposer;
import com.wentjiang.dto.SeckillExecution;
import com.wentjiang.dto.SeckillResult;
import com.wentjiang.entity.Seckill;
import com.wentjiang.enums.SeckillStateEnum;
import com.wentjiang.exception.RepeatKillexception;
import com.wentjiang.exception.SeckillCloseException;
import com.wentjiang.service.SeckillService;

@Controller
@RequestMapping("/seckill")//url:/模块/资源/{id}/细分
public class SeckillController {
	private final Logger logger = LoggerFactory.getLogger(SeckillController.class);
	@Autowired
	private SeckillService seckillService;
	
	@RequestMapping(value="/list",method=RequestMethod.GET,produces={"application/json;charset=UTF-8"})
	public String list(Model model){
		//获取列表
		List<Seckill> list = seckillService.getSeckillList();
		model.addAttribute("list", list);
		return "list";
	}
	
	@RequestMapping(value="/{seckillId}/detail",method=RequestMethod.GET)
	public String detail(@PathVariable("seckillId") Long seckillId,Model model){
		if(seckillId==null){
			return "redirect:/seckill/list";
		}
		Seckill seckill = seckillService.getById(seckillId);
		if (seckill == null) {
			return "forword:/seckill/list";
		}
		model.addAttribute("seckill", seckill);
		return "detail";
	}
	//ajax json
	@ResponseBody
	@RequestMapping(value="/{seckillId}/exposer",
	method=RequestMethod.POST,
	produces={"application/json;charset=UTF-8"})
	public SeckillResult<Exposer> exposer(@PathVariable("seckillId")Long seckillId){
		SeckillResult<Exposer> result;
		try{
		Exposer exposer = seckillService.exportSeckillUrl(seckillId);
		result = new SeckillResult<Exposer>(true, exposer);
		}catch(Exception e){
			logger.error(e.getMessage(),e);
			result = new SeckillResult<Exposer>(false, e.getMessage());
		}
				return result;
	}
	@ResponseBody
	@RequestMapping(value="/{seckillId}/{md5}/execution",
			method=RequestMethod.POST,
			produces={"application/json;charset=UTF-8"})
	public SeckillResult<SeckillExecution> execute(@PathVariable("seckillId")Long seckillId,
		@PathVariable("md5")String md5,
		@CookieValue(value="killPhone",required=false)Long phone){
		//SeckillResult<SeckillExecution> result;
		
		//也可以使用springmvc valid
		if (phone==null) {
			return new SeckillResult<>(false, "未注册");
		}
		try{
		SeckillExecution execution = seckillService.executeSeckillProcedure(seckillId, phone, md5);
		
		return new SeckillResult<SeckillExecution>(true, execution);
		}catch(RepeatKillexception e){
			SeckillExecution execution = new SeckillExecution(seckillId, SeckillStateEnum.REPEAT_KILL);
			return new SeckillResult<SeckillExecution>(true, execution);
		}catch(SeckillCloseException e){
			SeckillExecution execution = new SeckillExecution(seckillId, SeckillStateEnum.END);
			return new SeckillResult<SeckillExecution>(true, execution);
		}catch(Exception e){
			logger.error(e.getMessage(),e);
			SeckillExecution execution = new SeckillExecution(seckillId, SeckillStateEnum.INNER_ERROR);
			return new SeckillResult<SeckillExecution>(true, execution);
		}
	
	}
	@ResponseBody
	@RequestMapping(value="/time/now",method=RequestMethod.GET)
	public SeckillResult<Long> time (){
		Date now = new Date();
		return new SeckillResult<Long>(true, now.getTime());
	}
}

  1. 代码实现以后,controller的跳转功能也完成了。至此,从读取数据库,到mybatis实现的类被spring加载管理,实现spring和mybatis整合在一起,再到service逻辑功能实现,再到springmvc配置完成,扫描了所有的controller类,最后到spring的所有配置文件被servlet加载,完成ssm的整合。

前端代码实现

  1. 前端的话,根据你的需求,可以前后分离,也可以直接在webapp下写,servlet会去里面的文件中找jsp视图文件。(springmvc完成映射),整个项目的具体代码可以上github拿,前端我就不写了。链接:github源码
待补充知识点
  1. logback的具体用法
  2. try catch的具体用法
  3. spring ioc的四大优点
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值