【SSM抢红包简单项目】 ---- (二) 后台代码及超发现象

目录

1. pojo层
2. dao层和映射文件
3. service接口和实现类
4. controller层
5. 超发现象

1.pojo层

RedPacket 红包类

// 实现序列化接口,这样就可以序列化对象
// 红包pojo
public class RedPacket implements Serializable {
	private Long id; // 红包编号
	private Long userId; // 发红包用户
	private Double mount; // 红包金额
	private Date sendDate; // 发红包时间
	private Integer total; // 小红包总数
	private Double unitAmount; // 单个小红包金额
	private Integer stock; // 剩余小红包个数
	private Integer version; // 版本
	private String note; // 备注
	/**setter and getter **/
}

UserRedPacket 用户抢红包类

// 用户抢红包表
public class UserRedPacket implements Serializable {
	private Long id; // 编号
	private Long redPacketId; // 用户编号
	private Long userId; // 抢用户编号
	private Double amount; // 抢红包金额
	private Date grabTime; // 抢红包时间
	private String note; // 备注
	/** setter and getter **/
}

2.dao层和映射文件

RedPacketDao

// 红包DAO
@Repository
public interface RedPacketDao {
	/**
	 * 获取红包信息
	 * @param id 红包id
	 * @return 红包具体信息
	 */
	RedPacket getRedPacket(Long id);

	/**
	 * 扣减抢红包数
	 * @param id 红包id
	 * @return 更新记录条数
	 */
	int decreaseRedPacket(Long id);
}

其中两个方法,一个是查询红包,另一个是扣减红包库存。
抢红包逻辑: 先查询红包信息,看其是否拥有存量可以扣减。如果有存量,就扣减它,否则不扣减

UserRedPacketDao

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

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="cn.whc.dao.RedPacketDao">

    <!--查询红包具体信息-->
    <select id="getRedPacket" parameterType="long" resultType="cn.whc.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>

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="cn.whc.dao.UserRedPacketDao">
    <!--使用了useGenerateKeys和keyProperty,返回数据库生成的主键信息-->
    <insert id="grapRedPacket" parameterType="cn.whc.pojo.UserRedPacket" useGeneratedKeys="true" keyProperty="id">
        insert into T_USER_RED_PACKET(red_packet_id, user_id, amount, grab_time, note)
        values (#{redPacketId}, #{userId}, #{amount}, now(), #{note})
    </insert>
</mapper>

3.service接口和实现类

UserRedPacketService

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

UserRedPacketServiceImpl

@Service("userRedPacketService")
public class UserRedPacketServiceImpl implements UserRedPacketService {

	@Autowired
	private UserRedPacketDao userRedPacketDao = null;

	@Autowired
	private RedPacketDao redPacketDao = null;

	// 失败
	private static final int FAILED = 0;

	// 抢红包逻辑: 先获取红包信息,发现红包库存大于0,则抢红包并生成抢红包信息保存到数据库中
	@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;
	}
}

grapRedPacket方法的逻辑是首先获取红包信息,如果发现红包库存大于0,则说明还有红包可抢,抢夺红包并生成抢红包的信息将其保存到数据库中。

配置事务注解@Transactional,让程序在事务中运行,以保证数据的一致性,采用读/写提交的隔离级别。
提高数据库的并发能力,传播行为采用Propagation.REQUIRED
在这里插入图片描述

4.controller层

UserRedPacketController

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

	@Autowired
	private UserRedPacketService userRedPacketService = null;

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

5.超发现象

<%@ page contentType="text/html;charset=UTF-8" language="java" isELIgnored="false" %>
<html>
<head>
    <title>参数</title>
    <%--加载JQuery文件--%>
    <script type="text/javascript" src="${pageContext.request.contextPath}/static/js/jquery-1.7.2.js"></script>

    <script type="text/javascript">

        // 就绪函数,表示当前页面加载完毕后,直接执行里面的代码
        $(function () {
            // 模拟30000个异步请求,进行并发
            var max = 30000;
            for (var i = 1; i <= max; i++) {
                $.ajax({
                    type: 'post',
                    url:"${pageContext.request.contextPath}/userRedPacket/grapRedPacket.do?redPacketId=1&userId=" + i,
                    success:function (result) {

                    }
                })
            }
        })
    </script>
</head>
<body>

</body>
</html>

用js异步发送请求模拟3万人同时抢红包场景,要使用火狐浏览器进行测试(Google测试的话大多请求丢失),抢夺id为1的红包,而数据库中是一个20万元的红包,一共有两万个。
注意两个问题: 数据的一致性和性能问题

  • 数据一致性问题
    在这里插入图片描述出现了高并发的超发现象,两万个小红包,结果发出了20001个红包,现有库存为-1,超出之前的规定。

  • 性能问题
    在这里插入图片描述最后一个红包和第一个红包的时间间隔,性能还是不错的,但是逻辑上存在超发错误,需要解决超发问题。

超发现象是由多线程下数据不一致造成的,对于解决此问题,通过悲观锁和乐观锁来处理,保证数据一致性,这两种方法的性能是不一样的

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值