【MyBatis-Plus】MyBatis进阶使用

目录

一、MyBatis-Plus简介

[1.1 介绍](#1.1 介绍)

[1.2 优点](#1.2 优点)

[1.3 结构](#1.3 结构)

二、MyBatis-Plus基本使用

[2.1 配置](#2.1 配置)

[2.2 代码生成](#2.2 代码生成)

[2.3 CRUD接口测试](#2.3 CRUD接口测试)

三、MyBatis-Plus策略详解

[3.1 主键生成策略](#3.1 主键生成策略)

[3.2 雪花ID生成器](#3.2 雪花ID生成器)

[3.3 字段自动填充策略](#3.3 字段自动填充策略)

[3.4 逻辑删除](#3.4 逻辑删除)

四、MyBatis-Plus插件使用

[4.1 乐观锁插件](#4.1 乐观锁插件)

[4.1.1 什么是乐观锁和悲观锁?](#4.1.1 什么是乐观锁和悲观锁?)

[4.1.2 乐观锁实现](#4.1.2 乐观锁实现)

[4.2 分页插件](#4.2 分页插件)


一、MyBatis-Plus简介

1.1 介绍

MyBatis-Plus (opens new window)(简称 MP)是一个 MyBatis (opens new window)的增强工具,在 MyBatis 的基础上只做增强不做改变(也就是说mybatis有的plus都有),为简化开发、提高效率而生。

1.2 优点

  • 代码生成器: MyBatis-Plus 提供了一个代码生成器,可以根据数据库表自动生成实体类、mapper 接口和 XML 映射文件,大大提高了开发效率。
  • CRUD 操作简化: MyBatis-Plus 提供了一系列 CRUD 操作的方法,例如 save()saveOrUpdate()updateById()deleteById(),这些方法可以简化常见的数据库操作。
  • 分页查询: MyBatis-Plus 提供了分页查询的支持,您可以使用 PageHelper 插件或者内置的分页功能来实现分页查询。
  • 性能优化: MyBatis-Plus 提供了二级缓存和批量操作的支持,可以提高数据库操作的性能。
  • 代码简洁: MyBatis-Plus 提供了许多有用的注解,可以简化 MyBatis 的代码,例如 @TableId@TableField@TableLogic
  • 可扩展性: MyBatis-Plus 提供了许多扩展点,您可以通过自定义插件或拦截器来扩展 MyBatis-Plus 的功能。

如果您正在使用 MyBatis,我强烈建议您使用 MyBatis-Plus !!!

1.3 结构

  1. 核心模块(Core Module):核心模块包含了Mybatis-Plus的核心功能和基本组件。它提供了通用的Mapper接口、通用的CRUD方法的实现、条件构造器、分页插件等核心功能的实现。核心模块是整个框架的基础,其他模块都依赖于它。

  2. 代码生成器(Code Generator):代码生成器是一个可选的辅助工具,用于根据数据库表结构自动生成实体类、Mapper接口、XML映射文件等基础代码。通过代码生成器,开发者可以快速生成基础代码,减少手写重复代码的工作量。

  3. 扩展模块(Extension Module):扩展模块是Mybatis-Plus提供的一些扩展功能和增强组件的集合。它包括了一些常用的增强功能,如逻辑删除、字段自动填充、乐观锁等。开发者可以通过引入扩展模块,轻松地增加这些功能的支持。

  4. 注解(annotation):用于对实体类、Mapper接口以及SQL语句进行配置和标记。通过使用注解,可以简化配置文件的编写,提高代码的可读性和可维护性。

简介 | MyBatis-PlusMyBatis-Plus 官方文档https://baomidou.com/pages/24112f/#%E7%89%B9%E6%80%A7

二、MyBatis-Plus基本使用

2.1 配置

1、导入pom依赖

java 复制代码
        <!--mybatis-plus-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.5.2</version>
        </dependency>
        <!--自动生成模版引擎-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-freemarker</artifactId>
        </dependency>
        <!--代码生成器-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-generator</artifactId>
            <version>3.5.2</version>
        </dependency>

2、配置数据库与mybatis-plus

XML 复制代码
server:
  port: 8080
spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    username: root
    password: 123456
    url: jdbc:mysql://localhost:3306/bookshop

mybatis-plus:
  # Mybatis Mapper所对应的XML位置
  mapper-locations: classpath:mapper/*.xml
  # 别名包扫描路径
  type-aliases-package: com.ycxw.mybatis_test.model
  # 是否开启自动驼峰命名规则(camel case)映射
  configuration:
    map-underscore-to-camel-case: true
logging:
  level:
    com.ycxw.mybatis_test.mapper: debug

2.2 代码生成

1、添加代码生成器

java 复制代码
package com.ycxw.mybatis_test.config;

import com.baomidou.mybatisplus.generator.FastAutoGenerator;
import com.baomidou.mybatisplus.generator.config.DataSourceConfig;
import com.baomidou.mybatisplus.generator.config.OutputFile;
import com.baomidou.mybatisplus.generator.config.rules.DateType;
import com.baomidou.mybatisplus.generator.engine.FreemarkerTemplateEngine;
import lombok.extern.slf4j.Slf4j;

import java.util.Arrays;
import java.util.Collections;
import java.util.List;

@Slf4j
public class MySQLGenerator {

    private final static String URL = "jdbc:mysql://localhost:3306/bookshop";
    private final static String USERNAME = "root";
    private final static String PASSWORD = "123456";

    private final static DataSourceConfig.Builder DATA_SOURCE_CONFIG =
            new DataSourceConfig.Builder(URL, USERNAME, PASSWORD);

    public static void main(String[] args) {
        FastAutoGenerator.create(DATA_SOURCE_CONFIG)
                .globalConfig(
                        (scanner, builder) ->
                                builder.author("云村小威")
                                        .outputDir(System.getProperty("user.dir") + "\\src\\main\\java")
                                        .commentDate("yyyy-MM-dd")
                                        .dateType(DateType.TIME_PACK)
                                        .disableOpenDir()
                )
                .packageConfig((builder) ->
                        builder.parent("com.ycxw.mybatis_test")
                                .entity("entity")
                                .service("service")
                                .serviceImpl("service.impl")
                                .mapper("mapper")
                                .xml("mapper.xml")
                                .pathInfo(Collections.singletonMap(OutputFile.xml, System.getProperty("user.dir") + "\\src\\main\\resources\\mapper"))
                )
                .injectionConfig((builder) ->
                        builder.beforeOutputFile(
                                (a, b) -> log.warn("tableInfo: " + a.getEntityName())
                        )
                )
                .strategyConfig((scanner, builder) ->
                        builder.addInclude(getTables(scanner.apply("请输入表名,多个英文逗号分隔?所有输入 all")))
                                .addTablePrefix("tb_", "t_", "lay_", "meeting_", "sys_")
                                .entityBuilder()
                                .enableChainModel()
                                .enableLombok()
                                .enableTableFieldAnnotation()
                                .controllerBuilder()
                                .enableRestStyle()
                                .enableHyphenStyle()
                                .build()
                )
                .templateEngine(new FreemarkerTemplateEngine())
                .execute();
    }

    protected static List<String> getTables(String tables) {
        return "all".equals(tables) ? Collections.emptyList() : Arrays.asList(tables.split(","));
    }

}

这段 Java 代码是用于自动生成 MyBatis-Plus 代码的。它使用 FastAutoGenerator 来生成实体类、服务类、服务实现类、映射器接口和映射器 XML 文件。

代码主要做了以下事情:

  1. 定义数据源配置,指定数据库 URL、用户名和密码。
  2. 创建 FastAutoGenerator 实例,并指定数据源配置。
  3. 配置全局配置,包括作者、输出目录、注释日期和日期类型等。
  4. 配置包配置,包括父包名、实体包名、服务包名、服务实现包名、映射器包名和 XML 包名等。
  5. 配置注入配置,在输出文件之前记录表信息。
  6. 配置策略配置,包括要生成的表名、要移除的前缀、实体类配置、控制器类配置等。
  7. 指定模板引擎为 Freemarker。
  8. 执行代码生成。

当运行此代码时,它将生成 MyBatis-Plus 代码,并将其输出到指定目录。

3、运行该类输入需要生成的数据表名

tips:

在这里可以发现除了entity实体包下的Book类其他的类和接口都是空了,其实它自带了增删改查等方法的,可以直接调用,如果需要更复杂的数据接口可以自行编写。

最后注意添加mapper注释交给spring管理,并在启动器添加扫描mapper的注释:

2.3 CRUD接口测试

java 复制代码
package com.ycxw.mybatis_test.controller;

import com.github.yitter.idgen.YitIdHelper;
import com.ycxw.mybatis_test.entity.Book;
import com.ycxw.mybatis_test.service.IBookService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

/**
 * <p>
 * 书本信息表 前端控制器
 * </p>
 *
 * @author 云村小威
 * @since 2023-12-16
 */
@RestController
@RequestMapping("/book")
public class BookController {
    @Autowired
    private IBookService bookService;

    @GetMapping("/list")
    public Object list() {
        return bookService.list();
    }

    @PostMapping("/save")
    public Object add(Book book) {
        /*设置雪花id*/
        book.setId(YitIdHelper.nextId());
        return bookService.save(book);
    }

    @PatchMapping("/update")
    public Object edit(Book book) {
        return bookService.updateById(book);
    }

    @DeleteMapping("/{id}")
    public Object del(@PathVariable Long id) {
        return bookService.removeById(id);
    }

}

查询测试:

等等。。。

三、MyBatis-Plus策略详解

3.1 主键生成策略

java 复制代码
@Getter
@Setter
@Accessors(chain = true)
@TableName("t_book")
public class Book implements Serializable {

    private static final long serialVersionUID = 1L;

    /**
     * 书本编号
     */
    @TableId(value = "id", type = IdType.AUTO)
    private Long id;
  • @TableId注解属性
属性 类型 必须指定 默认值 描述
value String "" 主键字段名
type Enum IdType.NONE 指定主键类型

IdType属性:

描述
AUTO 数据库 ID 自增
NONE 无状态,该类型为未设置主键类型(注解里等于跟随全局,全局里约等于 INPUT)
INPUT insert 前自行 set 主键值
ASSIGN_ID 分配 ID(主键类型为 Number(Long 和 Integer)或 String)(since 3.3.0),使用接口IdentifierGenerator的方法nextId(默认实现类为DefaultIdentifierGenerator雪花算法)
ASSIGN_UUID 分配 UUID,主键类型为 String(since 3.3.0),使用接口IdentifierGenerator的方法nextUUID(默认 default 方法)
ID_WORKER 分布式全局唯一 ID 长整型类型(please use ASSIGN_ID)
UUID 32 位 UUID 字符串(please use ASSIGN_UUID)
ID_WORKER_STR 分布式全局唯一 ID 字符串类型(please use ASSIGN_ID)

3.2 雪花ID生成器

提示

自 3.3.0 开始,默认使用雪花算法+UUID(不含中划线)

自定义示例工程:

方法 主键生成策略 主键类型 说明
nextId ASSIGN_ID,ID_WORKER,ID_WORKER_STR Long,Integer,String 支持自动转换为 String 类型,但数值类型不支持自动转换,需精准匹配,例如返回 Long,实体主键就不支持定义为 Integer
nextUUID ASSIGN_UUID,UUID String 默认不含中划线的 UUID 生成

示例:

为什么要用ID生成器?

UUID 的主要优点是:

  • 它能够生成真正随机的标识符,不需要依赖于数据库或其他外部服务。
  • 它可以跨不同的系统和平台使用,而不需要进行任何修改。

Snowflake ID 的主要优点是:

  • 它能够生成递增的唯一标识符。
  • 它可以分布式生成,因此可以扩展到大型系统。

首先UUID它是随机生成的,生成的id可能跨度较大。在性能方面,Snowflake ID 优于 UUID。Snowflake ID 的生成速度更快,存储空间更小,并且具备递增性。但是,Snowflake ID 需要依赖于数据库或其他外部服务来存储和管理元数据,并且需要进行额外的配置和维护。

在实际应用中,可以选择使用 UUID 或 Snowflake ID,具体取决于具体的需求和场景。

3.3 字段自动填充策略

  • @TableField 字段注解
java 复制代码
    /**
     * 书本名称
     */
    @TableField(value = "bookname",fill = FieldFill.INSERT)
    private String bookname;
属性 类型 必须指定 默认值 描述
value String "" 数据库字段名
exist boolean true 是否为数据库表字段
fill Enum FieldFill.DEFAULT 字段自动填充策略

Fill 属性值

描述
DEFAULT 默认不处理
INSERT 插入时填充字段
UPDATE 更新时填充字段
INSERT_UPDATE 插入和更新时填充字段

在这里我们可以设置该字段是否自动填充以及什么时候填充,一般在时间字段设置自动填充方便查询数据的插入和更新时间,当然下面只是一个简单的案例:

我们还需要编写 自定义实现类 MyMetaObjectHandler

java 复制代码
package com.ycxw.mybatis_test.config;

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

@Slf4j
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {

    @Override
    public void insertFill(MetaObject metaObject) {
        log.info("插入时自动填充 ....");
        this.strictInsertFill(metaObject, "bookname", String.class, "暂无"); // 起始版本 3.3.0(推荐使用)
    }

    @Override
    public void updateFill(MetaObject metaObject) {
        log.info("修改时自动填充 ....");
        this.strictUpdateFill(metaObject, "updateTime", String.class, "未填写修改名称"); // 起始版本 3.3.0(推荐)
    }
}

定义了自动填充,我们在新增或修改时就可以不为该字段赋值。

3.4 逻辑删除

为什么要使用逻辑删除?

  1. **保留历史记录:**通过逻辑删除,可以保留项目的完整历史记录,而无需物理删除数据。这对于调试、审计和数据分析非常有用。
  2. **恢复数据:**如果项目数据被意外删除,通过逻辑删除可以轻松恢复。而物理删除的数据则无法恢复。
  3. **性能优化:**逻辑删除可以提高项目的性能,因为不需要物理删除数据。这对于大型项目或具有大量数据的项目尤为重要。
  4. **安全性:**逻辑删除可以提高项目的安全性,因为不需要物理删除数据,可以防止不必要的数据泄露。
  5. **合规性:**一些行业或法规可能要求保留数据一段特定时间。逻辑删除可以帮助项目遵守这些规定,而无需永久存储数据。

通过mybatis-plus扩展的逻辑删除功能,再调用删除接口时将删除自动转为修改。将数据状态修改为不可查询到(前提是该表含有特定的状态列)

++使用方法:++

1、全局配置

2、局部配置

这里不可以配置@TableLogic注解,但必须指定@TableField并设置对应数据库中的字段名。

3、测试

例如删除第一条数据:

再次查看该数据表:

调用查询测试:

可以发现刚才删除的数据将不能查到,而数据库仍存在。

四、MyBatis-Plus插件使用

4.1 乐观锁插件

4.1.1 什么是乐观锁和悲观锁?

乐观锁悲观锁是两种不同的并发控制机制,用于防止多用户同时修改同一份数据时产生数据不一致的问题。

  • 乐观锁假设在并发操作时,数据不会被其他用户修改,因此在执行更新操作前不需要对数据加锁。在提交更新时,会检查数据是否已经被修改。如果数据已经被修改,则会抛出异常,要求用户重新获取数据并重新提交更新。
  • 悲观锁假设在并发操作时,数据可能会被其他用户修改,因此在执行更新操作前需要先对数据加锁。在更新操作执行完成后,锁才会被释放。这样可以确保在数据更新时,不会被其他用户修改。

乐观锁的优点:

  • 性能好,因为在大多数情况下不需要加锁。
  • 可伸缩性好,因为不需要维护锁状态。
  • 不会出现死锁。

乐观锁的缺点:

  • 可能出现数据不一致的问题,因为在提交更新时,数据可能已经被其他用户修改。
  • 需要应用程序代码来处理并发冲突。

悲观锁的优点:

  • 可以保证数据一致性,因为在更新数据时,其他用户不能修改数据。
  • 不需要应用程序代码来处理并发冲突。

悲观锁的缺点:

  • 性能差,因为在更新数据时需要加锁。
  • 可伸缩性差,因为需要维护锁状态。
  • 可能出现死锁。

4.1.2 乐观锁实现

当要更新一条记录的时候,希望这条记录没有被别人更新。

乐观锁实现方式:

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

所以先给需要加锁的表添加version列,同时在实体类中加上@Version注解

java 复制代码
@Version
private Integer version;

1、配置插件

java 复制代码
package com.ycxw.mybatis_test.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;

// Spring Boot 方式
@Configuration
public class MybatisPlusConfig {

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

2、编写修改接口测试

java 复制代码
    @PutMapping("/edit")
    public Object update(){
        Book b1 = bookService.getById("16");
        b1.setPrice(133F);
        bookService.updateById(b1);
        return "1";
    }

**调用查询接口可以看到:**首先查询该数据,在修改version值并判断该数据的version值是否等于当前的version值。

可以看到version值已变成2了,没修改一次就会加1。这就意味着当我修改改数据时version变为了2,而其他人同时修改该数据时首先查询到的是1,而我已经修改为2,他人再修改时条件已不再成立将无法修改。

以下案例进行验证:

java 复制代码
    @PutMapping("/edit")
    public Object update(){
        /*模仿两人同时修改不同数据时*/
        Book b1 = bookService.getById("16");
        b1.setPrice(133F);
        Book b2 = bookService.getById("16");
        b2.setPrice(210F);

        bookService.updateById(b1);
        bookService.updateById(b2);
        return "1";
    }

可以看到只有第一条修改成功了,且version变为3(首先查询的值为2,修改后自动变为3)。如果他人同时于此修改时,而version判断不成立则无法修改。

4.2 分页插件

1、首先添加分页插件配置,与乐观锁配置位置一样,该类可以同时配置多个插件。

java 复制代码
package com.ycxw.mybatis_test.config;

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

// Spring Boot 方式
@Configuration
public class MybatisPlusConfig {

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

        return mybatisPlusInterceptor;
    }
}

2、编写查询接口测试

java 复制代码
    @GetMapping("/list")
    public Object list() {
        //简单的分页查询
        Page<Book> page = new Page<>(1,5);
        Page<Book> res = bookService.page(page, wrapper);
        res.getTotal();//数据总数
        res.getPages();//数据总页数
        return res.getRecords();
    }

以上只是一个简单的查询案例,mp通过page类实现了**简单分页模型。需要进行条件查询则要了解查询构造器:**

QueryWrapper是Mybatis-Plus提供的一个条件构造器,用于快速构建SQL查询语句的条件部分。

QueryWrapper的语法类似于Mybatis的XML文件中的where标签,其最终会被转换为SQL语句的条件部分。我们可以通过链式调用的方式,不断添加查询条件,从而构建出复杂的查询条件。

代码示例:

java 复制代码
    @GetMapping("/list")
    public Object list(@RequestParam(value = "bookname", required = false, defaultValue = "")  String bookname) {
        //查询条件
        QueryWrapper<Book> wrapper = new QueryWrapper<>();
        /**
         * 设置查询条件 bookname like %name%
         * 如:
         *    warpper.eq("price",100,300) == price 在 100 ~ 300 之间
         *    ...
         */
        wrapper.like("bookname",bookname);

        //分页条件条件
        Page<Book> page = new Page<>(1,5);
        Page<Book> res = bookService.page(page, wrapper);
        res.getTotal();//数据总数
        res.getPages();//数据总页数
        return res.getRecords();
    }

可以看到通过like方法指定注入需要查询的列名,自定拼接到sql中

相关推荐
不务专业的程序员--阿飞5 分钟前
【SQL 如何解锁递归】
java·数据库·sql
嘵奇12 分钟前
Spring Boot拦截器详解:原理、实现与应用场景
java·spring boot·后端
八股文领域大手子12 分钟前
Java死锁排查:线上救火实战指南
java·开发语言·面试
jackson凌19 分钟前
【Java学习笔记】finalize方法
java·笔记·学习
fanTuanye22 分钟前
redis 缓存穿透,缓存击穿,缓存雪崩
java·redis·缓存
神秘的t39 分钟前
Spring Web MVC————入门(2)
java·spring·mvc
开开心心就好1 小时前
高效全能PDF工具,支持OCR识别
java·前端·python·pdf·ocr·maven·jetty
冷心笑看丽美人1 小时前
Spring MVC数据绑定和响应 你了解多少?
java·spring·mvc
XQ丶YTY1 小时前
大二java第一面小厂(挂)
java·开发语言·笔记·学习·面试
呆萌很1 小时前
基于 Spring Boot 瑞吉外卖系统开发(十四)
spring boot