Mybatis-Plus

Mybatis-Plus(下篇)


条件构造器

条件构造器 | MyBatis-Plushttps://baomidou.com/guides/wrapper/

条件构造器是 MyBatis-Plus 提供的一个核心功能,它用于构建 SQL 的 WHERE 条件部分。通过链式调用的方式,可以非常灵活地组合各种查询条件,避免了手写 SQL 的繁琐和易错性。

MyBatis-Plus 提供了一套强大的条件构造器(Wrapper),用于构建复杂的数据库查询条件。Wrapper 类允许开发者以链式调用的方式构造查询条件,无需编写繁琐的 SQL 语句,从而提高开发效率并减少 SQL 注入的风险。

常用的条件构造器

  1. QueryWrapper:用于构建查询条件。

  2. UpdateWrapper:用于构建更新条件。

  3. DeleteWrapper:用于构建删除条件。可以将2,3进行合并统一使用2来进行操作,减少记忆,和操作API的种类。

前言:

WhereWrapper‌是MyBatis-Plus中用于构建SQL查询条件的一个重要工具。它通过提供各种条件构造器(如QueryWrapperUpdateWrapper)来帮助开发者更方便地构建复杂的查询条件,而不需要手动编写大量的SQL语句

WhereWrapper是MyBatis-Plus中条件构造器的一个抽象类,其下有多种实现,包括:

  • QueryWrapper‌:用于查询/删除操作,可以构建复杂的查询/删除条件。

  • UpdateWrapper‌:用于更新操作,可以构建复杂的更新条件。

  • LambdaQueryWrapper‌:使用Lambda表达式进行查询/删除操作。

  • LambdaUpdateWrapper‌:使用Lambda表达式进行更新操作。

Wrapper : 条件构造抽象类,最顶端父类

  • AbstractWrapper : 用于查询条件封装,生成 sql 的 where 条件

    • QueryWrapper : 查询/删除条件封装

    • UpdateWrapper : 修改条件封装

    • AbstractLambdaWrapper : 使用Lambda 语法

      • LambdaQueryWrapper :用于Lambda语法使用的查询Wrapper

      • LambdaUpdateWrapper : Lambda 更新封装Wrapper

在 MyBatis-Plus 中,Wrapper 类是构建查询和更新条件的核心工具。以下是主要的 Wrapper 类及其功能:

  • AbstractWrapper这是一个抽象基类,提供了所有 Wrapper 类共有的方法和属性。它定义了条件构造的基本逻辑,包括字段(column)、值(value)、操作符(condition)等。所有的 QueryWrapper、UpdateWrapper、LambdaQueryWrapper 和 LambdaUpdateWrapper 都继承自 AbstractWrapper。

  • QueryWrapper专门用于构造查询条件,支持基本的等于、不等于、大于、小于等各种常见操作。它允许你以链式调用的方式添加多个查询条件,并且可以组合使用 andor 逻辑。

  • UpdateWrapper用于构造更新条件,可以在更新数据时指定条件。与 QueryWrapper 类似,它也支持链式调用和逻辑组合。使用 UpdateWrapper 可以在不创建实体对象的情况下,直接设置更新字段和条件。

  • LambdaQueryWrapper:这是一个基于 Lambda 表达式的查询条件构造器,它通过 Lambda 表达式来引用实体类的属性,从而避免了硬编码字段名。这种方式提高了代码的可读性和可维护性,尤其是在字段名可能发生变化的情况下。

  • LambdaUpdateWrapper:类似于 LambdaQueryWrapper,LambdaUpdateWrapper 是基于 Lambda 表达式的更新条件构造器。它允许你使用 Lambda 表达式来指定更新字段和条件,同样避免了硬编码字段名的问题。

基于QueryWrapper 组装条件

①获取条件构造器QueryWrapper

②在条件构造器中拼接对应的条件表达语句

③在对应的增删改查方法形参列表中,加入已经构建(拼接)号的条件构造器实体

@SpringBootTest
public class QueryWrapperTest {
​
    @Autowired
    private UserMapper userMapper;
===============================组装查询条件========================================
    @Test
    public void testQueryWrapper() {
        //查询用户名包含a,年龄在20到30之间,并且邮箱不为null的用户信息
    //SELECT id,username AS name,age,email,is_deleted FROM t_user WHERE is_deleted=0 AND (username LIKE ? AND age BETWEEN ? AND ? AND email IS NOT NULL)
​
        QueryWrapper<User> QueryWrapper = new QueryWrapper<>();
​
        QueryWrapper.like("name","a");
        QueryWrapper.between("age",20,30);
        QueryWrapper.isNotNull("email");
​
        List<User> users = userMapper.selectList(QueryWrapper);
        users.forEach(System.out::println);
        //或者可以直接使用链式语法进行条件构造器设置。代码量少,方便又美观(下面就统一使用链式语法进行条件构造器的编译)
        QueryWrapper.like("name","a")
                .between("age",20,30)
                .isNotNull("email");
    }
​
=========================组装排序条件==============================================
    @Test
    public void testQueryWrapper2() {
        //按年龄降序查询用户,如果年龄相同则按id升序排列
    //SELECT id,username AS name,age,email,is_deleted FROM t_user WHERE is_deleted=0 ORDER BY age DESC,id ASC
        QueryWrapper<User> QueryWrapper = new QueryWrapper<>();
​
        QueryWrapper.orderByDesc("age")
                    .orderByAsc("id");
​
        List<User> users = userMapper.selectList(QueryWrapper);
        users.forEach(System.out::println);
    }
​
============================组装删除条件===========================================
    @Test
    public void testQueryWrapper3() {
       //删除email为空的用户
    //DELETE FROM t_user WHERE (email IS NULL)
        QueryWrapper<User> userQueryWrapper = new QueryWrapper<>();
        userQueryWrapper.isNull("email");
​
        userMapper.delete(userQueryWrapper);
        System.out.println("受影响的行数:" + result);
    }
​
=========================and与or关键字的使用======================================== 
    @Test
    public void testQueryWrapper4() {
        //将年龄大于20并且用户名中包含有a或邮箱为null的用户信息修改
    //UPDATE t_user SET age=?, email=? WHERE username LIKE ? AND age > ? OR email IS NULL)
        QueryWrapper<User> userQueryWrapper = new QueryWrapper<>();
​
        userQueryWrapper.like("name","a")
                .gt("age",20)
                .or()
                .isNull("email");
​
        User user = new User();
        user.setAge(20);
        user.setName("胡尔摩斯");
​
        userMapper.update(user, userQueryWrapper);
        System.out.println("受影响的行数:" + result);
    }
​
==========================指定映射查询=============================================
    @Test
    public void testQueryWrapper5() {
        //查询用户信息的username和age字段
    //SELECT username,age FROM t_user
        QueryWrapper<User> userQueryWrapper = new QueryWrapper<>();
        userQueryWrapper.select("name","age");
​
        //selectMaps()返回Map集合列表,通常配合select()使用,避免User对象中没有被查询到的列值为null
        List<Map<String, Object>> maps = userMapper.selectMaps(userQueryWrapper);
        maps.forEach(System.out::println);
    }
​
=========================condition判断组织条件查询================================== 
    @Test
    public void testQueryWrapper6() {
    // 拼接condition判断
    //每个条件拼接方法都condition参数,这是一个比较运算,为true追加当前条件!
    //eq(condition,列名,值)
        Integer age = 20;
​
        QueryWrapper<User> userQueryWrapper = new QueryWrapper<>();
        userQueryWrapper.eq(age!=null && age>18,"age",age);
        userMapper.selectList(userQueryWrapper);
    }
​
}

注意:使用queryWrapper + 实体类形式可以实现修改,但是无法将列值修改为null值!

基于 UpdateWrapper组装条件

@Test
public void testQuick2(){
​
    UpdateWrapper<User> updateWrapper = new UpdateWrapper<>();
    //将id = 3 的email设置为null, age = 18
    updateWrapper.eq("id",3)
            .set("email",null)  // set 指定列和结果
            .set("age",18);
    //如果使用updateWrapper 实体对象写null即可!
    int result = userMapper.update(null, updateWrapper);
    System.out.println("result = " + result);
​
}

基于LambdaQueryWrapper组装条件

LambdaQueryWrapper对比QueryWrapper优势

提高了代码可读性:通过 Lambda 表达式引用属性,代码更加直观易懂,尤其是在属性名较长或复杂时。

提高了代码健壮性:通过 Lambda 表达式来引用实体类的属性,例如 lambdaQueryWrapper.eq(User::getName, "张三")这种方式在编译时就能检查属性是否存在或拼写错误。

提高了代码可维护性:Lambda 表达式引用的是实体类的属性,只要实体类属性与数据库字段的映射关系正确,代码就能正常工作。

@Test
public void testQuick4(){
​
    String name = "root";
    int    age = 18;
​
    QueryWrapper<User> queryWrapper = new QueryWrapper<>();
    //每个条件拼接方法都condition参数,这是一个比较运算,为true追加当前条件!
    //eq(condition,列名,值)
    queryWrapper.eq(!StringUtils.isEmpty(name),"name",name)
            .eq(age>1,"age",age);
​
    //TODO: 使用lambdaQueryWrapper
    LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<>();
    //注意: 需要使用方法引用
    //技巧: 类名::方法名
    lambdaQueryWrapper.eq(!StringUtils.isEmpty(name), User::getName,name);
    List<User> users= userMapper.selectList(lambdaQueryWrapper);
    System.out.println(users);
}

基于LambdaUpdateWrapper组装条件

@Test
public void testQuick2(){
​
    UpdateWrapper<User> updateWrapper = new UpdateWrapper<>();
    //将id = 3 的email设置为null, age = 18
    updateWrapper.eq("id",3)
            .set("email",null)  // set 指定列和结果
            .set("age",18);
​
    //使用lambdaUpdateWrapper
    LambdaUpdateWrapper<User> updateWrapper1 = new LambdaUpdateWrapper<>();
    updateWrapper1.eq(User::getId,3)
            .set(User::getEmail,null)
            .set(User::getAge,18);
    
    //如果使用updateWrapper 实体对象写null即可!
    int result = userMapper.update(null, updateWrapper);
    System.out.println("result = " + result);
}

核心注解

思考:

MyBatis-plus中Mapper继承BaseMapper<实体类>后为什么会自动生成对应实体类(对应数据库中的一张表)的CRUD方法。

默认情况下, 根据指定的<实体类>的名称对应数据库表名,属性名对应数据库的列名!

但是不是所有数据库的信息和实体类都完全映射!

例如: 表名 t_user → 实体类 User 这时候就不对应了!

自定义映射关系就可以使用mybatis-plus提供的注解即可!

@TableName注解

描述:表名注解,标识实体类对应的数据库表

使用位置:实体类

使用场景:

  • 实体类的类名与数据库表名不一致(忽略大小写的情况)。

  • 数据库表名遵循特定的命名规范(例如,使用下划线分隔单词),而实体类名遵循 Java 的命名规范(使用驼峰命名法)。

  • 在多数据源场景下,不同的数据源可能包含相同名称的表,但需要通过不同的实体类来映射这些表。

#对于Spring Boot工程还可以进行yml文件配置来回正对应的映射关系(设置一些全局前缀等...)
mybatis-plus: # mybatis-plus的配置
  global-config:
    db-config:
      table-prefix: sys_ # 表名前缀字符串

@TableId 注解

描述:主键注解

使用位置:实体类主键字段

注解属性:

  • value指定实体类中的主键属性名。如果不指定,MyBatis-Plus 默认会寻找名为 id 的属性作为主键。

  • type指定主键的生成策略。MyBatis-Plus 提供了多种内置的主键生成策略,如 IdType.AUTO(数据库 ID 自增)、IdType.ASSIGN_ID(分配 ID,默认实现是雪花算法)、IdType.ASSIGN_UUID(分配 UUID)等。如果不指定,MyBatis-Plus 会根据数据库的类型和配置自动选择一种合适的策略。

使用场景:

  • 实体类的字段与数据库表的主键字段不同名

  • 自定义主键生成策略,如使用 UUID、雪花算法等。

属性类型必须指定默认值描述
valueString""主键字段名
typeEnumIdType.NONE指定主键类型

其中IdType属性可选值如下:

@TableName("sys_user")
public class User {
    @TableId(value="主键列名",type=主键策略)
    private Long id;
    private String name;
    private Integer age;
    private String email;
}

①主键策略为雪花算法时,数据库主键需要是 bigint/varchar(64)类型

实体类需要是long类型,随机生成一个数字赋值给主键(不重复)

②如果要设置全局主键策略,可以在application.xml文件中进行配置

雪花算法

雪花算法(Snowflake Algorithm)是一种分布式系统中生成全局唯一ID的算法,由Twitter开源。雪花算法生成的ID是一个64位的整数,通常以一个特定的格式来组织,以确保在分布式环境下生成的ID不仅全局唯一,而且是有序的。

雪花算法的结构

一个典型的雪花算法生成的64位ID由以下几部分组成:

  1. 符号位:1位,始终为0,因为生成的ID都是正整数。

  2. 时间戳位:41位,用来记录时间戳,单位通常是毫秒。这41位时间戳可以使用69年(从1970年开始计算,因为时间戳是从1970年1月1日00:00:00 UTC开始的)。

  3. 数据中心ID:5位,可以部署在1024个数据中心。

  4. 机器ID:5位,每个数据中心可以部署32台机器。

  5. 序列号:12位,毫秒内的计数,同一毫秒内最多可以生成4096个ID。

雪花算法的工作方式如下:

  1. 当前时间戳从某一固定的起始时间开始计算,可以用于计算ID的时间部分。

  2. 节点ID是分布式系统中每个节点的唯一标识,可以通过配置或自动分配的方式获得。

  3. 序列号用于记录在同一毫秒内生成的不同ID的序号,从0开始自增,最多支持4096个ID生成。

需要注意的是,雪花算法依赖于系统的时钟,需要确保系统时钟的准确性和单调性,否则可能会导致生成的ID不唯一或不符合预期的顺序。

雪花算法是一种简单但有效的生成唯一ID的算法,广泛应用于分布式系统中,如微服务架构、分布式数据库、分布式锁等场景,以满足全局唯一标识的需求。

你需要记住的: 雪花算法生成的数字,需要使用Long 或者 String类型主键!!

mybatis-plus:
  configuration:
    # 配置MyBatis日志
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  global-config:
    db-config:
      # 配置MyBatis-Plus操作表的默认前缀
      table-prefix: t_
      # 配置MyBatis-Plus的主键策略
      id-type: auto

@TableField

描述:字段注解(非主键)

使用位置:实体类的属性

注解属性:

  • value指定数据库表的列名。如果不指定,MyBatis-Plus 默认会使用实体类属性的名称(去除前缀,并将驼峰命名法转换为下划线分隔的形式)作为列名。

  • exist指定该属性是否映射到数据库表的列。如果设置为 false,则表示该属性不会映射到任何数据库表的列,这在进行一些特殊的查询或更新操作时可能很有用。

使用场景:

  • 实体类的属性名与数据库表的列名不一致。

  • 实体类的属性需要映射到数据库表的非主键列。

  • 在使用 MyBatis-Plus 的代码生成器时,可以通过配置自动生成带有 @TableField 注解的实体类,从而简化开发工作。

属性类型必须指定默认值描述
valueString“ ”数据库字段名
existBooleantrue是否为数据库表字段

@TableLogic

描述:逻辑删除注解

使用位置:实体类的属性

@TableLogic 注解有一个可选的属性 value,用于指定逻辑删除标记字段的值。如果不指定 value 属性,MyBatis-Plus 默认会使用 1 表示删除状态,0 表示未删除状态。

使用场景:

  • 需要保留数据的历史记录,而不是永久删除。

  • 避免物理删除可能带来的数据恢复困难或数据安全问题。

  • 实现软删除功能,即数据在逻辑上被删除,但在物理上仍然保留在数据库中。

MyBatis-Plus会自动开启驼峰命名风格映射!!!

高级扩展

逻辑删除

由于数据库中所有表均采用逻辑删除策略,所以查询数据时均需要增加过滤条件`is_deleted=0`。

上述操作虽不难实现,但是每个查询接口都要考虑到,也显得有些繁琐。为简化上述操作,可以使用Mybatis-Plus提供的逻辑删除功能,它可以自动为查询操作增加`is_deleted=0`过滤条件,并将删除操作转为更新语句。具体配置如下:

两种删除方式:

  • 物理删除:真实删除,将对应数据从数据库中删除,之后查询不到此条被删除的数据

  • 逻辑删除:假删除,将对应数据中代表是否被删除字段的状态修改为“被删除状态”,之后在数据库中仍旧能看到此条数据记录

逻辑删除实现:

①添加逻辑删除字段(可以声明为一个布尔类型,整数类型或枚举类型)

②实体类添加逻辑删除属性

③指定逻辑删除字段和属性值

ALTER TABLE USER ADD deleted INT DEFAULT 0 ;  # int 类型 1 逻辑删除 0 未逻辑删除
@Data
public class User {
​
   // @TableId
    private Integer id;
    private String name;
    private Integer age;
    private String email;
    
    @TableLogic
    //逻辑删除字段 int mybatis-plus下,默认 逻辑删除值为1 未逻辑删除 1 
    private Integer deleted;
}
​
//单一进行指定
@Data
public class User {
​
   // @TableId
    private Integer id;
    private String name;
    private Integer age;
    private String email;
     @TableLogic
    //逻辑删除字段 int mybatis-plus下,默认 逻辑删除值为1 未逻辑删除 1 
    private Integer deleted;
}
#全局进行指定
mybatis-plus:
  global-config:
    db-config:
      logic-delete-field: deleted # 全局逻辑删除的实体字段名(since 3.3.0,配置后可以忽略不配置步骤2)
      logic-delete-value: 1 # 逻辑已删除值(默认为 1)
      logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)

注意

逻辑删除功能只对Mybatis-Plus自动注入的sql起效,也就是说,对于手动在Mapper.xml文件配置的sql不会生效,需要单独考虑。

忽略特定字段

通常情况下接口响应的Json对象中并不需要`create_time`、`update_time`、`is_deleted`等字段,这时只需在实体类中的相应字段添加`@JsonIgnore`注解,该字段就会在序列化时被忽略。

具体配置如下:

package com.atguigu.lease.model.entity;

import com.baomidou.mybatisplus.annotation.*;
import com.fasterxml.jackson.annotation.JsonIgnore;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;

import java.io.Serializable;
import java.util.Date;

@Data
public class BaseEntity implements Serializable {

    @Schema(description = "主键")
    @TableId(value = "id", type = IdType.AUTO)
    private Long id;

    @Schema(description = "创建时间")
    @TableField(value = "create_time")
    @JsonIgnore
    private Date createTime;

    @Schema(description = "更新时间")
    @TableField(value = "update_time")
    @JsonIgnore//忽视序列化
    //后端查询完数据库后,将查询到的数据序列化后,才将数据发送给前端
    //在使用该注解后,会在序列化数据时,将本字段忽略掉
    private Date updateTime;

    @Schema(description = "逻辑删除")
    @TableField("is_deleted")
    @JsonIgnore
    @TableLogic//逻辑删除字段注解
    //可以在配置文件中来指定对应的逻辑删除字段名
    //配置逻辑删除值,用来将指定什么值代表逻辑删除,什么值代表未删除
    //当然了,在未配置的情况下。默认1是逻辑删除,0是未删除
    //注意:这种逻辑删除的配置只对通用mapper和通用service生效。对于自己写的sql代码mapper层是不生效的
    private Byte isDeleted;

}

公共字段填充

保存或更新数据时,前端通常不会传入`createTime`、`updateTime`这2个字段,因此我们需要手动赋值。但是数据库中几乎每张表都有上述字段,所以手动去赋值就显得有些繁琐。为简化上述操作,我们可采取以下措施。

create_time和update_time:可使用mybatis-plus的自动填充功能,所谓自动填充,就是通过统一配置,在插入或更新数据时,自动为某些字段赋值。 为相关字段配置触发填充的时机,例如`create_time`需要在插入数据时填充,而`update_time`需要在更新数据时填充。具体配置如下,观察`@TableField`注解中的`fill`属性。

package com.atguigu.lease.model.entity;

import com.baomidou.mybatisplus.annotation.*;
import com.fasterxml.jackson.annotation.JsonIgnore;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;

import java.io.Serializable;
import java.util.Date;

@Data
public class BaseEntity implements Serializable {

    @Schema(description = "主键")
    @TableId(value = "id", type = IdType.AUTO)
    private Long id;

    @Schema(description = "创建时间")
    @TableField(value = "create_time",fill = FieldFill.INSERT)
    @JsonIgnore
    private Date createTime;

    @Schema(description = "更新时间")
    @TableField(value = "update_time",fill = FieldFill.UPDATE)
    @JsonIgnore//忽视序列化
    //后端查询完数据库后,将查询到的数据序列化后,才将数据发送给前端
    //在使用该注解后,会在序列化数据时,将本字段忽略掉
    private Date updateTime;

    @Schema(description = "逻辑删除")
    @TableField("is_deleted")
    @JsonIgnore
    @TableLogic//逻辑删除字段注解
    //可以在配置文件中来指定对应的逻辑删除字段名
    //配置逻辑删除值,用来将指定什么值代表逻辑删除,什么值代表未删除
    //当然了,在未配置的情况下。默认1是逻辑删除,0是未删除
    //注意:这种逻辑删除的配置只对通用mapper和通用service生效。对于自己写的sql代码mapper层是不生效的
    private Byte isDeleted;

}

 自动填充组件配置:

package com.atguigu.lease.common.mybatisplus;

import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.stereotype.Component;

import java.util.Date;

@Component
public class MybatisMetaObjectHandler implements MetaObjectHandler {
    @Override
    public void insertFill(MetaObject metaObject) {
        this.strictInsertFill(metaObject, "createTime", Date.class, new Date());
        //当填充操作发生时,会为实体对象的createTime属性填充当前时间
    }

    @Override
    public void updateFill(MetaObject metaObject) {
        this.strictUpdateFill(metaObject, "updateTime", Date.class, new Date());
        //当更新操作发生时,会为实体对象的updateTime属性填充当前时间
    }
}

乐观锁与悲观锁

乐观锁与悲观锁是并发控制中的两种不同策略用于解决多线程环境下的数据一致性问题。

悲观锁和乐观锁是两种解决并发数据问题的思路,不是具体技术!!!

乐观锁

乐观锁假设多用户并发的事务在处理时不会彼此干扰,因此在数据提交时才会对数据的冲突与否进行检测。如果发现冲突了,则回滚当前事务,让用户重新尝试。乐观锁的实现通常依赖于数据版本(Version)记录机制。

特点:

  1. 乐观态度认为冲突很少发生,因此在进行更新操作时不会加锁。

  2. 性能较高因为减少了锁的竞争,所以在高并发环境下性能较好

具体技术和实现方案:

数据版本:在数据库表中增加一个版本字段,每次更新数据时,版本字段都会递增。在更新操作时,会检查当前版本是否与预期版本一致,如果一致则更新成功,否则更新失败。

时间戳:类似于数据版本,但使用时间戳来记录数据的最后修改时间。

CAS(Compare-and-Swap):使用原子操作比较当前值与旧值是否一致,若一致则进行更新操作,否则重新尝试。

无锁数据结构:采用无锁数据结构,如无锁队列、无锁哈希表等,通过使用原子操作实现并发安全。

悲观锁

悲观锁则假设最坏的情况,认为多用户并发的事务一定会发生冲突,因此在数据处理过程中,将数据处于锁定状态。悲观锁的实现依赖于数据库的锁机制。

特点:

  1. 悲观态度认为冲突经常发生,因此在整个数据处理过程中都会保持锁定状态。

  2. 性能较低因为锁定了数据,所以其他线程无法访问被锁定的数据,可能导致并发性能下降。

具体技术和实现方案:

锁机制:使用传统的锁机制,如互斥锁(Mutex Lock)或读写锁(Read-Write Lock)来保证对共享资源的独占访问。

数据库锁:在数据库层面使用行级锁或表级锁来控制并发访问。

信号量(Semaphore):使用信号量来限制对资源的并发访问。

版本号乐观锁实现:

①每条数据添加一个版本号字段version

②取出记录时,获取当前 version

③更新时,检查获取版本号是不是数据库当前最新版本号

④如果是[证明没有人修改数据], 执行更新, set 数据更新 , version = version+ 1

⑤如果 version 不对[证明有人已经修改了],我们现在的其他记录就是失效数据!就更新失败

===============================添加版本号更新插件====================================
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
    MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
    interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
    return interceptor;
}
 
===============================数据库添加version字段================================
ALTER TABLE USER ADD VERSION INT DEFAULT 1 ;  # int 类型 乐观锁字段
 
=====================在数据库所对应的实体类上添加@version的注解=========================
@Version
private Integer version;
===========================正常对数据库进行操作即可===================================
//演示乐观锁生效场景
@Test
public void testQuick7(){
    //步骤1: 先查询,在更新 获取version数据
    //同时查询两条,但是version唯一,最后更新的失败
    User user  = userMapper.selectById(5);
    User user1  = userMapper.selectById(5);
​
    user.setAge(20);
    user1.setAge(30);
​
    userMapper.updateById(user);
    //乐观锁生效,失败!
    userMapper.updateById(user1);
}
  

@Version注解

描述:用于在实体类中标记乐观锁的版本号字段

使用位置:实体类的属性

使用场景

  • 在多用户并发访问和修改同一数据记录的场景下,使用乐观锁可以确保数据的一致性和完整性。

  • 乐观锁适用于读多写少的场景,因为它假设冲突不常发生,从而减少了锁的开销,提高了系统的并发性能。

防止全表的更新和删除实现

针对 update 和 delete 语句 作用: 阻止恶意的全表更新删除

==========================添加防止全表更新和删除拦截器================================
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
  MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
  interceptor.addInnerInterceptor(new BlockAttackInnerInterceptor());
  return interceptor;
}
}
 
===========================测试全表更新或者删除======================================
@Test
public void testQuick8(){
    User user = new User();
    user.setName("custom_name");
    user.setEmail("xxx@mail.com");
    //Caused by: com.baomidou.mybatisplus.core.exceptions.MybatisPlusException: Prohibition of table update operation
    //全局更新,报错
    userService.saveOrUpdate(user,null);
} 

Mybatisx代码生成器(MyBatisX插件)

MybatisX 是一款专为 IntelliJ IDEA 设计的快速开发插件,旨在提升 MyBatis 与 MyBatis-Plus 框架的开发效率。

MyBatis-Plus为我们提供了强大的mapper和service模板,能够大大的提高开发效率

MyBatisX插件,还有许多核心功能并没有提及如果感兴趣请移步至官网开发手册,有全套核心功能教程供学习,。这里主要说明与一下其逆向工程和代码生成功能简易开发。(在前面的MyBatis笔记下篇中有逆向工程的具体操作流程,可以自行查看)

Mybatis X 插件 | MyBatis-Plushttps://baomidou.com/guides/mybatis-x/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值