史上最全的MybatisPlus学习教程从入门到精通

news2024/11/25 16:00:02

一、MybatisPlus是什么

1.1 MyBatis-Plus简介

MyBatis-Plus(简称MP)是一个MyBatis的增强工具,它在MyBatis的基础上进行了增强,但并没有改变原有的MyBatis框架。MyBatis-Plus的主要目标是简化开发和提高开发效率。它提供了诸如分页插件、代码生成器、注解支持等丰富的功能特性,使得开发者在使用MyBatis时能够更加便捷和高效。MyBatis-Plus的设计理念是“无侵入”,即引入它不会对现有工程产生影响,同时它还具备损耗小、CRUD操作强大、支持Lambda表达式调用等优点。

1.2 为什么会出现MyBatis-Plus

MyBatis-Plus框架的出现主要是为了解决MyBatis在使用过程中存在的一些不足。尽管MyBatis是一个非常优秀的持久层框架,它支持自定义SQL、存储过程以及高级映射,但它在一些简单的单表操作上并没有提供一套现成的通用增删改查(CRUD)操作,这就需要开发者自己去编写,这样不仅繁琐而且浪费时间。此外,MyBatis自身的功能不够丰富,例如它没有自带分页插件、没有分布式ID支持等。因此,MyBatis-Plus应运而生,它旨在提供更加丰富的功能特性,简化开发流程,提高开发效率。

1.3 MyBatis-Plus的特点

以下特点引用自MP官网:简介 | MyBatis-Plus (baomidou.com)

特性:

  • 无侵入:只做增强不做改变,引入它不会对现有工程产生影响,如丝般顺滑
  • 损耗小:启动即会自动注入基本 CURD,性能基本无损耗,直接面向对象操作
  • 强大的 CRUD 操作:内置通用 Mapper、通用 Service,仅仅通过少量配置即可实现单表大部分 CRUD 操作,更有强大的条件构造器,满足各类使用需求
  • 支持 Lambda 形式调用:通过 Lambda 表达式,方便的编写各类查询条件,无需再担心字段写错
  • 支持主键自动生成:支持多达 4 种主键策略(内含分布式唯一 ID 生成器 - Sequence),可自由配置,完美解决主键问题
  • 支持 ActiveRecord 模式:支持 ActiveRecord 形式调用,实体类只需继承 Model 类即可进行强大的 CRUD 操作
  • 支持自定义全局通用操作:支持全局通用方法注入( Write once, use anywhere )
  • 内置代码生成器:采用代码或者 Maven 插件可快速生成 Mapper 、 Model 、 Service 、 Controller 层代码,支持模板引擎,更有超多自定义配置等您来使用
  • 内置分页插件:基于 MyBatis 物理分页,开发者无需关心具体操作,配置好插件之后,写分页等同于普通 List 查询
  • 分页插件支持多种数据库:支持 MySQL、MariaDB、Oracle、DB2、H2、HSQL、SQLite、Postgre、SQLServer 等多种数据库
  • 内置性能分析插件:可输出 SQL 语句以及其执行时间,建议开发测试时启用该功能,能快速揪出慢查询
  • 内置全局拦截插件:提供全表 delete 、 update 操作智能分析阻断,也可自定义拦截规则,预防误操作

二、MybatisPlus快速入门

2.1 创建mp_test数据库

2.2 新建user表

CREATE TABLE user
(
    id BIGINT(20) NOT NULL COMMENT '主键ID',
    name VARCHAR(30) NULL DEFAULT NULL COMMENT '姓名',
    age INT(11) NULL DEFAULT NULL COMMENT '年龄',
    email VARCHAR(50) NULL DEFAULT NULL COMMENT '邮箱',
    PRIMARY KEY (id)
);

2.3 初始化user表

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

2.4 初始化项目

初始化一个新的springboot项目

2.5 添加mybatisplus依赖

修改pom.xml文件,添加如下依赖

        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.5.1</version>
            </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <scope>provided</scope>
        </dependency>

2.6 修改配置文件

修改application.yml文件,当然在初始化的情况下是叫做application.propertis

# DataSource Config
spring:
  datasource:
    # 密码和用户名需要修改为自己的
    username: root
    password: root
    url: jdbc:mysql://localhost:3306/mp_test?userUnicode=true&characterEncoding=utf-8
    driver-class-name: com.mysql.cj.jdbc.Driver

2.7 编码

1.创建实体类User

package com.ltx.mybatisplus_test.demos;

import lombok.Data;

/**
 * @author 罗添煦
 */
@Data
public class User {
    private Long id;
    private String name;
    private Integer age;
    private String email;
}

2.创建UserMapper接口

在这个接口中你就可以看见mybaitsplus的魔力了,只需要extends接口就可以完成所有的crud操作。

package com.ltx.mybatisplus_test.demos.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.ltx.mybatisplus_test.demos.User;
import org.apache.ibatis.annotations.Mapper;

//再对应的mapper上面实现基本的接口 BaseMapper
@Mapper
public interface UserMapper extends BaseMapper<User> {
    //所有的CRUD都已经完成
    //不需要像以前一样配置一大堆文件:pojo-dao(连接mybatis,配置mapper.xml文件)==>service-controller
}

 3.扫描Mapper

package com.ltx.mybatisplus_test;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
@MapperScan("com.ltx.mybatisplus_test.demos.mapper")
public class MybatisPlusTestApplication {

    public static void main(String[] args) {
        SpringApplication.run(MybatisPlusTestApplication.class, args);
    }

}

有了MapperScan注解就可以不是用mapper去扫描接口但是还是建议养成在每个mapper上加上@Mapper的注解。 

2.8 测试使用

package com.ltx.mybatisplus_test;

import com.ltx.mybatisplus_test.demos.User;
import com.ltx.mybatisplus_test.demos.mapper.UserMapper;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import java.util.List;

@SpringBootTest
class MybatisPlusTestApplicationTests {
    @Autowired
    private UserMapper userMapper;

    @Test
    void TestSelect() {
        List<User> users = userMapper.selectList(null);
        users.forEach(System.out::println);
        System.out.println("我修改咯");
    }
    
}

结果如下所示就代表你已经成功的入门了mybatisplus了

三、配置日志

所有的SQL都是不可见的,所以在后台是希望看到SQL是怎么执行的,就必须要配置日志。

在.yml配置文件中配置日志:

#配置日志
mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

输出sql日志如下所示: 

四、增删改查操作

4.1 新增操作

    @Test
    void TestInsert() {
        User user = new User();
        user.setName("ltx");
        user.setAge(666);
        user.setEmail("1429189960@gmail.com");
        int result = userMapper.insert(user);
        System.out.println("result:" + result);
        System.out.println("user:" + user);
    }

 结果如下所示:

对于上面的结果我们可以看到,我们其实没有指定id的值,但是为什么在插入的时候自动的帮我们加上了id呢。

4.2 自定义id生成器

在复杂分布式系统中,往往需要大量的数据和消息进行唯一标识。比如支付宝每一个账号在数据库分表后都需要有一个唯一ID做标识。此时一个能够生成全局唯一ID的系统是非常必要的。

所以全局唯一ID需要有如下特征:

  1. 全局唯一性:确保生成的ID在整个系统中是独一无二的,避免任何重复或冲突。
  2. 趋势有序性:某些场景下,ID需要大体按照时间顺序生成,这样可以在物理存储上以这个字段排序,提高查询效率。
  3. 单调递增:在某些特殊需求下,如事务版本号、消息队列等,需要保证ID的递增性。
  4. 高可用性:ID生成系统需要保证在任何时候都能稳定地生成ID,不能有单点故障

我们可以通过@TableId注解来查看有哪些主键的自动生成策略

package com.ltx.mybatisplus_test.demos;

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

/**
 * @author 罗添煦
 */
@Data
public class User {
    @TableId(type = IdType.AUTO)
    private Long id;
    private String name;
    private Integer age;
    private String email;
}

我们点入IdType查看自动生成策略

@Getter
public enum IdType {
    /**
     * 数据库ID自增
     * <p>该类型请确保数据库设置了 ID自增 否则无效</p>
     */
    AUTO(0),
    /**
     * 该类型为未设置主键类型(注解里等于跟随全局,全局里约等于 INPUT)
     */
    NONE(1),
    /**
     * 用户输入ID
     * <p>该类型可以通过自己注册自动填充插件进行填充</p>
     */
    INPUT(2),
​
    /* 以下3种类型、只有当插入对象ID 为空,才自动填充。 */
    /**
     * 分配ID (主键类型为number或string),
     * 默认实现类 {@link com.baomidou.mybatisplus.core.incrementer.DefaultIdentifierGenerator}(雪花算法)
     *
     * @since 3.3.0
     */
    ASSIGN_ID(3),
    /**
     * 分配UUID (主键类型为 string)
     * 默认实现类 {@link com.baomidou.mybatisplus.core.incrementer.DefaultIdentifierGenerator}(UUID.replace("-",""))
     */
    ASSIGN_UUID(4);
​
    private final int key;
​
    IdType(int key) {
        this.key = key;
    }
}   

MyBatis-Plus 提供了灵活的自定义ID生成器功能,允许开发者根据业务需求定制ID生成策略。从3.3.0版本开始,默认使用雪花算法结合不含中划线的UUID作为ID生成方式。

下面我具体说一下UUID和雪花算法的实现原理

4.2.1 UUID

UUID(Universally Unique Identifier,通用唯一识别码)是一种标准化的唯一性标识符,用于在分布式系统中无需中央协调就能保证唯一性。UUID通常表示为一个32位的16进制数字,按照8-4-4-4-12的格式分为五部分,形成一个36个字符长度的字符串。

uuid有多个版本的生成

  • 版本1:基于时间的UUID,结合时间戳和机器的MAC地址。
  • 版本2:基于分布式系统的安全UUID,使用POSIX UID/GID。
  • 版本3和版本5:基于名字空间的UUID,通过散列算法生成。
  • 版本4:基于随机数的UUID。

优点:

  1. 全局唯一性:UUID的设计确保了在全局范围内的唯一性,减少了ID冲突的可能性。
  2. 去中心化生成:不需要中央服务器或服务来生成ID,适合分布式系统。
  3. 高性能:本地生成,没有网络请求的开销,可以快速生成。
  4. 简单易用:大多数编程语言提供了生成UUID的库或函数,易于集成到应用程序中。
  5. 安全性:某些UUID版本(如版本4)不包含任何可识别的信息,增加了隐私性。
  6. 时间戳基础:某些UUID版本(如版本1)包含时间戳,可以间接反映生成时间。

缺点:

  1. 存储空间:UUID通常由36个字符组成,相比于其他类型的ID(如自增ID),占用更多的存储空间。
  2. 性能问题:由于UUID的无序性,作为数据库主键时可能影响索引性能,特别是在写入密集型的应用中。
  3. 传输效率:较长的字符串长度可能导致在网络传输时效率较低。
  4. 可读性差:UUID是随机的16进制数字符串,不具备可读性,不便于人工解读和记录。
  5. 信息泄露:某些UUID版本可能会暴露生成它的机器的某些信息,如版本1的MAC地址。
  6. 单调性问题:UUID不保证生成顺序,对于需要ID单调递增的场景(如某些数据库的自增主键)不适用。
  7. 版本依赖:UUID有多个版本,不同版本有不同的生成规则和特性,选择合适的版本可能需要额外考虑。

4.2.2 雪花算法

这种方案大致来说是一种以划分命名空间(UUID也算,由于比较常见,所以单独分析)来生成ID的一种算法,这种方案把64-bit分别划分成多段,分开来标示机器、时间等,比如在snowflake中的64-bit分别表示如下图(图片来自网络)所示:

41-bit的时间可以表示(1L<<41)/(1000L360024*365)=69年的时间,10-bit机器可以分别表示1024台机器。如果我们对IDC划分有需求,还可以将10-bit分5-bit给IDC,分5-bit给工作机器。这样就可以表示32个IDC,每个IDC下可以有32台机器,可以根据自身需求定义。12个自增序列号可以表示2^12个ID,理论上snowflake方案的QPS约为409.6w/s,这种分配方式可以保证在任何一个IDC的任何一台机器在任意毫秒内生成的ID都是不同的。

4.2 删除操作

4.2.1 删除单条数据

    @Test
    void TestDelete() {
        User user = new User();
        user.setId(1L);
        userMapper.deleteById(user);
    }

结果如下所示:

 这个地方我们需要注意到的是我们需要传入的是一个User对象,而不是传入一个Id

 4.2.2 批量删除

    /**
     * 批量删除
     */
    @Test
    void TestDeleteList(){
        userMapper.deleteBatchIds(Arrays.asList(4L,5L));
    }

结果如下所示:

4.2.3 条件删除

    /**
     * 根据map删除
     */
    @Test
    void TestDeleteMap(){
        Map<String, Object> map = new HashMap<>();
        map.put("name","ltx");
        userMapper.deleteByMap(map);
    }

结果如下所示:

 所以我们可以通过map进行不是id外的参数进行删除,也可以在map中指定多个参数进行删除。

4.2.4 逻辑删除

在很多的情况下用户删除数据不可以被删除,比如博客被删除就是作为逻辑删除不会被真的删除可能用户会要恢复数据的情况出现。

物理删除:从数据库中直接删除

逻辑删除:在数据库中没有被删除,而是通过一个变量来让它失效。 deleted=0 ==》deleted=1

 1.在数据库中新增逻辑删除字段

2.实体类修改,并加上@TableLogic注解

package com.ltx.mybatisplus_test.demos;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableLogic;
import lombok.Data;

/**
 * @author 罗添煦
 */
@Data
public class User {
    @TableId(type = IdType.AUTO)
    private Long id;
    private String name;
    private Integer age;
    private String email;
    @TableLogic
    private Integer delete;
}

3.修改yml文件

#配置日志
mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  global-config:
    db-config:
      logic-delete-field: flag # 全局逻辑删除的实体字段名(since 3.3.0,配置后可以忽略不配置步骤2)
      logic-delete-value: 1 # 逻辑已删除值(默认为 1)
      logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)

 4.测试逻辑删除

结果如下所示:

通过结果我们可以看到逻辑删除实质上就是update(修改)语句,将deleted字段从1修改为0。

然后我们可以进行一个查询操作看看mp是怎么做到的逻辑删除

我们可以看到在查询的时候mp自动帮我们拼接上了deleted=0,所以也就没有抽查到数据。

4.3 修改操作

    /**
     * 更新
     */
    @Test
    void TestUpdate(){
        User user = new User();
        user.setId(1829036034502017025L);
        user.setName("ltx");
        userMapper.updateById(user);
    }

结果如下所示:

4.4 查询操作

当然在查询操作之前你需要知道的是很有可能查询操作你会有和我不一样的id数据这个时候你可以自己加上或者改成自己的数据都是可以的。

4.4.1 通过ID查询

    /**
    *通过id查询
    */
    @Test
    void TestSelectById(){
        User user = userMapper.selectById(1829036034502017025L);
        System.out.println(user);
    }

结果如下所示

4.4.2 批量查询


    /**
     * 批量查询
     */
    @Test
    void TestSelectList(){
        List<User> users = userMapper.selectBatchIds(Arrays.asList(1L,2L));
        users.forEach(System.out::println);
    }

结果如下所示: 

4.4.3 条件查询

    /**
     * 条件查询
     */
    @Test
    void TestSelectMap(){
        Map<String, Object> map = new HashMap<>();
        map.put("name","ltx");
        map.put("age",666);
        userMapper.selectByMap(map);
    }

条件查询和我们前面介绍到的条件删除是一样的逻辑就不再这个地方过多的介绍了。

结果如下所示:

4.4.4 分页查询

mysql原生使用Limit进行分页查询

LIMIT offset, count
  • offset:这是从结果集的哪一条记录开始返回。它是一个整数,表示从查询结果的第一条记录开始计数的偏移量。如果设置为 0,则从第一条记录开始返回。如果设置为 10,则从第 11 条记录开始返回,因为 SQL 计数通常是从 0 开始的。
  • count:这是返回记录的最大数量。它也是一个整数,表示从 offset 指定的位置开始,最多返回多少条记录。。

MyBatis-Plus 的分页插件 PaginationInnerInterceptor 提供了强大的分页功能,支持多种数据库,使得分页查询变得简单高效。

配置方法

在 Spring Boot 项目中,你可以通过 Java 配置来添加分页插件:

@Configuration
public class MybatisPlusConfig {

    /**
     * 添加分页插件
     */
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); // 如果配置多个插件, 切记分页最后添加
        // 如果有多数据源可以不配具体类型, 否则都建议配上具体的 DbType
        return interceptor;
    }
}

测试分页组件

    /**
     * 分页查询
     */
    @Test
    void TestSelectPage(){

    // 两个参数:current的值默认是1,从1开始,不是0。size是每一页的条数。
        Page<User> page = new Page<>(1, 4);
        userMapper.selectPage(page,null);
        page.getRecords().forEach(System.out::println);
    }

 结果如下所示:

其实分页查询插件在底层还是使用的是limit进行查询 

Page中的其他方法

//page的其他方法
System.out.println("当前页:" + page.getCurrent());
System.out.println("总页数:" + page.getPages());
System.out.println("记录数:" + page.getTotal());
System.out.println("是否有上一页:" + page.hasPrevious());
System.out.println("是否有下一页:" + page.hasNext());

五、自动填充

在常用业务中有些属性需要配置一些默认值,MyBatis-Plus提供了实现此功能的插件,也就是自动填充功能。比如创建时间、修改时间这些操作一般都是自动化完成的,是不用去手动更新的。

5.1 数据库层面

1.添加字段

在数据库中添加create_time和update_time字段并且设置默认值        

2.实体类同步

    private Date createTime;
    private Date updateTime;

3.代码测试

sql语句中并没有把create_time和update_time插入到数据库中,我们可以查看数据库中的数据。

4.数据库查看

我们可以看到插入的数据自动的帮我们把create_time和uadate_time给我们赋值了

5.2 代码层面

1.修改数据库,删除默认值

2.注解填充字段 @TableField(.. fill = FieldFill.INSERT) 

    @TableField(fill = FieldFill.INSERT)
    private Date createTime;
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private Date updateTime;

3.自定义实现类 MyMetaObjectHandler 处理这个注解

@Slf4j
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {

    @Override
    public void insertFill(MetaObject metaObject) {
        log.info("开始插入填充...");
        this.strictInsertFill(metaObject, "create_time", LocalDateTime.class, LocalDateTime.now());
    }

    @Override
    public void updateFill(MetaObject metaObject) {
        log.info("开始更新填充...");
        this.strictUpdateFill(metaObject, "update_time", LocalDateTime.class, LocalDateTime.now());
    }
}

六、条件构造器

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

下面我举例几个简单的test,大家如果有兴趣的话可以去官网看更多的使用:

1、查询name、邮箱不为空且年龄大于等于20的用户

@Test
void WrapperTest(){
    //查询name、邮箱不为空且年龄大于等于20的用户
    QueryWrapper<User> wrapper = new QueryWrapper<>();
    wrapper
            .isNotNull("name")
            .isNotNull("email")
            .ge("age",12);
    userMapper.selectList(wrapper).forEach(System.out::println);
}

结果如下所示:

2、查询姓名为小罗的用户

    @Test
    void WrapperTest2(){
        //查询姓名叫做“小罗”用户
        QueryWrapper<User> wrapper = new QueryWrapper<>();
        wrapper
                .eq("name","小罗");
        System.out.println(userMapper.selectOne(wrapper));
    }

结果如下所示:

 

3、查询年龄在19-28之间的用户

    @Test
    void WrapperTest3(){
        //查询年龄在19-28之间的用户
        QueryWrapper<User> wrapper = new QueryWrapper<>();
        wrapper
                .between("age",19,28);
        Long count = userMapper.selectCount(wrapper);//查询结果数
        System.out.println(count);
    }

结果如下所示: 

4、模糊查询

    @Test
    void WrapperTest4(){
        QueryWrapper<User> wrapper = new QueryWrapper<>();
        wrapper
                .notLike("name","l")    //查询姓名中不包含a的用户
                .likeRight("email","1");   //左和右是代表%的位置 两边都要匹配则为%e%,这里是email以t开头的 t%
        List<Map<String, Object>> maps = userMapper.selectMaps(wrapper);
        maps.forEach(System.out::println);
    }

结果如下所示: 

5、通过ID进行排序

@Test
void WrapperTest5(){
    //通过ID进行排序
    QueryWrapper<User> wrapper = new QueryWrapper<>();
    wrapper.orderByAsc("id");   //通过id升序
    List<User> users = userMapper.selectList(wrapper);
    users.forEach(System.out::println);
}

结果如下所示:

七、乐观锁和悲观锁

7.1 什么是乐观锁和悲观锁

乐观锁:十分乐观,认为不会出现问题,无论干什么都不会去上锁,如果出现问题,就再次更新测试值

乐观锁是对于数据冲突保持一种乐观态度,操作数据时不会对操作的数据进行加锁(这使得多个任务可以并行的对数据进行操作),只有到数据提交的时候才通过一种机制来验证数据是否存在冲突(一般实现方式是通过加版本号然后进行版本号的对比方式实现);

特点:乐观锁是一种并发类型的锁,其本身不对数据进行加锁而是通过业务实现锁的功能,不对数据进行加锁就意味着允许多个请求同时访问数据,同时也省掉了对数据加锁和解锁的过程,这种方式因为节省了悲观锁加锁的操作,所以可以一定程度的的提高操作的性能,不过在并发非常高的情况下,会导致大量的请求冲突,冲突导致大部分操作无功而返而浪费资源,所以在高并发的场景下,乐观锁的性能却反而不如悲观锁。

悲观锁:十分悲观,认为总是出现问题,无论干什么都会上锁,再去操作

悲观锁是基于一种悲观的态度类来防止一切数据冲突,它是以一种预防的姿态在修改数据之前把数据锁住,然后再对数据进行读写,在它释放锁之前任何人都不能对其数据进行操作,直到前面一个人把锁释放后下一个人数据加锁才可对数据进行加锁,然后才可以对数据进行操作,一般数据库本身锁的机制都是基于悲观锁的机制实现的;

特点:可以完全保证数据的独占性和正确性,因为每次请求都会先对数据进行加锁, 然后进行数据操作,最后再解锁,而加锁释放锁的过程会造成消耗,所以性能不高;

7.2 配置乐观锁

1 在数据库中增加version字段

2 同步到实体类

在实体类中添加version字段并且加上@Version注解

    @Version
    private Integer version;

3 配置插件

@Configuration
public class MybatisPlusConfig {

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

4 测试使用

测试这个地方就不测试成功的情况,我们直接测试失败的情况,模拟两个线程同时修改数据,这个时候就会到后面修改数据的线程无法修改成功。

    @Test
    void testOptimisticLocker_failure() {
        //模拟多线程实现插队效果
        //线程1
        User user = userMapper.selectById(1L);
        user.setName("罗");
        user.setAge(21);
        //线程2
        User user2 = userMapper.selectById(1L);
        user2.setName("小罗");
        user2.setAge(19);
        userMapper.updateById(user2);   //在这里插队

        userMapper.updateById(user);    //如果没有乐观锁就会覆盖插队线程的值
    }

八、SQL分析与打印

8.1 引入依赖

首先,需要在项目中引入p6spy依赖。

<dependency>
    <groupId>p6spy</groupId>
    <artifactId>p6spy</artifactId>
    <version>3.9.1</version>
</dependency>

8.2 配置

修改application.yml配置文件

spring:
  datasource:
    driver-class-name: com.p6spy.engine.spy.P6SpyDriver
    url: jdbc:p6spy:h2:mem:test
    # 其他数据库配置...

注意这个地方我们需要修改数据库的驱动名和 url 前缀为 jdbc:p6spy 跟着冒号为对应数据库连接地址。

spy.properties

新建文件放在resources目录下,p6spy的配置文件spy.properties包含了多个配置项,以下是一些关键配置的示例:

# 模块列表,根据版本选择合适的配置
modulelist=com.baomidou.mybatisplus.extension.p6spy.MybatisPlusLogFactory,com.p6spy.engine.outage.P6OutageFactory

# 自定义日志格式
logMessageFormat=com.baomidou.mybatisplus.extension.p6spy.P6SpyLogger

# 日志输出到控制台
appender=com.baomidou.mybatisplus.extension.p6spy.StdoutLogger

# 取消JDBC驱动注册
deregisterdrivers=true

# 使用前缀
useprefix=true

# 排除的日志类别
excludecategories=info,debug,result,commit,resultset

# 日期格式
dateformat=yyyy-MM-dd HH:mm:ss

# 实际驱动列表
# driverlist=org.h2.Driver

# 开启慢SQL记录
outagedetection=true

# 慢SQL记录标准(单位:秒)
outagedetectioninterval=2

8.3测试

我们可以看到下面的结果中帮我们统计了执行sql语句的时间。

九、代码生成器

代码生成器可以参考我的另一篇博客

MyBatis-Plus代码生成器(最新版适合新手跟做)-CSDN博客 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2092855.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

源码阅读-SpirngBoot Mybatis 自动配置

MybatisPlusAutoConfiguration ObjectProvider#getIfAvailable ObjectProvider为我们提供了拓展&#xff0c;我们可以自定义一些插件或者类型转换器&#xff0c;同时也可以定义一些Customizer用来配置SqlSessionFactoryBean,MybatisPlusProperties等。 通过源码我们可以看到最…

Redis从入门到入门(上)

1.Redis概述 文章目录 1.Redis概述1.1 什么是Redis1.2 Redis的应用场景 2.Linux下Redis的安装与使用2.1 Redis下载2.2 Redis的启动2.3 Redis配置2.4 连接Redis 1.1 什么是Redis Redis是用C语言开发的一个开源的高性能键值对&#xff08;key-value&#xff09;数据库&#xff0…

数学建模--K-Means聚类分析

目录 1.聚类分析步骤 1.1简单介绍 1.2两个概念 1.3几种距离 1.4更新质心 1.5终止条件 2.归一化处理 3.肘部法则 4.搭建K-Means分析模型 5.选择最佳K值 6.绘制3D图形 1.聚类分析步骤 1.1简单介绍 K-Means聚类分析是属于聚类分析的一种&#xff0c;这个数据机器学习的…

YOLOv8改进 | Neck篇 | YOLOv8引入Slim-Neck(超轻量)

1. Slim-Neck介绍 摘要:目标检测是计算机视觉中重要的下游任务。 对于车载边缘计算平台来说,巨大的模型很难达到实时检测的要求。 而且,由大量深度可分离卷积层构建的轻量级模型无法达到足够的精度。 我们引入了一种新的轻量级卷积技术 GSConv,以减轻模型重量但保持准确性。…

《软件工程导论》(第6版)第4章 形式化说明技术 复习笔记

第4章 形式化说明技术 一、概述 按照形式化的程度&#xff0c;可以把软件工程使用的方法划分成非形式化、半形式化和形式化3类。用自然语言描述需求规格说明&#xff0c;是典型的非形式化方法。用数据流图或实体联系图建立模型&#xff0c;是典型的半形式化方法。所谓形式化…

idea的springboot里面的resources是什么

在IDEA&#xff08;IntelliJ IDEA&#xff09;中的Spring Boot项目中&#xff0c;resources目录扮演着非常重要的角色。这个目录主要用于存放项目的非代码资源&#xff0c;包括但不限于配置文件、静态资源文件&#xff08;如图片、CSS、JavaScript等&#xff09;、模板文件&…

YOLO缺陷检测学习笔记(2)

YOLO缺陷检测学习笔记&#xff08;2&#xff09; 残差连接1. **YOLO 的残差连接结构**2. **YOLO 使用残差连接的目的**3. **YOLO 中的残差块**4. **YOLOv3 和 YOLOv4 的残差连接架构** YOLO网络架构概述1. 特征提取网络2. 预测头&#xff08;Detection Head&#xff09;3. 后处…

Android CCodec Codec2 (五)C2Param - Ⅲ

这一节我们来看看简单参数的定义方式 1、C2SimpleValueStruct Codec2框架提供了模板类C2SimpleValueStruct来帮助我们定义非灵活数组的简单参数。C2SimpleValueStruct的定义如下&#xff1a; template<typename T> struct C2SimpleValueStruct {T value; ///< simpl…

阿里 “通义灵码” 真的 “灵吗”,全保姆级实操

最近很多朋友&#xff0c;都在关注阿里公测的“通义灵码”&#xff0c;我索性也安装了&#xff0c;准备看看它真的有说的那么“灵吗”&#xff1f; 一、安装 官网&#xff1a;https://tongyi.aliyun.com/lingma/ 安装方式&#xff1a;https://tongyi.aliyun.com/lingma/downl…

Redis 集群:引领企业级 NoSQL 数据库新潮流

一 、关系型数据库和 NoSQL 数据库 在当今的数据库领域&#xff0c;关系型数据库和非关系型数据库都占据着重要的地位。 关系型数据库 关系型数据库是建立在关系模型基础上的数据库&#xff0c;它通过表与表之间的关系来存储和管理数据。 特点 数据结构清晰&#xff1a;以二…

“微服务革命”之后...

曾几何时&#xff0c;我记得我的手指疯狂地敲打键盘&#xff0c;与庞大而杂乱的代码库搏斗。那是巨石的时代&#xff0c;代码就像古老的城堡一样&#xff0c;由一块块石头砌成一个令人印象深刻的庞然大物。 几年过去了&#xff0c;时代变了。开发人员口中的流行语变成了“微服…

基于STM32校车安全监控系统的设计(论文+源码+实物)

1 方案设计 根据设计要求&#xff0c;本设计校车安全监控系统的设计以STM32F103单片机作为主控制器&#xff0c;通过MQ传感器实现异常气体的检测&#xff0c;当异常气体浓度异常时会通过继电器打开车窗进行通风&#xff0c;以保证舒适的环境&#xff0c;通过红外传感器用于监…

EXO:StandardNode _process_tensor

目录 EXO:StandardNode _process_tensor EXO:StandardNode _process_tensor 这段代码是在处理某种分片(sharding)逻辑时使用的,特别是在处理大型模型或数据处理任务时,这些任务被分割成多个较小的部分(即分片)来并行处理。这里,代码片段关注于根据特定的调试级别(DEBU…

大数据处理从零开始————1.Hadoop介绍

1. 大数据时代背景 1.1 大数据时代到来 在微信上&#xff0c;随手点的一个赞&#xff1b;在百度上&#xff0c;随手输入的搜素关键词&#xff1b;在健康记录应用上&#xff0c;每天所产生的微信步数这些都是数据。我们每人每天都在产生大量数据。人类近些年所产生的数据比过去…

【设计模式】创建型模式——简单工厂模式

文章目录 一、创建型模式1. 包含模式1.1 工厂模式1.2 建造者模式1.3 原型模式1.4 单例模式 二、工厂模式1. 概要1.1 意图1.2 主要解决问题1.3 何时使用1.4 如何解决1.5 关键代码1.6 使用场景1.7 优点1.8 缺点1.9 简单工厂实现 2. 简单工厂模式2.1 模式结构2.2 实例2.3 模式定义…

Charles激活

简介 Charles激活码计算 激活 Help -> Register Charles 添加 Registered Name 和计算出的 License key 点击 Register Java import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.util.Random; import java.util.Scanner;/*** program: ZK* descriptio…

开关二极管损坏如何判断

系列文章目录 1.元件基础 2.电路设计 3.PCB设计 4.元件焊接 5.板子调试 6.程序设计 7.算法学习 8.编写exe 9.检测标准 10.项目举例 11.职业规划 文章目录 前言1. 外观检查2. 测量正向压降3. 反向电阻测量4. 电路功能测试5. 高压测试6. 加热测试 前言 送给大学毕业后找不到奋…

【Java】 为什么是split 方法按点号分割是用 “ \\. “ ?

前言&#xff1a; 小知识&#xff0c;记录自用and分享。 原因分析&#xff1a; 字符串的很多方法是可以使用正则表达式的&#xff0c;这里就包括了split这个方法。 . 在正则表达式里的意思是通配符&#xff0c;表示&#xff1a;匹配任意单个字符。那么&#xff0c;当使用split…

参加 帆软 BI 上海城市 课堂(08-30培训)

参加 帆软 BI 城市 课堂&#xff08;0830&#xff09;&#xff1a; 由于目前是自由职业&#xff0c;也想学习一下新的知识 。所以参加本次的培训&#xff0c;总的来说还是比较专业。 培训在 上海 帆软的总部 环球港进行。时间是 13:30~17&#xff1a;00 老师很专业。学习中 课…

家具大卖nouhaus独立站拆解丨出海笔记

今天我们分析下一家传统外贸起家的大卖独立站&#xff1a;www.nouhaus.com 品牌背景是恒林股份&#xff08;A股603661&#xff09;旗下&#xff0c;算是有上市公司支持了。据资料显示&#xff1a;恒林股份成立于1998年&#xff0c;一年能卖出1000万件的办公椅和沙发&#xff0…