在 Spring 中, 事务有两种实现方式:
编程式事务管理: 编程式事务管理使用底层源码可实现更细粒度的事务控制. spring 推荐使用 TransactionTemplate, 典型的模板模式.
申明式事务管理: 添加 @Transactional 注解, 并定义传播机制 + 回滚策略. 基于 Spring AOP 实现, 本质是对方法前后进行拦截,
方法开始之前创建或者加入一个事务, 在执行完目标方法之后根据执行情况提交或者回滚事务.
关于 spring 事务实现方式:
引用博文: https://www.cnblogs.com/dennyzhangdd/p/9708499.html
关于分布式锁的实现方式:
引用博文: https://www.cnblogs.com/dennyzhangdd/p/7133653.html
提供一个具体实例来说明如何使用 spring 事务
基于数据库锁实现
1. 悲观锁: select for update(一致性锁定读)
查询官方文档如上图, 事务内起作用的行锁. 能够保证当前 session 事务所锁定的行不会被其他 session 所修改(这里的修改指更新或者删除).
对读取的记录加 X 锁, 即排它锁, 其他事不能对上锁的行加任何锁.
BEGIN;(确保以下 2 步骤在一个事务中:)
SELECT * FROM tb_product_stock WHERE product_id=1 FOR UPDATE--->product_id 有索引, 锁行. 加锁
(注: 条件字段必须有索引才能锁行, 否则锁表, 且最好用 explain 查看一下是否使用了索引, 因为有一些会被优化掉最终没有使用索引)
UPDATE tb_product_stock SET number=number-1 WHERE product_id=1--->更新库存 - 1. 解锁
COMMIT;
2. 乐观锁: 版本控制
选一个字段作为版本控制字段, 更新前查询一次, 更新时该字段作为更新条件.
不同业务场景, 版本控制字段, 可以 0 1 控制, 也可以 + 1 控制, 也可以 - 1 控制, 这个随意.
BEGIN;(确保以下 2 步骤在一个事务中:)
SELECT number FROM tb_product_stock WHERE product_id=1--》查询库存总数, 不加锁
UPDATE tb_product_stock SET number=number-1 WHERE product_id=1 AND number = 第一步查询到的库存数 --》number 字段作为版本控制字段
COMMIT;
场景举例:
卖商品, 先查询库存 > 0, 更新库存 - 1.
例如: 一种商品, 有两件库存, 多人来下单买, 一个人一次只能买一件商品
创建表 biz_shoe
- CREATE TABLE `biz_shoe` (
- `shoe_uuid` char(32) NOT NULL,
- `inventory_number` int(11) DEFAULT NULL COMMENT '库存数量',
- `is_putaway` int(1) DEFAULT NULL COMMENT '是否上架',
- PRIMARY KEY (`shoe_uuid`)
- ) ENGINE=InnoDB DEFAULT CHARSET=utf8
给库存赋值, 如图
3. 基于悲观锁实现
- /**
- * 悲观锁
- * @param shoe
- * @return
- */
- @Transactional(添加 spring 事务注解)
- @Override
- public Integer vieShoe(BizShoe shoe) {
- // 抢单商品加悲观锁
- BizShoe modleShoe = shoeMapper.lockForUpdate(shoe.getShoeUuid());
- // 商品库存
- Integer number = modleShoe.getInventoryNumber();
- System.out.println("库存:" + number);
- System.out.println("线程名称:" + Thread.currentThread().getName());
- if (number != 0) {
- number = number - 1;
- System.out.println("剩余库存:" + number);
- modleShoe.setInventoryNumber(number);
- shoeMapper.updateByPrimaryKeySelective(modleShoe);
- } else {
- AssertUtil.isTrue(number == 0, StatusCode.NumberIsNull, StatusCode.NumberIsNull.getMessage());
- }
- return number;
- }
在 mapping 中具体 sql 语句
- <select id="lockForUpdate" parameterType="java.lang.String" resultMap="BaseResultMap">
- SELECT <include refid="Base_Column_List" /> FROM `biz_shoe` WHERE shoe_uuid = #{shoeUuid,jdbcType=CHAR} FOR UPDATE;
- </select>
4. 基于乐观锁
- /**
- * 乐观锁
- * @param shoe
- * @return
- */
- @Transactional
- @Override
- public Integer vieShoe(BizShoe shoe) {
- // 抢单商品加乐观锁
- BizShoe modleShoe = shoeMapper.selectByPrimaryKey(shoe.getShoeUuid());
- // 商品库存
- Integer number = modleShoe.getInventoryNumber();
- System.out.println("库存:" + number);
- System.out.println("线程名称:" + Thread.currentThread().getName());
- if(number>0){
- int susus = shoeMapper.updateByShoeUuidInventoryNumber(shoe.getShoeUuid(),number);
- System.out.println("更新成功"+susus);
- if(susus==1){// 更新成功才算抢单成功
- // 商品库存
- BizShoe modleShoeNew = shoeMapper.selectByPrimaryKey(shoe.getShoeUuid());
- System.out.println("新库存:" + modleShoeNew.getInventoryNumber());
- }else{
- AssertUtil.isTrue(true, StatusCode.NumberIsNull, StatusCode.NumberIsNull.getMessage());
- }
- }else {
- AssertUtil.isTrue(true, StatusCode.NumberIsNull, StatusCode.NumberIsNull.getMessage());
- }
- return number;
- }
sql 语句
- <update id="updateByShoeUuidInventoryNumber" parameterType="com.zstax.springtest.bean.BizShoe">
- UPDATE biz_shoe SET inventory_number=inventory_number-1 WHERE
- shoe_uuid=#{shoeUuid,jdbcType=CHAR} and inventory_number=#{InventoryNumber,jdbcType=INTEGER}
- </update>
来源: http://www.bubuko.com/infodetail-2947279.html