mybatisplus使用OptimisticLockerInnerInterceptor实现版本号乐观锁

目录

OptimisticLockerInnerInterceptor 介绍

创建项目

创建项目

引入依赖

创建数据表

application.yml配置

项目结构

配置乐观锁拦截器

创建实体类

创建mapper

创建service

创建返回包装类BaseResponse

创建UserController

测试

查询

修改

​编辑

修改后再查询 

再次按版本号0修改 

修改时不传入版本号


OptimisticLockerInnerInterceptor 介绍

当要更新一条记录的时候,希望这条记录没有被别人更新
乐观锁实现方式:

取出记录时,获取当前 version
更新时,带上这个 version
执行更新时, set version = newVersion where version = oldVersion
如果 version 不对,就更新失败。

创建项目

创建项目

引入依赖

<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.5.7</version>
        </dependency>
        <dependency>
            <groupId>com.mysql</groupId>
            <artifactId>mysql-connector-j</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>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <version>RELEASE</version>
            <scope>compile</scope>
        </dependency>
    </dependencies>

创建数据表

DROP TABLE IF EXISTS `user`;
CREATE TABLE `user`
(
    id BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID',
    name VARCHAR(30) NULL DEFAULT NULL COMMENT '姓名',
    age INT NULL DEFAULT NULL COMMENT '年龄',
    email VARCHAR(50) NULL DEFAULT NULL COMMENT '邮箱',
    version INT not NULL DEFAULT 0 COMMENT '版本年龄',
    PRIMARY KEY (id)
)ENGINE=InnoDB DEFAULT CHARSET=utf8;

DELETE FROM user;
 
INSERT INTO user (id, name, age, email,version) VALUES
(1, 'Jone', 18, 'test1@baomidou.com',0),
(2, 'Jack', 20, 'test2@baomidou.com',0),
(3, 'Tom', 28, 'test3@baomidou.com',0),
(4, 'Sandy', 21, 'test4@baomidou.com',0),
(5, 'Billie', 24, 'test5@baomidou.com',0);

application.yml配置

server:
  port: 8080

spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://XXX:63306/user
    username: 
    password: 

mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

项目结构

配置乐观锁拦截器

package com.sky.mybatisversiondemo.config;

import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class MybatisPlusConfig {

    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
        return interceptor;
    }
}

创建实体类

version字段需要添加@Version注解表名是版本号字段
package com.sky.mybatisversiondemo.entity;

import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.Version;
import lombok.Data;


@Data
public class User {
    /**
     * 主键ID
     */
    @TableId(value = "id")
    private Long id;

    /**
     * 姓名
     */
    private String name;

    /**
     * 年龄
     */
    private Integer age;

    /**
     * 邮箱
     */
    private String email;
    /**
     * 版本号
     */
    @Version
    private Integer version;

}

创建mapper

package com.sky.mybatisversiondemo.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.sky.mybatisversiondemo.entity.User;
import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface UserMapper extends BaseMapper<User> {
}

创建service

这里利用了mybatisPlus的特性,提供了一些默认的查询。

UserService

package com.sky.mybatisversiondemo.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.sky.mybatisversiondemo.entity.User;

public interface UserService extends IService<User> {
}
UserServiceImpl
package com.sky.mybatisversiondemo.service.impl;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.sky.mybatisversiondemo.entity.User;
import com.sky.mybatisversiondemo.mapper.UserMapper;
import com.sky.mybatisversiondemo.service.UserService;
import org.springframework.stereotype.Service;

@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
}

创建返回包装类BaseResponse

package com.sky.mybatisversiondemo.vo;

import lombok.Data;

@Data
public class BaseResponse<T> {
    protected int code = 0;
    protected String message = "成功";

    protected T data;

    public BaseResponse(int code, String message) {
        this.code = code;
        this.message = message;
    }

    public static <T> BaseResponse of(T data) {
        BaseResponse<T> response = new BaseResponse();
        response.setData(data);
        return response;
    }
    public BaseResponse() {
    }
}

创建UserController

package com.sky.mybatisversiondemo.controller;

import com.sky.mybatisversiondemo.entity.User;
import com.sky.mybatisversiondemo.service.UserService;
import com.sky.mybatisversiondemo.vo.BaseResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@RequestMapping("/user")
public class UserController {
    @Autowired
    private UserService userService;

    @GetMapping("/list")
    public BaseResponse list(){
        List<User> list = userService.list();
        return BaseResponse.of(list);
    }

    @GetMapping("/ById")
    public  BaseResponse ById(Integer id){
        User user = userService.getById(id);
        if (user != null){
            return BaseResponse.of(user);
        }else {
            return new BaseResponse(500,"fail");
        }
    }

    /**
     * 插入学生信息
     * @param user
     */
    @PostMapping("/insert")
    public BaseResponse insertInfo(@RequestBody User user){
        boolean save = userService.save(user);
        if (save){
            return BaseResponse.of(save);
        }
        return new BaseResponse(500,"fail");
    }

    /**
     * 根据id更新学生表信息
     * @param user
     */
    @PutMapping("/update")
    public BaseResponse updateById(@RequestBody User user){
        boolean save = userService.updateById(user);
        if (save){
            return BaseResponse.of(save);
        }
        return new BaseResponse(500,"fail");
    }

    /**
     * 根据id删除学生信息
     * @param id
     */
    @DeleteMapping("/delete")
    public BaseResponse deleteById(String id){
        boolean b = userService.removeById(id);
        if (b){
            return BaseResponse.of(b);
        }
        return new BaseResponse(500,"fail");
    }

}

测试

查询

修改

修改是自动增加版本号,并且会增加版本号的判断,只有版本号一致才能修改成功。

日志

==>  Preparing: UPDATE user SET name=?, age=?, email=?, version=? WHERE id=? AND version=?
==> Parameters: Jone(String), 18(Integer), test1@baomidou.com(String), 1(Integer), 1(Long), 0(Integer)
<==    Updates: 1

修改后再查询 

此时版本号增加到1

再次按版本号0修改 

修改失败,因为版本号已经增加到了1,实现了乐观锁功能。

如果开发中遇到这种因为乐观锁修改失败的,则可以直接报错,或者再次查询数据后重试。推荐直接报错,修改失败,请重试。

修改时不传入版本号

则可以直接修改成功 ,但是修改后版本号不会增加,不推荐不传入版本号的修改。

日志

==>  Preparing: UPDATE user SET name=?, age=?, email=? WHERE id=?
==> Parameters: Jone(String), 18(Integer), test1@baomidou.com(String), 1(Long)
<==    Updates: 1

<think>我们正在讨论MyBatis-Plus中的乐观锁和悲观锁实现机制。根据引用内容,我们可以总结如下:1.**乐观锁**:MyBatis-Plus通过内置的乐观锁插件(`OptimisticLockerInterceptor`)提供支持。它使用版本号(或时间戳)机制,在更新数据时检查版本号是否被修改。如果版本号匹配则更新数据并递增版本号,否则抛出异常表示更新失败。2.**悲观锁**:MyBatis-Plus本身不直接提供悲观锁的实现,但可以通过在SQL语句中手动添加`FORUPDATE`子句(在查询时锁定记录)来实现,这需要依赖数据库的锁机制。接下来,我将详细说明这两种锁在MyBatis-Plus中的具体实现方式。###乐观锁实现步骤1.**实体类添加版本字段**在实体类中定义一个版本字段(如`version`),并使用`@Version`注解标记。```javapublicclassUser{privateLongid;privateStringname;privateIntegerage;@VersionprivateIntegerversion;//版本字段}```2.**配置乐观锁插件**在MyBatis-Plus的配置类中注册乐观锁拦截器。```java@ConfigurationpublicclassMybatisPlusConfig{@BeanpublicMybatisPlusInterceptormybatisPlusInterceptor(){MybatisPlusInterceptorinterceptor=newMybatisPlusInterceptor();//添加乐观锁插件interceptor.addInnerInterceptor(newOptimisticLockerInnerInterceptor());returninterceptor;}}```3.**更新操作**执行更新时,MyBatis-Plus会自动在`UPDATE`语句中添加版本条件,例如:```sqlUPDATEuserSETname=?,age=?,version=?WHEREid=?ANDversion=?```如果更新时发现版本不匹配(即记录已被其他事务修改),则更新失败。###悲观锁的实现方式由于MyBatis-Plus不直接封装悲观锁,需要手动编写SQL:1.**Mapper接口定义方法时添加`FORUPDATE`**在XML映射文件或注解SQL中,使用`SELECT...FORUPDATE`锁定记录。```xml<selectid="selectUserForUpdate"resultType="User">SELECT*FROMuserWHEREid=#{id}FORUPDATE</select>```2.**在事务中使用**在事务中执行查询锁定记录,然后进行更新操作。注意:事务结束后锁会自动释放。```java@TransactionalpublicvoidupdateUserWithPessimisticLock(Longid){//1.查询并锁定记录Useruser=userMapper.selectUserForUpdate(id);//2.执行业务逻辑和更新user.setAge(user.getAge()+1);userMapper.updateById(user);}```###两种锁的适用场景对比|锁类型|适用场景|性能影响||--------|-----------------------------------|------------------------||乐观锁|读多写少,冲突概率低|无锁,高并发下性能更好||悲观锁|写多读少,冲突概率高,强一致性要求|加锁操作会降低并发性能|###注意事项-**乐观锁冲突处理**:乐观锁更新失败时通常需要重试或提示用户。引用[5]提到可通过重试机制(如重试一定次数)来优化。-**悲观锁死锁风险**:长时间持有悲观锁可能导致死锁,需谨慎设计事务边界。§§相关问题§§1.MyBatis-Plus乐观锁如何实现版本号的自动更新?2.在哪些场景下应该选择乐观锁而不是悲观锁?3.如何避免MyBatis-Plus悲观锁实现中的死锁问题?4.MyBatis-Plus乐观锁更新失败时有哪些处理策略?</think>### MyBatis-Plus 乐观锁与悲观锁实现机制详解 #### 一、乐观锁实现机制 乐观锁的核心思想是**假设并发冲突概率低**,通过版本号控制实现无锁化并发: 1. **版本字段设计** 在数据库表中添加`version`字段(默认值0),并在实体类中用`@Version`标记: ```java public class Product { private Long id; private String name; @Version // 关键注解 private Integer version; } ``` 2. **插件配置** 注册乐观锁拦截器(引用[2][5]): ```java @Configuration public class MybatisPlusConfig { @Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor()); return interceptor; } } ``` 3. **更新流程** - 读取数据时获取当前版本号 $V_0$ - 更新时自动附加版本条件: ```sql UPDATE product SET name=?, version=version+1 WHERE id=? AND version=$V_0$ -- 版本校验 ``` - 若更新行数=0,说明版本冲突,抛出`OptimisticLockException` 4. **重试机制**(引用[3][5]) 通过循环重试解决冲突: ```java int retryCount = 0; while (retryCount < MAX_RETRY) { try { productMapper.updateById(product); break; // 成功则退出 } catch (OptimisticLockException e) { retryCount++; // 重新查询最新数据 product = productMapper.selectById(id); } } ``` #### 二、悲观锁实现机制 悲观锁依赖**数据库锁机制**(MyBatis-Plus无原生支持,需手动实现): 1. **SQL级锁定**(引用[4]) 在Mapper XML中使用`SELECT ... FOR UPDATE`: ```xml <select id="selectForUpdate" resultType="Product"> SELECT * FROM product WHERE id = #{id} FOR UPDATE <!-- 数据库行锁 --> </select> ``` 2. **事务中使用** 结合`@Transactional`确保锁生效: ```java @Transactional public void updateWithPessimisticLock(Long id) { Product product = productMapper.selectForUpdate(id); // 加锁 product.setStock(product.getStock() - 1); productMapper.updateById(product); } // 事务提交后自动释放锁 ``` 3. **Java级同步锁** 作为补充方案(引用[4]): ```java public synchronized void safeUpdate() { // 同步代码块保证单线程操作 } ``` #### 三、两种锁的对比 | **特性** | 乐观锁 | 悲观锁 | |----------------|--------------------------------|--------------------------------| | **实现原理** | 版本号校验 | 数据库行锁/Java锁 | | **并发性能** | 高(无锁等待) | 低(锁竞争开销) | | **适用场景** | 读多写少(电商库存) | 写多读少(账户扣款) | | **数据一致性** | 最终一致 | 强一致 | | **典型异常** | `OptimisticLockException` | 死锁/超时 | #### 四、选型建议 - **乐观锁适用场景** - 冲突概率 < 20% 的系统 - 需要高并发的读操作(如商品查询) - 业务容忍重试(如秒杀库存扣减) - **悲观锁适用场景** - 资金交易等强一致性需求 - 临界区操作复杂(需锁定多个资源) - 无法接受重试的场景(如支付订单) > 引用说明:乐观锁插件配置[^2][^5],悲观锁实现[^4],重试机制[^3][^5]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值