从零搭建spring boot项目实现乐观锁+重试机制 模拟下单
新建数据库表
新建goods表
DROP TABLE IF EXISTS `goods`;
CREATE TABLE `goods` (
`id` int NOT NULL AUTO_INCREMENT,
`name` varchar(50) DEFAULT NULL,
`stock` int DEFAULT NULL,
`version` int NOT NULL DEFAULT '0',
`update_time` datetime NOT NULL ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
新建goods_order表
DROP TABLE IF EXISTS `goods_order`;
CREATE TABLE `goods_order` (
`id` int NOT NULL AUTO_INCREMENT,
`user_id` bigint NOT NULL,
`goods_id` int NOT NULL,
`add_time` datetime NOT NULL ON UPDATE CURRENT_TIMESTAMP,
`stock` int NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=14 DEFAULT CHARSET=utf8;
搭建spring boot工程
pom.xml引入的依赖
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.4</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.1</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.alibaba/druid -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.3</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
</dependencies>
application.yml属性配置
spring:
datasource:
#MySQL
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://192.168.1.100:3306/zmltestdb?useUnicode=true&characterEncoding=UTF-8&useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=Asia/Shanghai
username: zlyf
password: zlyf@taobaodian
initial-size: 2
max-active: 5
min-idle: 2
max-wait: 6000
pool-prepared-statements: true
max-pool-prepared-statement-per-connection-size: 5
time-between-eviction-runs-millis: 60000
min-evictable-idle-time-millis: 300000
#Oracle需要打开注释
#validation-query: SELECT 1 FROM DUAL
test-while-idle: true
test-on-borrow: false
test-on-return: false
filter:
stat:
log-slow-sql: true
slow-sql-millis: 1000
merge-sql: false
wall:
config:
multi-statement-allow: true
type: com.alibaba.druid.pool.DruidDataSource
mybatis-plus:
mapper-locations: classpath:/mapper/**/*.xml
#实体扫描,多个package用逗号或者分号分隔
typeAliasesPackage: com.minglei.optimistic_lock_demo_two.entity
global-config:
#数据库相关配置
db-config:
#主键类型 AUTO:"数据库ID自增", INPUT:"用户输入ID", ID_WORKER:"全局唯一ID (数字类型唯一ID)", UUID:"全局唯一ID UUID";
id-type: AUTO
banner: false
#原生配置
configuration:
map-underscore-to-camel-case: true
cache-enabled: false
call-setters-on-nulls: true
jdbc-type-for-null: 'null'
实体类
goods表实体类
package com.minglei.optimistic_lock_demo_two.entity;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.annotation.Version;
import lombok.Data;
import java.util.Date;
/**
* @Author: 薛定谔的英短
* @Date: 2020/12/7 17:40
* @mail: 18612371148@163.com
*/
@Data
@TableName(value = "goods")
public class goodsEntity {
@TableId
private Integer id;
private String name;
private Integer stock;
@Version
private Integer version;
private Date update_time;
}
goods_order实体类
package com.minglei.optimistic_lock_demo_two.entity;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.annotation.Version;
import lombok.Data;
import java.util.Date;
/**
* @Author: 薛定谔的英短
* @Date: 2020/12/8 14:35
* @mail: 18612371148@163.com
*/
@Data
@TableName(value = "goods_order")
public class orderEntity {
@TableId
private Integer id;
private Long user_id;
private Integer goods_id;
private Date add_time;
private Integer stock;
}
创建dao
goodsDao
package com.minglei.optimistic_lock_demo_two.dao;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.minglei.optimistic_lock_demo_two.dto.goods.goodsUpdateDto;
import com.minglei.optimistic_lock_demo_two.entity.goodsEntity;
import org.apache.ibatis.annotations.Mapper;
/**
* @Author: 薛定谔的英短
* @Date: 2020/12/7 17:42
* @mail: 18612371148@163.com
*/
@Mapper
public interface goodsDao extends BaseMapper<goodsEntity> {
Integer updateGoods(goodsUpdateDto params);
}
orderDao
package com.minglei.optimistic_lock_demo_two.dao;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.minglei.optimistic_lock_demo_two.entity.orderEntity;
import org.apache.ibatis.annotations.Mapper;
/**
* @Author: 薛定谔的英短
* @Date: 2020/12/8 14:34
* @mail: 18612371148@163.com
*/
@Mapper
public interface orderDao extends BaseMapper<orderEntity> {
}
Service
goodsService
package com.minglei.optimistic_lock_demo_two.service;
import com.minglei.optimistic_lock_demo_two.dto.goods.goodsUpdateDto;
import com.minglei.optimistic_lock_demo_two.exceptionCustom.retryException;
/**
* @Author: 薛定谔的英短
* @Date: 2020/12/8 10:17
* @mail: 18612371148@163.com
*/
public interface goodsService {
/**
* 测试购买商品扣减库存
* @param params
* @return
*/
Integer buyGoods(goodsUpdateDto params) throws retryException;
}
goodsServiceImpl 实现类
package com.minglei.optimistic_lock_demo_two.service.impl;
import com.minglei.optimistic_lock_demo_two.annotationCustom.retry;
import com.minglei.optimistic_lock_demo_two.dao.goodsDao;
import com.minglei.optimistic_lock_demo_two.dao.orderDao;
import com.minglei.optimistic_lock_demo_two.dto.goods.goodsUpdateDto;
import com.minglei.optimistic_lock_demo_two.entity.orderEntity;
import com.minglei.optimistic_lock_demo_two.exceptionCustom.retryException;
import com.minglei.optimistic_lock_demo_two.service.goodsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Transactional;
import java.util.Date;
/**
* @Author: 薛定谔的英短
* @Date: 2020/12/8 10:23
* @mail: 18612371148@163.com
*/
@Service
public class goodsServiceImpl implements goodsService {
@Autowired
goodsDao goodsDao;
@Autowired
orderDao orderDao;
/**
* 测试购买商品扣减库存
*
* @param params
* @return
*/
@Override
@Transactional(isolation = Isolation.READ_COMMITTED)
@retry
public Integer buyGoods(goodsUpdateDto params) throws retryException {
Integer integer = goodsDao.updateGoods(params);
if(integer>0){
//新增订单
orderEntity orderEntity=new orderEntity();
orderEntity.setAdd_time(new Date());
orderEntity.setGoods_id(params.getId());
orderEntity.setStock(params.getStock());
orderEntity.setUser_id(1L);
int insert = orderDao.insert(orderEntity);
}else {
//重试
System.out.println("重试ing...");
throw new retryException("Retrying....");
}
return integer;
}
}
Mapper XML文件
goodsDao.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.minglei.optimistic_lock_demo_two.dao.goodsDao">
<resultMap type="com.minglei.optimistic_lock_demo_two.entity.goodsEntity" id="goodsMap">
<result property="id" column="id"/>
<result property="name" column="name"/>
<result property="stock" column="stock"/>
<result property="version" column="version"/>
<result property="update_time" column="update_time"/>
</resultMap>
<update id="updateGoods" parameterType="com.minglei.optimistic_lock_demo_two.dto.goods.goodsUpdateDto">
UPDATE goods
SET version = version + 1,
stock = stock - #{stock}
WHERE
version = #{version}
AND id = #{id}
AND stock - #{stock} >= 0
</update>
</mapper>
控制器
package com.minglei.optimistic_lock_demo_two.controller;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.minglei.optimistic_lock_demo_two.dao.goodsDao;
import com.minglei.optimistic_lock_demo_two.dto.goods.goodsUpdateDto;
import com.minglei.optimistic_lock_demo_two.entity.goodsEntity;
import com.minglei.optimistic_lock_demo_two.exceptionCustom.retryException;
import com.minglei.optimistic_lock_demo_two.service.goodsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
/**
* @Author: 薛定谔的英短
* @Date: 2020/12/7 17:52
* @mail: 18612371148@163.com
*/
@RestController
@RequestMapping("test")
public class TestController {
@Autowired
goodsDao goodsDao;
@Autowired
goodsService goodsService;
@GetMapping("hello")
public String hello() throws retryException {
QueryWrapper<goodsEntity> wrapper=new QueryWrapper<>();
wrapper.eq("id",1);
goodsEntity goodsEntity = goodsDao.selectOne(wrapper);
goodsUpdateDto params=new goodsUpdateDto();
params.setId(1);
params.setStock(10);
params.setVersion(goodsEntity.getVersion());
Integer integer = goodsService.buyGoods(params);
System.out.println("结果是:"+integer);
if(integer>0){
return "下单成功";
}else {
return "下单失败";
}
}
}
自定义异常类
package com.minglei.optimistic_lock_demo_two.exceptionCustom;
/**
* @Author: 薛定谔的英短
* @Date: 2020/12/8 10:53
* @mail: 18612371148@163.com
*/
public class retryException extends Exception {
public retryException(String msg){
super(msg);
}
}
核心模块-自定义注解
package com.minglei.optimistic_lock_demo_two.annotationCustom;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @Author: 薛定谔的英短
* @Date: 2020/12/8 11:06
* @mail: 18612371148@163.com
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface retry {
/**
* 默认重试3次
* @return
*/
int value() default 3;
}
核心模块-自定义切面类
package com.minglei.optimistic_lock_demo_two.aspectCustom;
import com.minglei.optimistic_lock_demo_two.annotationCustom.retry;
import com.minglei.optimistic_lock_demo_two.exceptionCustom.retryException;
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.stereotype.Component;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.interceptor.TransactionAspectSupport;
/**
* @Author: 薛定谔的英短
* @Date: 2020/12/8 11:21
* @mail: 18612371148@163.com
*/
@Aspect
@Component
public class retryAspect {
@Pointcut("@annotation(com.minglei.optimistic_lock_demo_two.annotationCustom.retry)")
public void retryPointCut(){
}
@Around("retryPointCut() && @annotation(retry)")
@Transactional(isolation = Isolation.READ_COMMITTED)
public Object tryAgain(ProceedingJoinPoint joinPoint,retry retry) throws Throwable {
int count=0;
do {
count++;
try{
System.out.println("重试次数:"+count);
return joinPoint.proceed();
} catch (retryException throwable) {
if(count>retry.value()){
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
return 0;
}
}
}
while (true);
}
}
JMeter测试
模拟5000用户下单
条件:库存数量100、每次下单10个库存
执行结果:
商品库存余量:
订单数量:
测试结束:库存为0、订单数量为10.