完整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.

项目整体结构

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值