MyBatis-Plus讲解

MyBatisPlus是MyBatis的增强工具,提供极简开发、无侵入式增强和丰富功能,包括内置CRUD方法、条件构造器、分页插件、主键生成策略、逻辑删除和乐观锁等。通过BaseMapper接口可自动获得常用方法,支持Lambda表达式构建查询条件,并配备代码生成器提高效率。还提供自动填充、分页查询和乐观锁插件等实用功能,简化开发流程。

作者头像
LumiBee
22 天前 · 55 0
分享

介绍

MyBatis-Plus 是一个 MyBatis 的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。

特性

  • 极简开发: 对MyBatis进行了大量的封装,尤其对单表操作,提供了丰富的内置的CRUD方法

  • 无侵入:只做增强不做改变,完全兼容原生MyBatis的所有功能.引入它不会对现有工程产生影响,甚至可以混用MyBatis-plus的功能和原生MyBatis XML配置

  • 强大的功能:除了基础的CRUD,还提供了诸如条件构造器,分页插件,主键生成策略,逻辑删除,乐观锁,性能分析插件等一系列实用功能

  • 高效便捷:通过代码生成器可以快速生成实体,Mapper,Service等代码,大大提高开发效率

核心概念与特性

BaseMapper<T> - 通用 CRUD 接口

这是MyBatis-Plus的精髓之一,只需要让Mapper接口继承BaseMapper<Entity>,就可以自动获得以下常用方法,而无需编写任何SQL:

  • insert(T entity): 插入一条记录。
  • deleteById(Serializable id): 根据主键 ID 删除记录。
  • deleteByMap(Map<String, Object> columnMap): 根据 columnMap 条件删除记录。
  • delete(Wrapper<T> wrapper): 根据 Wrapper 条件删除记录。
  • deleteBatchIds(Collection<? extends Serializable> idList): 批量删除。
  • updateById(T entity): 根据主键 ID 更新记录(实体中非 null 字段会参与更新)。
  • update(T entity, Wrapper<T> updateWrapper): 根据 Wrapper 条件更新记录。
  • selectById(Serializable id): 根据主键 ID 查询一条记录。
  • selectBatchIds(Collection<? extends Serializable> idList): 根据主键 ID 批量查询。
  • selectByMap(Map<String, Object> columnMap): 根据 columnMap 条件查询多条记录。
  • selectOne(Wrapper<T> queryWrapper): 根据 Wrapper 条件查询一条记录(结果多于 1 条会报错)。
  • selectCount(Wrapper<T> queryWrapper): 根据 Wrapper 条件查询总记录数。
  • selectList(Wrapper<T> queryWrapper): 根据 Wrapper 条件查询多条记录列表。
  • selectMaps(Wrapper<T> queryWrapper): 根据 Wrapper 条件查询多条记录,结果为 List<Map<String, Object>>。
  • selectObjs(Wrapper<T> queryWrapper): 根据 Wrapper 条件查询多条记录,只返回第一个字段的值。
  • selectPage(IPage<T> page, Wrapper<T> queryWrapper): 分页查询。
  • selectMapsPage(IPage<Map<String, Object>> page, Wrapper<T> queryWrapper): 分页查询,结果为 Map。

实体注解(Entity Annotations)

  • @TableName("table_name"): 指定实体类对应的数据库表名。如果表名与实体类名符合驼峰转下划线规则(如 UserInfo -> user_info),则可以省略。

  • @TableId(value = "column_name", type = IdType.xxx):主键相关的配置,如果主键名字就是id并且不需要主键策略可以省略

    • value: 指定数据库表的主键列名。

    • type: 指定主键策略:

      • IdType.AUTO: 数据库ID自增。

      • IdType.NONE: 未设置主键类型(由 MyBatis 自身或手动赋值).

      • IdType.INPUT: 用户手动输入ID。

      • IdType.ASSIGN_ID: 分配ID(主键类型为 Number(Long 和 Integer) 或 String),MP 默认使用雪花算法生成。

      • IdType.ASSIGN_UUID: 分配UUID(主键类型为 String).

  • @TableField(value = "column_name" , ...): 指定属性对应的数据库表字段名。当属性名与字段名不符合驼峰转下划线规则时使用。

    • exist = false: 表示该属性不是数据库表字段。

    • select = false: 查询时不返回该字段的值。

    • fill = FieldFill.xxx: 自动填充策略(如 INSERT, UPDATE, INSERT_UPDATE),需配合元数据处理器使用。

  • @Version: 标记乐观锁版本号字段。

  • @TableLogic: 标记逻辑删除字段(及其未删除值和已删除值)。

条件构造器(Wrapper)

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

​ 在 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 表达式来指定更新字段和条件,同样避免了硬编码字段名的问题。

常用方法示例

  • eq(R column, Object val): 等于 column = val
  • ne(R column, Object val): 不等于 column != val
  • gt(R column, Object val): 大于 column > val
  • ge(R column, Object val): 大于等于 column >= val
  • lt(R column, Object val): 小于 column < val
  • le(R column, Object val): 小于等于 column <= val
  • like(R column, Object val): 模糊查询 column LIKE '%val%'
  • likeLeft(R column, Object val): 右模糊 column LIKE '%val'
  • likeRight(R column, Object val): 左模糊 column LIKE 'val%'
  • notLike(R column, Object val): column NOT LIKE '%val%'
  • isNull(R column): column IS NULL
  • isNotNull(R column): column IS NOT NULL
  • in(R column, Collection<?> coll): column IN (v1, v2, ...)
  • notIn(R column, Collection<?> coll): column NOT IN (v1, v2, ...)
  • between(R column, Object val1, Object val2): column BETWEEN val1 AND val2
  • orderByAsc(R column): 升序排序 ORDER BY column ASC
  • orderByDesc(R column): 降序排序 ORDER BY column DESC
  • last(String lastSql): 拼接 SQL 到最后(慎用,有 SQL 注入风险)
  • and(Consumer<LambdaQueryWrapper<T>> consumer): AND (子查询条件)
  • or(Consumer<LambdaQueryWrapper<T>> consumer): OR (子查询条件)
  • select(R... columns): 指定查询哪些字段。

更全的可以参考官网

分页插件(PaginationInnerInterceptor)

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

​ 如果想要使用插件还需要单独引入mybatis-plus-jsqlparser依赖:

<!-- jdk 11+ 引入可选模块 -->
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-jsqlparser</artifactId>
</dependency>

<!-- jdk 8+ 引入可选模块 -->
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-jsqlparser-4.9</artifactId>
</dependency>

​ 可以通过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;
    }
}

属性介绍

PaginationInnerInterceptor 提供了以下属性来定制分页行为:

| 属性名 | 类型 | 默认值 | 描述 | | :------: | :------: | :----: | :----------------------: | | overflow | boolean | false | 溢出总页数后是否进行处理 | | maxLimit | Long | | 单页分页条数限制 | | dbType | DbType | | 数据库类型 | | dialect | IDialect | | 方言实现类 |

使用

在Service层或Controller层创建Page<T>对象,并将其作为参数传递给Mapper的selectPage方法

// Service 层
public Page<ArticleDTO> getArticles(long pageNum, long pageSize) {
  	//1. 创建分页请求对象(用于向Mapper传递分页参数)
    Page<Article> pageRequest = new Page<>(pageNum, pageSize); // pageNum 从1开始
    //2. 创建LambdaQueryWrapper用于构建查询条件
    LambdaQueryWrapper<Article> wrapper = new LambdaQueryWrapper<>();
    wrapper.orderByDesc(Article::getGmtPublished);
  	//3. 执行分页查询,得到包含实体对象的分页结果
    // 返回的articleEntityPage包含了当前页Article实体列表以及总记录数,总页数等分页信息
    Page<Article> articleEntityPage = articleMapper.selectPage(pageRequest, wrapper);
    //4. 将 Page<Article> 转换为 Page<ArticleDTO>
    List<ArticleDTO> articleDTOList = new ArrayList<>();
  	    if (articleEntityPage.getRecords() != null && !articleEntityPage.getRecords().isEmpty()) {
        for (Article article : articleEntityPage.getRecords()) {
            // 这里假设有一个 convertToArticleDTO(Article article) 的方法
            // 这个方法负责将单个 Article 实体转换为 ArticleDTO,
            // 可能包括处理作者信息、日期格式化等。
            ArticleDTO dto = convertToArticleDTO(article); // 需要实现这个转换方法
            articleDTOList.add(dto);
        }
    }

    //5. 创建并填充最终返回的 Page<ArticleDTO> 对象
    // 参数1: articleEntityPage.getCurrent() - 获取原始分页结果的当前页码
    // 参数2: articleEntityPage.getSize() - 获取原始分页结果的每页大小
    // 参数3: articleEntityPage.getTotal() - 获取原始分页结果的总记录数
    Page<ArticleDTO> dtoPage = new Page<>(articleEntityPage.getCurrent(), articleEntityPage.getSize(), articleEntityPage.getTotal());
    //6. 设置转换后的DTO列表到新的分页对象中
    dtoPage.setRecords(articleDTOList);
    //7. 设置总页数到新的分页对象中
    dtoPage.setPages(articleEntityPage.getPages());
    return dtoPage;
}

Page类

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

| 属性名 | 类型 | 默认值 | 描述 | | :--------------------: | :-------------: | :-------: | :-----------------------------------------: | | records | List | emptyList | 查询数据列表 | | total | Long | 0 | 查询列表总记录数 | | size | Long | 10 | 每页显示条数,默认 10 | | current | Long | 1 | 当前页 | | orders | List | emptyList | 排序字段信息 | | optimizeCountSql | boolean | true | 自动优化 COUNT SQL | | optimizeJoinOfCountSql | boolean | true | 自动优化 COUNT SQL 是否把 join 查询部分移除 | | searchCount | boolean | true | 是否进行 count 查询 | | maxLimit | Long | | 单页分页条数限制 | | countId | String | | XML 自定义 count 查询的 statementId |

乐观锁插件(Optimistic Lock Plugin)

​ 乐观锁是一种并发控制机制,用于确保在更新记录时,该记录未被其他事务修改。MyBatis-Plus 提供了 OptimisticLockerInnerInterceptor 插件,使得在应用中实现乐观锁变得简单。

实现

  1. 在数据库表中添加一个版本号字段(通常是version,类型为Integer或Long)
  2. 在实体类中对应属性上添加@Version注解:
@Version
private Integer version;
  1. 配置OptimisticLockerInnerInterceptor 插件:
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
    MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
    interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor()); // 添加乐观锁插件
    // ... 可以继续添加其他插件,如分页插件
    return interceptor;
}

原理

乐观锁的实现通常包括以下步骤:

  1. 读取记录时,获取当前的版本号(version)。
  2. 在更新记录时,将这个版本号一同传递。
  3. 执行更新操作时,设置 version = newVersion 的条件为 version = oldVersion
  4. 如果版本号不匹配,则更新失败。

逻辑删除(Logical Delete)

​ 数据并不会真正从数据库中物理删除,而是通过一个字段来标记其为“已删除”状态

实现

  1. 在数据库表中添加一个逻辑删除标记字段(如 deleted,类型通常为 Integer 或 Boolean)。

  2. 在实体类中对应的属性上添加 @TableLogic 注解,并可以指定未删除值和已删除值。

    @TableLogic
    @TableField(fill = FieldFill.INSERT) // 可选:插入时自动填充为未删除状态
    private Integer deleted; // 0 表示未删除, 1 表示已删除
    
  3. 可以在全局配置文件中设置默认的未删除值和已删除值:

    mybatis-plus:
     global-config:
      db-config:
       logic-delete-field: deleted # 全局逻辑删除的实体字段名
       logic-delete-value: 1 # 逻辑已删除值(默认为 1)
       logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)
    

原理

  • 执行删除操作 (deleteById, delete 等) 时,MP 会自动将其转换为更新操作,将逻辑删除字段的值更新为“已删除”状态。
  • 执行查询操作 (selectById, selectList 等) 时,MP 会自动在 WHERE 条件中加入逻辑删除字段的过滤条件,只查询出“未删除”状态的数据。
  • 如果确实需要查询包括已逻辑删除的数据,可以使用特定的 Wrapper 方法或自定义 SQL。

自动填充(Automatic Filling)

​ MyBatis-Plus 提供了一个便捷的自动填充功能,用于在插入或更新数据时自动填充某些字段,如创建时间、更新时间等。

实现

  1. 创建自定义的元数据处理器类,实现MetaObjectHandler接口,并重写insertFill和updateFill方法:
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {

    @Override
    public void insertFill(MetaObject metaObject) {
        // metaObject:元对象,可以获取到原始实体对象及其属性
        // "gmtCreate" 和 "gmtModified" 是实体类中的属性名
        this.strictInsertFill(metaObject, "gmtCreate", LocalDateTime::now, LocalDateTime.class);
        this.strictInsertFill(metaObject, "gmtModified", LocalDateTime::now, LocalDateTime.class);
        // 假设有 deleted 字段,并希望插入时默认为0 (未删除)
        this.strictInsertFill(metaObject, "deleted", () -> 0, Integer.class);
    }

    @Override
    public void updateFill(MetaObject metaObject) {
        this.strictUpdateFill(metaObject, "gmtModified", LocalDateTime::now, LocalDateTime.class);
    }
}
  1. 在实体类的需要自动填充的属性上使用@TableField(fill = FieldFill.xxx)注解
@TableField(fill = FieldFill.INSERT)
private Date gmtCreate;

@TableField(fill = FieldFill.INSERT_UPDATE)
private Date gmtModified;

@TableField(fill = FieldFill.INSERT)
private Integer deleted;

FieldFill 枚举可选值:

  • DEFAULT: 默认不处理。
  • INSERT: 插入时填充。
  • UPDATE: 更新时填充。
  • INSERT_UPDATE: 插入和更新时都填充。

代码生成器(AutoGenerator)

​ MyBatis-Plus 提供了一个强大的代码生成器模块 (mybatis-plus-generator),可以根据数据库表结构自动生成 Entity、Mapper 接口、Mapper XML、Service 接口、Service 实现类甚至 Controller 类的基础代码。

  • 使用方式:通常是创建一个单独的 Java 类,在 main 方法中配置 AutoGenerator 的各种策略(数据源配置、全局配置、包配置、模板配置、策略配置等),然后运行该类即可生成代码。
  • 优点:极大减少了手动创建这些样板代码的工作量,尤其是在项目初期或表结构较多时,能显著提高开发效率。
  • 配置项丰富:可以自定义生成的代码风格、父类、是否覆盖已有文件、表名/字段名转换规则等。

配置

具体的安装可以参考官网的教程

application.yml相关配置

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/your_database?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8
    username: root
    password: your_password
    driver-class-name: com.mysql.cj.jdbc.Driver

mybatis-plus:
  mapper-locations: classpath*:/mapper/**/*.xml # 如果有自定义XML
  type-aliases-package: com.example.entity # 实体类别名包
  global-config:
    db-config:
      id-type: assign_id # 全局主键策略
      # logic-delete-field: deleted # 全局逻辑删除字段名 (如果配置了)
      # logic-delete-value: 1 # 全局逻辑删除值 (如果配置了)
      # logic-not-delete-value: 0 # 全局逻辑未删除值 (如果配置了)
    banner: false # 关闭MP启动时的banner信息
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 打印SQL日志
    map-underscore-to-camel-case: true # 开启驼峰命名转换

阅读量: 55

评论区

登录后发表评论

正在加载评论...
相关阅读

暂无相关文章推荐

返回首页浏览更多文章