Mybatis-Plus 查询操作详解

Mybatis-Plus 提供了丰富的查询操作,可以方便地进行单表的 CRUD 操作。以下是一些常用的查询操作:

1. 根据主键查询

使用 BaseMapper 提供的 selectById 方法可以根据主键查询记录,例如:

User user = userMapper.selectById(1);

2. 条件查询

2.1 使用 Lambda 表达式

Mybatis-Plus 支持使用 Lambda 表达式进行条件查询,例如:

List<User> userList = userMapper.selectList(Wrappers.<User>lambdaQuery()
        .eq(User::getAge, 20)
        .like(User::getName, "Tom"));

2.2 使用 QueryWrapper

也可以使用 QueryWrapper 进行条件查询,例如:

QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("age", 20).like("name", "Tom");
List<User> userList = userMapper.selectList(queryWrapper);

3. 分页查询

Mybatis-Plus 提供了分页插件,可以方便地进行分页查询,例如:

Page<User> page = new Page<>(1, 10); // 第一页,每页10条记录
IPage<User> userPage = userMapper.selectPage(page, Wrappers.<User>lambdaQuery().eq(User::getAge, 20));
List<User> userList = userPage.getRecords();

4. 自定义 SQL 查询

除了以上方法,还可以使用 @Select 注解或者 XML 文件中的 SQL 进行自定义查询,例如:

@Select("SELECT * FROM user WHERE age = #{age}")
List<User> selectByAge(@Param("age") Integer age);

其余见官方文档https://baomidou.com/guides/data-interface/#select

分页插件

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

配置方法

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

@Configuration
@MapperScan("scan.your.mapper.package")
public class MybatisPlusConfig {

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

自定义 Mapper 方法中使用分页

你可以通过以下方式在 Mapper 方法中使用分页:

IPage<UserVo> selectPageVo(IPage<?> page, Integer state);
// 或者自定义分页类
MyPage selectPageVo(MyPage page);
// 或者返回 List
List<UserVo> selectPageVo(IPage<UserVo> page, Integer state);

对应的 XML 配置:

<select id="selectPageVo" resultType="xxx.xxx.xxx.UserVo">
  SELECT id,name FROM user WHERE state=#{state}
</select>

如果返回类型是 IPage,则入参的 IPage 不能为 null。如果想临时不分页,可以在初始化 IPage 时 size 参数传入小于 0 的值。 如果返回类型是 List,则入参的 IPage 可以为 null,但需要手动设置入参的 IPage.setRecords(返回的 List)。 如果 XML 需要从 page 里取值,需要使用 page.属性 获取。

其他注意事项

  • 生成 countSql 时,如果 left join 的表不参与 where 条件,会将其优化掉。建议在任何带有 left join 的 SQL 中,都给表和字段加上别名。

  • 在使用多个插件时,请将分页插件放到插件执行链的最后面,以避免 COUNT SQL 执行不准确的问题。

Page 类

Page 类继承了 IPage 类,实现了简单分页模型。如果你需要实现自己的分页模型,可以继承 Page 类或实现 IPage 类。

属性名

类型

默认值

描述

records

List<T>

emptyList

查询数据列表

total

Long

0

查询列表总记录数

size

Long

10

每页显示条数,默认 10

current

Long

1

当前页

orders

List<OrderItem>

emptyList

排序字段信息

optimizeCountSql

boolean

true

自动优化 COUNT SQL

optimizeJoinOfCountSql

boolean

true

自动优化 COUNT SQL 是否把 join 查询部分移除

searchCount

boolean

true

是否进行 count 查询

maxLimit

Long

单页分页条数限制

countId

String

XML 自定义 count 查询的 statementId

通过这些配置和使用方法,你可以轻松地在 MyBatis-Plus 中实现分页查询,提高应用的性能和用户体验。

Mybatis-Plus 删除操作详解

Mybatis-Plus 提供了丰富的删除操作,可以方便地进行单表的删除操作。以下是一些常用的删除操作:

1. 根据主键删除

使用 BaseMapper 提供的 deleteById 方法可以根据主键删除记录,例如:

int result = userMapper.deleteById(1);

2. 条件删除

2.1 使用 Lambda 表达式

Mybatis-Plus 支持使用 Lambda 表达式进行条件删除,例如:

int result = userMapper.delete(Wrappers.<User>lambdaQuery()
        .eq(User::getAge, 20)
        .like(User::getName, "Tom"));

2.2 使用 QueryWrapper

也可以使用 QueryWrapper 进行条件删除,例如:

QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("age", 20).like("name", "Tom");
int result = userMapper.delete(queryWrapper);

3. 物理删除与逻辑删除

Mybatis-Plus 支持物理删除和逻辑删除。通过在实体类中使用 @TableLogic 注解,可以实现逻辑删除。在进行删除操作时,Mybatis-Plus 会自动判断是否需要进行逻辑删除。

4. 自定义 SQL 删除

除了以上方法,还可以使用 @Delete 注解或者 XML 文件中的 SQL 进行自定义删除,例如:

@Delete("DELETE FROM user WHERE age = #{age}")
int deleteByAge(@Param("age") Integer age);

详见官方文档https://baomidou.com/guides/data-interface/#delete

逻辑删除

逻辑删除是指在数据库中不直接删除数据记录,而是通过标记某个字段的值来表示该记录已被删除。在 Mybatis-Plus 中,可以通过使用 @TableLogic 注解来实现逻辑删除。

逻辑删除的工作原理

MyBatis-Plus 的逻辑删除功能会在执行数据库操作时自动处理逻辑删除字段。以下是它的工作方式:

  • 插入:逻辑删除字段的值不受限制。

  • 查找:自动添加条件,过滤掉标记为已删除的记录。

  • 更新:防止更新已删除的记录。

  • 删除:将删除操作转换为更新操作,标记记录为已删除。

例如:

  • 删除update user set deleted=1 where id = 1 and deleted=0

  • 查找select id,name,deleted from user where deleted=0

支持的数据类型

逻辑删除字段支持所有数据类型,但推荐使用 IntegerBooleanLocalDateTime。如果使用 datetime 类型,可以配置逻辑未删除值为 null,已删除值可以使用函数如 now() 来获取当前时间。

使用方法

步骤 1: 配置全局逻辑删除属性

application.yml 中配置 MyBatis-Plus 的全局逻辑删除属性:

mybatis-plus:
  global-config:
    db-config:
      logic-delete-field: deleted # 全局逻辑删除字段名
      logic-delete-value: 1 # 逻辑已删除值
      logic-not-delete-value: 0 # 逻辑未删除值

步骤 2: 在实体类中使用 @TableLogic 注解

在实体类中,对应数据库表的逻辑删除字段上添加 @TableLogic 注解:

import com.baomidou.mybatisplus.annotation.TableLogic;

public class User {
    // 其他字段...

    @TableLogic
    private Integer deleted;
}

1. 如何处理插入操作?

  • 方法一:在数据库中为逻辑删除字段设置默认值。

  • 方法二:在插入数据前手动设置逻辑删除字段的值。

  • 方法三:使用 MyBatis-Plus 的自动填充功能。

2. 删除接口自动填充功能失效怎么办?

  • 方法一:使用 deleteById 方法。

  • 方法二:使用 update 方法,并使用 UpdateWrapper.set(column, value)

  • 方法三:使用 update 方法,并使用 UpdateWrapper.setSql("column=value")

SQL分析与打印

MyBatis-Plus提供了SQL分析与打印的功能,通过集成p6spy组件,可以方便地输出SQL语句及其执行时长。本功能适用于MyBatis-Plus 3.1.0及以上版本。

p6spy简介

p6spy 是一个针对数据库访问进行拦截和记录的工具,它通过代理JDBC驱动程序来工作。这意味着你的应用程序可以像往常一样使用JDBC,而p6spy会在幕后记录所有的SQL语句及其执行时间。这对于开发和调试过程中的SQL优化非常有用。

p6spy不仅限于记录SQL日志,它还提供了一些高级功能,如:

  • 慢SQL检测:通过配置outagedetectionoutagedetectionintervalp6spy可以记录执行时间超过设定阈值的SQL语句。

  • 自定义日志格式:通过logMessageFormat,你可以自定义SQL日志的输出格式,包括时间戳、执行时间、SQL语句等。

  • 日志输出控制appender配置项允许你选择日志输出到控制台、文件或日志系统。

p6spy是一个强大的工具,它为MyBatis-Plus用户提供了便捷的SQL分析与打印功能。通过合理配置,你可以在开发和测试阶段有效地监控和优化SQL语句。

依赖引入

<dependency>
    <groupId>com.github.gavlyukovskiy</groupId>
    <artifactId>p6spy-spring-boot-starter</artifactId>
    <version>1.9.2</version>
</dependency>
decorator:
  datasource:
    p6spy:
      # 日志格式
      log-format: "\ntime:%(executionTime) || sql:%(sql)\n"
      # 自定义日志类
      logging: custom
      custom-appender-class: com.example.testinit.config.StdoutLogger
public class StdoutLogger extends com.p6spy.engine.spy.appender.StdoutLogger {
    public void logText(String text) {
        System.err.println(text);
    }
}

注意事项

  • driver-class-name应配置为p6spy提供的驱动类。

  • url前缀应为jdbc:p6spy,后跟实际的数据库连接地址。

  • 如果打印的SQL为null,请在excludecategories中增加commit

  • 如果批量操作不打印SQL,请去除excludecategories中的batch

  • 对于批量操作打印重复的问题,请使用MybatisPlusLogFactory(3.2.1新增)。

  • 请注意,该插件可能会带来性能损耗,不建议在生产环境中使用。

条件构造器详解

条件构造器是 Mybatis-Plus 中用于构建查询条件的重要工具,可以帮助用户灵活地构造复杂的查询条件。以下是关于条件构造器的详细解释:

主要的 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<User> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("age", 20).like("name", "Tom");
List<User> userList = userMapper.selectList(queryWrapper);

// 使用 UpdateWrapper 构造更新条件
UpdateWrapper<User> updateWrapper = new UpdateWrapper<>();
updateWrapper.set("age", 30).eq("name", "Tom");
int result = userMapper.update(null, updateWrapper);

// 使用 LambdaQueryWrapper 构造查询条件
List<User> userList = userMapper.selectList(new LambdaQueryWrapper<User>()
        .eq(User::getAge, 20)
        .like(User::getName, "Tom"));

// 使用 LambdaUpdateWrapper 构造更新条件
int result = userMapper.update(null, new LambdaUpdateWrapper<User>()
        .set(User::getAge, 30)
        .eq(User::getName, "Tom"));

常用方法

条件构造器提供了丰富的方法来构建查询条件,常用的方法包括:

  • eq:等于

  • ne:不等于

  • gt:大于

  • ge:大于等于

  • lt:小于

  • le:小于等于

  • like:模糊查询

  • in:包含在某个集合中

  • notIn:不包含在某个集合中

  • isNull:字段为 NULL

  • isNotNull:字段不为 NULL

  • orderBy:排序

复杂条件构造

条件构造器还支持复杂的条件构造,可以通过 andor 等方法组合多个条件。例如:

QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("age", 20)
        .and(i -> i.like("name", "Tom").or().like("name", "Jerry"));
List<User> userList = userMapper.selectList(queryWrapper);

在上述代码中,通过 and 方法组合了年龄为 20 且姓名包含 "Tom" 或 "Jerry" 的条件。

详见官方文档条件构造器 | MyBatis-Plus (baomidou.com)

代码生成器

安装

<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-generator</artifactId>
    <version>3.5.7</version>
</dependency>

由于代码生成器用到了模板引擎,请自行引入您喜好的模板引擎。MyBatis-Plus Generator 支持如下模板引擎:

  • VelocityTemplateEngine(Default)

  • FreemarkerTemplateEngine

  • BeetlTemplateEngine

  • EnjoyTemplateEngine

如果您还想使用或适配其他模板引擎,可自行继承 AbstractTemplateEngine 并参考其他模板引擎实现自定义。

生成方式

代码生成器目前支持两种生成方式:

  1. DefaultQuery (元数据查询)

    • 优点: 根据通用接口读取数据库元数据相关信息,对数据库通用性较好。

    • 缺点: 依赖数据库厂商驱动实现。

    • 备注: 默认方式,部分类型处理可能不理想。

  2. SQLQuery (SQL查询)

    • 优点: 需要根据数据库编写对应表、主键、字段获取等查询语句。

    • 缺点: 通用性不强,同数据库厂商不同版本可能会存在兼容问题(例如,H2数据库只支持1.X版本)。

    • 备注: 后期不再维护。

快速生成

在 CodeGenerator 中的 main 方法中直接添加生成器代码,并进行相关配置,然后直接运行即可生成代码。

public static void main(String[] args) {
    FastAutoGenerator.create("url", "username", "password")
            .globalConfig(builder -> {
                builder.author("baomidou") // 设置作者
                        .enableSwagger() // 开启 swagger 模式
                        .outputDir("D://"); // 指定输出目录
            })
            .dataSourceConfig(builder ->
                    builder.typeConvertHandler((globalConfig, typeRegistry, metaInfo) -> {
                        int typeCode = metaInfo.getJdbcType().TYPE_CODE;
                        if (typeCode == Types.SMALLINT) {
                            // 自定义类型转换
                            return DbColumnType.INTEGER;
                        }
                        return typeRegistry.getColumnType(metaInfo);
                    })
            )
            .packageConfig(builder ->
                    builder.parent("com.baomidou.mybatisplus.samples.generator") // 设置父包名
                            .moduleName("system") // 设置父包模块名
                            .pathInfo(Collections.singletonMap(OutputFile.xml, "D://")) // 设置mapperXml生成路径
            )
            .strategyConfig(builder ->
                    builder.addInclude("t_simple") // 设置需要生成的表名
                            .addTablePrefix("t_", "c_") // 设置过滤表前缀
            )
            .templateEngine(new FreemarkerTemplateEngine()) // 使用Freemarker引擎模板,默认的是Velocity引擎模板
            .execute();
}

交互式生成

交互式生成在运行之后,会提示您输入相应的内容,等待配置输入完整之后就自动生成相关代码。

public static void main(String[] args) {
    FastAutoGenerator.create("url", "username", "password")
            // 全局配置
            .globalConfig((scanner, builder) -> builder.author(scanner.apply("请输入作者名称?")))
            // 包配置
            .packageConfig((scanner, builder) -> builder.parent(scanner.apply("请输入包名?")))
            // 策略配置
            .strategyConfig((scanner, builder) -> builder.addInclude(getTables(scanner.apply("请输入表名,多个英文逗号分隔?所有输入 all")))
                    .entityBuilder()
                    .enableLombok()
                    .addTableFills(
                            new Column("create_time", FieldFill.INSERT)
                    )
                    .build())
            // 使用Freemarker引擎模板,默认的是Velocity引擎模板
            .templateEngine(new FreemarkerTemplateEngine())
            .execute();
}

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