Springboot:商品库存并发更新,乐观锁失败重试机制

一个商城项目,用户下单时需要更新商品库存,在商品类增加了version字段,增加乐观锁,保证库存数据的线程安全,但是在多个用户同时下单更新库存时可能会导致库存更新失败,因此需要增加乐观锁失败重试机制

一、相关代码

(1)商品实体类

@Data
@ApiModel(value = "Goods对象", description = "Goods对象")
public class Goods {

	@ApiModelProperty(value = "商品名称")
	private String name;
	@ApiModelProperty(value = "商品标题图")
	private String tittleImg;
	@ApiModelProperty(value = "商品数量")
	private Integer quantity;
	@ApiModelProperty(value = "展示价格")
	private BigDecimal price;
	@ApiModelProperty(value = "商品详情")
	private String details;
	@ApiModelProperty(value = "商品分类")
	private String category;
	@ApiModelProperty(value = "版本")
	@Version
	private Integer version;
}

(2)失败重试注解类

import java.lang.annotation.*;

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface FailRetry {
	//重试次数,这里默认15
	int value() default 15;
}

(3)重试切面类

import XXXX.exception.TryAgainException;  //类的路径自行修改
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.interceptor.TransactionAspectSupport;

@Aspect
@Configuration
@Slf4j
public class TryAgainAspect {

	/**
	 * 定义切点
	 */
	@Pointcut("@annotation(com.ht.store.annotation.FailRetry)")
	private void failRetryPointCut() {

	}

	@Around("failRetryPointCut() && @annotation(failRetry)")
	@Transactional(rollbackFor = Exception.class)
	public Object retry(ProceedingJoinPoint joinPoint, FailRetry failRetry) throws Throwable {
		int count = 0;
		do {
			count++;
			try {
				log.info("重试次数:{}", count);
				return joinPoint.proceed();
			} catch (TryAgainException e) {
				if(count >= failRetry.value()){
					TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
					throw new TryAgainException("重试失败");
				}
			}
		} while (true);
	}
}

(3)更新失败异常类

public class TryAgainException extends RuntimeException {
	public TryAgainException(String  e) {
		super(e);
	}
}

(4)库存更新方法,注意方法上加上注解

@Override
@FailRetry
@Transactional(rollbackFor = TryAgainException.class)
public boolean updateGoodsQuantity(Long id,Integer number) {
	Goods goods = this.getById(id);
	goods.setQuantity(goods.getQuantity() - number);
	if (this.updateById(goods)){
		return true;
	}else{
		throw  new TryAgainException("更新异常,版本号不一致");
	}
}

二、执行测试

数据库商品目前库存976,version是23
在这里插入图片描述
调用接口更新库存 -1
Springboot:商品库存并发更新,乐观锁失败重试机制_第1张图片
更新成功了,查看数据库
在这里插入图片描述
修改代码,模拟一次更新失败

@Override
@FailRetry
@Transactional(rollbackFor = TryAgainException.class)
public boolean updateGoodsQuantity(Long id,Integer number) {
	Goods goods = this.getById(id);
	goods.setQuantity(goods.getQuantity() - number);
	goods.setVersion(20);  //此处修改为老版本,肯定更新失败
	if (this.updateById(goods)){
		return true;
	}else{
		throw  new TryAgainException("更新异常,版本号不一致");
	}
}

更新时version是20,而数据库的version是24,会判定为老版本,表示已经被更新过,会更新失败,触发异常,然后去执行重试机制
Springboot:商品库存并发更新,乐观锁失败重试机制_第2张图片
抛出TryAgainException异常,数据回滚,由于方法有@FailRetry注解,进入注解绑定的切面类
Springboot:商品库存并发更新,乐观锁失败重试机制_第3张图片
注解类FailRetry里面默认15次,最多重试15次,结束重试。
Springboot:商品库存并发更新,乐观锁失败重试机制_第4张图片
一般情况下,每次更新前读取最新的,然后减掉库存进行更新,失败的次数不会太多,可以满足日常使用。

你可能感兴趣的:(java,JavaWeb,spring,boot,java,spring,乐观锁,失败重试)