1、开发环境

模拟抢红包的场景(出现超发的现象)

1、开发环境

MyEclipse10、tomcat7、ssm(spring4、springmvc4、mybatis3)、mysql5

2、结构图

在这里插入图片描述

3、抢红包的数据表

create database chapter22;

use chapter22;
/*==============================================================*/
/* Table: 红包表                                        */
/*==============================================================*/
create table T_RED_PACKET
(
   id                   int(12)                        not null auto_increment,
   user_id              int(12)                        not null,
   amount               decimal(16,2)                  not null,
   send_date            timestamp                      not null,
   total                int(12)                        not null,
   unit_amount          decimal(12)                    not null,
   stock                int(12)                        not null,
   version              int(12) default 0              not null,
   note                 varchar(256)                    null,
   primary key clustered (id)
);

/*==============================================================*/
/* Table: 用户抢红包表                                                */
/*==============================================================*/
create table T_USER_RED_PACKET 
(
   id                   int(12)                        not null auto_increment,
   red_packet_id        int(12)                        not null,
   user_id              int(12)                        not null,
   amount               decimal(16,2)                  not null,
   grab_time            timestamp                      not null,
   note                 varchar(256)                   null,
    primary key clustered (id)
);




/**
* 插入一个20万元金额,100个小红包,每个200元的红包数据
  */
  insert into T_RED_PACKET(user_id, amount, send_date, total, unit_amount, stock, note)
   values(1, 200000.00, now(), 20000, 200.00,100,'20万元金额,100个小红包,每个200元');

4、两个pojo类

1)、红包类

package com.ssm.chapter22.pojo;
import java.io.Serializable;
import java.sql.Timestamp;
//实现Serializable接口,这样便可序列化对象
//红包表
public class RedPacket implements Serializable{

	private static final long serialVersionUID = 1L;
	
	private Long id;//红包编号
	private Long userId;//发红包的用户
	private Double amount;//红包的总金额
	private Timestamp sendDate;//发红包的时间
	private Integer total;//小红包的总数
	private Double unitAmount;//单个小红包的金额
	private Integer stock;//剩余小红包的个数
	private Integer version;//版本
	private String note;//备注
	
	public Long getId() {
		return id;
	}
	public void setId(Long id) {
		this.id = id;
	}
	public Long getUserId() {
		return userId;
	}
	public void setUserId(Long userId) {
		this.userId = userId;
	}
	public Double getAmount() {
		return amount;
	}
	public void setAmount(Double amount) {
		this.amount = amount;
	}
	public Timestamp getSendDate() {
		return sendDate;
	}
	public void setSendDate(Timestamp sendDate) {
		this.sendDate = sendDate;
	}
	public Integer getTotal() {
		return total;
	}
	public void setTotal(Integer total) {
		this.total = total;
	}
	public Double getUnitAmount() {
		return unitAmount;
	}
	public void setUnitAmount(Double unitAmount) {
		this.unitAmount = unitAmount;
	}
	public Integer getStock() {
		return stock;
	}
	public void setStock(Integer stock) {
		this.stock = stock;
	}
	public Integer getVersion() {
		return version;
	}
	public void setVersion(Integer version) {
		this.version = version;
	}
	public String getNote() {
		return note;
	}
	public void setNote(String note) {
		this.note = note;
	}
	}

​	
2)、抢红包用户类(抢红包信息)
package com.ssm.chapter22.pojo;
import java.io.Serializable;
import java.sql.Timestamp;
//实现Serializable接口,这样便可序列化对象
//抢红包信息表
public class UserRedPacket implements Serializable{

	private static final long serialVersionUID = 1L;
	
	private Long id;//编号
	private Long redPacketId;//红包编号
	private Long userId;//抢红包用户编号
	private Double amount;//抢红包金额
	private Timestamp grabTime;//抢红包时间
	private String note;//备注
	
	public Long getId() {
		return id;
	}
	public void setId(Long id) {
		this.id = id;
	}
	public Long getRedPacketId() {
		return redPacketId;
	}
	public void setRedPacketId(Long redPacketId) {
		this.redPacketId = redPacketId;
	}
	public Long getUserId() {
		return userId;
	}
	public void setUserId(Long userId) {
		this.userId = userId;
	}
	public Double getAmount() {
		return amount;
	}
	public void setAmount(Double amount) {
		this.amount = amount;
	}
	public Timestamp getGrabTime() {
		return grabTime;
	}
	public void setGrabTime(Timestamp grabTime) {
		this.grabTime = grabTime;
	}
	public String getNote() {
		return note;
	}
	public void setNote(String note) {
		this.note = note;
	}
	}

5、DAO层

1)红包Dao(RedPacketDao.java)

pckage com.ssm.chapter22.dao;
import org.springframework.stereotype.Repository;
import com.ssm.chapter22.pojo.RedPacket;
@Repository
public interface RedPacketDao {

	/**
	 * 获取红包信息.
	 * @param id --红包id
	 * @return 红包具体信息
	 */
	public RedPacket getRedPacket(Long id);
	/**
	 * 扣减抢红包数.
	 * @param id -- 红包id
	 * @return 更新记录条数
	 */
	public int decreaseRedPacket(Long id);
	}

2)、抢红包Dao(UserRedPacket.java)

package com.ssm.chapter22.dao;
import org.springframework.stereotype.Repository;
import com.ssm.chapter22.pojo.UserRedPacket;

@Repository
public interface UserRedPacketDao {

	/**
	 * 插入抢红包信息.
	 * @param userRedPacket ——抢红包信息
	 * @return 影响记录数.
	 */
	public int grapRedPacket(UserRedPacket  userRedPacket);
}

6、映射文件

1)、RedPacket.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.ssm.chapter22.dao.RedPacketDao">
	<!-- 查询红包具体信息 -->
	<select id="getRedPacket" parameterType="long"
		resultType="com.ssm.chapter22.pojo.RedPacket">
		select id, user_id as userId, amount, send_date as
		sendDate, total,
		unit_amount as unitAmount, stock, version, note from
		T_RED_PACKET
		where id = #{id}
	</select>

	<!-- 扣减抢红包库存 -->
	<update id="decreaseRedPacket">
		update T_RED_PACKET set stock = stock - 1 where id =
		#{id}
	</update>

  </mapper>

2)、UserRedPacket.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.ssm.chapter22.dao.UserRedPacketDao">
  <!-- 插入抢红包信息 -->
    <insert id="grapRedPacket" useGeneratedKeys="true" 
    keyProperty="id" parameterType="com.ssm.chapter22.pojo.UserRedPacket">
	    insert into T_USER_RED_PACKET( red_packet_id, user_id, amount, grab_time, note)
	    values (#{redPacketId}, #{userId}, #{amount}, now(), #{note}) 
    </insert>
  </mapper>



  注意:
  1)、这里的隔离级别采用”读/写提交“,之所以不采用更高的隔离级别,主要是提高数据库的并发能力;
  2)、传播行为采用“Propagation.REQUIRED”,这样调用这个方法的时候,如果没有事务会创建事务,如果有事务沿用当前事务。

7、Service层

1)、RedPacketService.java接口
package com.ssm.chapter22.service;

import com.ssm.chapter22.pojo.RedPacket;

public interface RedPacketService {
	/**
	 * 获取红包
	 * @param id ——编号
	 * @return 红包信息
	 */
	public RedPacket getRedPacket(Long id);

	/**
	 * 扣减红包
	 * @param id——编号
	 * @return 影响条数.
	 */
	public int decreaseRedPacket(Long id);
}

2)、UserRedP acketService.java接口

package com.ssm.chapter22.service;

public interface UserRedPacketService {

	/**
	 * 保存抢红包信息.
	 * @param redPacketId 红包编号
	 * @param userId 抢红包用户编号
	 * @return 影响记录数.
	 */
	public int grapRedPacket(Long redPacketId, Long userId);
	
	}

3)、Red PacketServiceImpl.java(接口的实现类)

package com.ssm.chapter22.service.Impl;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import com.ssm.chapter22.dao.RedPacketDao;
import com.ssm.chapter22.pojo.RedPacket;
import com.ssm.chapter22.service.RedPacketService;

@Service
public class RedPacketServiceImple implements RedPacketService {

	@Autowired
	private RedPacketDao  redPacketDao = null;
	
	@Override
	@Transactional(isolation=Isolation.READ_COMMITTED, propagation = Propagation.REQUIRED)
	public RedPacket getRedPacket(Long id) {
		return redPacketDao.getRedPacket(id);
	}
	
	@Override
	@Transactional(isolation=Isolation.READ_COMMITTED, propagation = Propagation.REQUIRED)
	public int decreaseRedPacket(Long id) {
		return redPacketDao.decreaseRedPacket(id);
	}


}

4)、UserRedPacketServiceI mpl.java(接口的实现类)

package com.ssm.chapter22.service.Impl;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import com.ssm.chapter22.dao.RedPacketDao;
import com.ssm.chapter22.dao.UserRedPacketDao;
import com.ssm.chapter22.pojo.RedPacket;
import com.ssm.chapter22.pojo.UserRedPacket;
import com.ssm.chapter22.service.UserRedPacketService;

@Service
public class UserRedPacketServiceImpl implements UserRedPacketService {

	@Autowired
	private UserRedPacketDao userRedPacketDao = null;
	
	@Autowired
	private RedPacketDao redPacketDao = null;
	
	// 失败
	private static final int FAILED = 0;
	
	//grapRedPacket方法的逻辑是:首先获取红包的信息,如果发现红包库存大于0,
	//则说明还有红包可以抢,抢夺红包并生成抢红包的信息将其保存到数据库中。
	@Override
	@Transactional(isolation = Isolation.READ_COMMITTED, propagation = Propagation.REQUIRED)
	public int grapRedPacket(Long redPacketId, Long userId) {
		// 获取红包信息
		 RedPacket redPacket = redPacketDao.getRedPacket(redPacketId);
		// 当前小红包库存大于0
		if (redPacket.getStock() > 0) {
			redPacketDao.decreaseRedPacket(redPacketId);
			// 生成抢红包信息
			UserRedPacket userRedPacket = new UserRedPacket();
			userRedPacket.setRedPacketId(redPacketId);
			userRedPacket.setUserId(userId);
			userRedPacket.setAmount(redPacket.getUnitAmount());
			userRedPacket.setNote("抢红包 " + redPacketId);
			// 插入抢红包信息
			int result = userRedPacketDao.grapRedPacket(userRedPacket);
			return result;
		}
		// 失败返回
		return FAILED;
	}
	
}

注意:

1)、这里的隔离级别采用”读/写提交“,之所以不采用更高的隔离级别,主要是提高数据库的并发能力;

2)、传播行为采用“Propagation.REQUIRED”,这样调用这个方法的时候,如果没有事务会创建事务,如果有事务沿用当前事务。

8、使用全注解方式开发SSM开发环境

1)、首先来配置WebAppInitializer
package com.ssm.chapter22.config;

import javax.servlet.MultipartConfigElement;
import javax.servlet.ServletRegistration.Dynamic;

import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;

public class WebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer{

	// Spring IoC环境配置
		@Override
		protected Class<?>[] getRootConfigClasses() {
			// 配置Spring IoC资源
			return new Class<?>[] { RootConfig.class };
		}
	
		// DispatcherServlet环境配置
		@Override
		protected Class<?>[] getServletConfigClasses() {
			// 加载Java配置类
			return new Class<?>[] { WebConfig.class };
		}
	
		// DispatchServlet拦截请求配置
		@Override
		protected String[] getServletMappings() {
			return new String[] { "*.do" };
		}
	
		/**
		 * @param dynamic
		 *            Servlet上传文件配置.
		 */
		@Override
		protected void customizeRegistration(Dynamic dynamic) {
			// 配置上传文件路径
			String filepath = "e:/mvc/uploads";
			// 5MB
			Long singleMax = (long) (5 * Math.pow(2, 20));
			// 10MB
			Long totalMax = (long) (10 * Math.pow(2, 20));
			// 设置上传文件配置
			dynamic.setMultipartConfig(new MultipartConfigElement(filepath, singleMax, totalMax, 0));
		}

}

2)、RootConfig.java

package com.ssm.chapter22.config;
import java.util.Properties;
import javax.sql.DataSource;
import org.apache.commons.dbcp.BasicDataSourceFactory;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.mapper.MapperScannerConfigurer;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ComponentScan.Filter;
import org.springframework.context.annotation.FilterType;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.stereotype.Repository;
import org.springframework.stereotype.Service;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import org.springframework.transaction.annotation.TransactionManagementConfigurer;

@Configuration
//定义Spring 扫描的包
@ComponentScan(value= "com.*", includeFilters= {@Filter(type = FilterType.ANNOTATION, value ={Service.class})})
//使用事务驱动管理器
@EnableTransactionManagement
//实现接口TransactionManagementConfigurer,这样可以配置注解驱动事务,是为了实现注解式事务
public class RootConfig implements TransactionManagementConfigurer{

private DataSource dataSource = null;
	
	/**
	 * 配置数据库.
	 * @return 数据连接池
	 */
	@Bean(name = "dataSource")
	public DataSource initDataSource() {
		if (dataSource != null) {
			return dataSource;
		}
		Properties props = new Properties();
		props.setProperty("driverClassName", "com.mysql.jdbc.Driver");
		props.setProperty("url", "jdbc:mysql://localhost:3306/chapter22");
		props.setProperty("username", "root");
		props.setProperty("password", "123456");
	   props.setProperty("maxActive", "200");
		props.setProperty("maxIdle", "20");
		props.setProperty("maxWait", "30000");
		try {
			dataSource = BasicDataSourceFactory.createDataSource(props);
		} catch (Exception e) {
			e.printStackTrace();
		}
		return dataSource;
	}
	
	/***
	 * 配置SqlSessionFactoryBean
	 * @return SqlSessionFactoryBean
	 */
	@Bean(name="sqlSessionFactory")
	public SqlSessionFactoryBean initSqlSessionFactory() {
		SqlSessionFactoryBean sqlSessionFactory = new SqlSessionFactoryBean();
		sqlSessionFactory.setDataSource(initDataSource());
		//配置MyBatis配置文件
		Resource resource = new ClassPathResource("mybatis/mybatis-config.xml");
		sqlSessionFactory.setConfigLocation(resource);
		return sqlSessionFactory;
	}
	
	/***
	 * 通过自动扫描,发现MyBatis Mapper接口
	 * @return Mapper扫描器
	 */
	@Bean 
	public MapperScannerConfigurer initMapperScannerConfigurer() {
		MapperScannerConfigurer msc = new MapperScannerConfigurer();
		msc.setBasePackage("com.*");
		msc.setSqlSessionFactoryBeanName("sqlSessionFactory");
		msc.setAnnotationClass(Repository.class);
		return msc;
	}


​	
	/**
	 * 实现接口方法,注册注解事务,当@Transactional 使用的时候产生数据库事务 
	 */
	@Override
	@Bean(name="annotationDrivenTransactionManager")
	public PlatformTransactionManager annotationDrivenTransactionManager() {
		DataSourceTransactionManager transactionManager = 
	       new DataSourceTransactionManager();
		transactionManager.setDataSource(initDataSource());
		return transactionManager;
	}

}

3)、Webconfig.java

  package com.ssm.chapter22.config;
    import java.util.ArrayList;
    import java.util.List;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Controller;

import org.springframework.web.servlet.HandlerAdapter;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;
import org.springframework.web.servlet.view.InternalResourceViewResolver;
import org.springframework.context.annotation.ComponentScan.Filter;
import org.springframework.context.annotation.FilterType;
import org.springframework.http.MediaType;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
@Configuration
//定义Spring MVC扫描的包
@ComponentScan(value="com.*", includeFilters= {@Filter(type = FilterType.ANNOTATION, value = Controller.class)})
//启动Spring MVC配置
@EnableWebMvc
public class WebConfig {
	/***
	 * 通过注解 @Bean 初始化视图解析器
	 * @return ViewResolver 视图解析器
	 */
	@Bean(name="internalResourceViewResolver")
	public ViewResolver initviewResolver(){
		InternalResourceViewResolver viewResolver =new InternalResourceViewResolver();
		viewResolver.setPrefix("/WEB-INF/jsp/");
		viewResolver.setSuffix(".jsp");
		return viewResolver;
	}
	
	/**
	 * 初始化RequestMappingHandlerAdapter,并加载Http的Json转换器
	 * @return  RequestMappingHandlerAdapter 对象
	 */
	@Bean(name="requestMappingHandlerAdapter") 
	public HandlerAdapter initRequestMappingHandlerAdapter() {
		//创建RequestMappingHandlerAdapter适配器
		RequestMappingHandlerAdapter rmhd = new RequestMappingHandlerAdapter();
		//HTTP JSON转换器
		MappingJackson2HttpMessageConverter  jsonConverter 
	        = new MappingJackson2HttpMessageConverter();
		//MappingJackson2HttpMessageConverter接收JSON类型消息的转换
		MediaType mediaType = MediaType.APPLICATION_JSON_UTF8;
		List<MediaType> mediaTypes = new ArrayList<MediaType>();
		mediaTypes.add(mediaType);
		//加入转换器的支持类型
		jsonConverter.setSupportedMediaTypes(mediaTypes);
		//往适配器加入json转换器
		rmhd.getMessageConverters().add(jsonConverter);
		return rmhd;
	}
}

9、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>
   <!-- 引入适配器 -->
    <mappers>
        <mapper resource="com/ssm/chapter22/mapper/UserRedPacket.xml"/>
        <mapper resource="com/ssm/chapter22/mapper/RedPacket.xml"/>
    </mappers>
  </configuration>

10、Con troller控制层

package com.ssm.chapter22.controller;

import java.util.HashMap;
import java.util.Map;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import com.ssm.chapter22.service.UserRedPacketService;

@Controller
@RequestMapping("/userRedPacket")
public class UserRedPacketController {


	@Autowired
	private UserRedPacketService userRedPacketService = null;
	
	@RequestMapping(value = "/grapRedPacket")
	@ResponseBody
	public Map<String, Object> grapRedPacket(Long redPacketId,Long userId) {
		// 抢红包
		System.out.println(userId);
		int result = userRedPacketService.grapRedPacket(redPacketId, userId);
		Map<String, Object> retMap = new HashMap<String, Object>();
		boolean flag = result > 0;
		retMap.put("success", flag);
		retMap.put("message", flag ? "抢红包成功" : "抢红包失败");
		return retMap;
	}

}

11、JSP页面

    <%@ page language

="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
<script type="text/javascript" src="https://code.jquery.com/jquery-3.2.0.js"></script>
  <script type="text/javascript">
            $(document).ready(function () {
            	  //模拟200个异步请求,进行并发,总共有100个小红包
                var max = 200;
                for (var i = 1; i <= max; i++) {
                    //jQuery的post请求,请注意这是异步请求
                    $.post({
                        //请求抢id为1的红包
                        //根据自己请求修改对应的url和大红包编号
                        url: "./userRedPacket/grapRedPacket.do?redPacketId=1&userId=" + i,
                        //成功后的方法
                        success: function (result) {
                        }
                    });
                }
            });
        </script>
</head>
<body>

</body>
</html>

12、测试步骤

启动服务器,在浏览器输入:http://localhost:8080/Chapter22/grap.jsp

13、结果

出现超发现象,在6秒内全抢完。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值