目录

快速入门

快速开始

引入依赖

定义Mapper

测试

常见注解

@TableName

@TableId

@TableFieId

常见配置

核心功能

条件构造器

自定义SQL

Service接口

基本用法

扩展功能

代码生成

静态工具

逻辑删除

枚举处理器

JSON处理器

插件功能

分页插件

通用分页实体


快速入门

MyBatisPlus官网:MyBatis-Plus 🚀 为简化开发而生

快速开始

比如我们要实现User表的CRUD,只需要下面两步:

  • 引入MybatisPlus依赖
  • 定义Mapper

引入依赖

具体可参考官方文档:安装 | MyBatis-Plus

SpringBoot2.X和SpringBoot3.X引入的依赖不同。

SpringBoot2.X maven依赖:

<dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.5.12</version>
</dependency>

SpringBoot3.X maven依赖:

<dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-spring-boot3-starter</artifactId><version>3.5.12</version>
</dependency>

定义Mapper

BaseMapper内置增删改查方法,只需要传入对象即可,就可以使用里面的方法CRUD。

MyBatis-Plus全方位通关指南:从零基础到高效开发实战_User

测试

@SpringBootTest
class UserMapperTest {@Autowiredprivate UserMapper userMapper;@Testvoid testInsert() {User user = new User();user.setId(5L);user.setUsername("Lucy");user.setPassword("123");user.setPhone("18688990011");user.setBalance(200);user.setInfo("{\"age\": 24, \"intro\": \"英文老师\", \"gender\": \"female\"}");user.setCreateTime(LocalDateTime.now());user.setUpdateTime(LocalDateTime.now());userMapper.insert(user);}@Testvoid testSelectById() {User user = userMapper.selectById(5L);System.out.println("user = " + user);}@Testvoid testQueryByIds() {List<User> users = userMapper.selectByIds(List.of(1L, 2L, 3L, 4L));users.forEach(System.out::println);}@Testvoid testUpdateById() {User user = new User();user.setId(5L);user.setBalance(20000);userMapper.updateById(user);}@Testvoid testDeleteUser() {userMapper.deleteById(5L);}
}

常见注解

具体可参考官网文档:注解配置 | MyBatis-Plus

MybatisPlus就是根据PO实体的信息来推断出表的信息,从而生成SQL的。默认情况下:

  • MybatisPlus会把PO实体的类名驼峰转下划线作为表名
  • MybatisPlus会把PO实体的所有变量名驼峰转下划线作为表的字段名,并根据变量类型推断字段类型
  • MybatisPlus会把名为id的字段作为主键

但很多情况下,默认的实现与实际场景不符,因此MybatisPlus提供了一些注解便于我们声明表信

息。

@TableName

说明:

  • 描述:主键注解,标识实体类中的主键字段
  • 使用位置:实体类的主键字段

示例:

@TableName("user")
public class User {private Long id;private String name;
}

 TableName注解除了指定表名以外,还可以指定很多其它属性:

属性

类型

必须指定

默认值

描述

value

String

""

表名

schema

String

""

schema

keepGlobalPrefix

boolean

false

是否保持使用全局的 tablePrefix 的值(当全局 tablePrefix 生效时)

resultMap

String

""

xml 中 resultMap 的 id(用于满足特定类型的实体类对象绑定)

autoResultMap

boolean

false

是否自动构建 resultMap 并使用(如果设置 resultMap 则不会进行 resultMap 的自动构建与注入)

excludeProperty

String[]

{}

需要排除的属性名 @since 3.3.1

@TableId

说明:

  • 描述:主键注解,标识实体类中的主键字段
  • 使用位置:实体类的主键字段

示例:

@TableName("user")
public class User {@TableIdprivate Long id;private String name;
}

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)

这里比较常见的有三种:

  • AUTO:利用数据库的id自增长
  • INPUT:手动生成id
  • ASSIGN_ID:雪花算法生成Long类型的全局唯一id,这是默认的ID策略

@TableFieId

说明:

描述:普通字段注解

示例:

@TableName("user")
public class User {@TableIdprivate Long id;private String name;private Integer age;@TableField(is_married")private Boolean isMarried;@TableField("`concat`")private String concat;
}

一般情况下我们并不需要给字段添加@TableField注解,一些特殊情况除外:

  • 成员变量名与数据库字段名不一致
  • 成员变量是以isXXX命名,按照JavaBean的规范,MybatisPlus识别字段时会把is去除,这就导致与数据库不符。
  • 成员变量名与数据库一致,但是与数据库的关键字冲突。使用@TableField注解给字段名添加转义字符:``

支持的其它属性如下:

属性

类型

必填

默认值

描述

value

String

""

数据库字段名

exist

boolean

true

是否为数据库表字段

condition

String

""

字段 where 实体查询比较条件,有值设置则按设置的值为准,没有则为默认全局的 %s=#{%s},参考(opens new window)

update

String

""

字段 update set 部分注入,例如:当在version字段上注解update="%s+1" 表示更新时会 set version=version+1 (该属性优先级高于 el 属性)

insertStrategy

Enum

FieldStrategy.DEFAULT

举例:NOT_NULL insert into table_a(<if test="columnProperty != null">column</if>) values (<if test="columnProperty != null">#{columnProperty}</if>)

updateStrategy

Enum

FieldStrategy.DEFAULT

举例:IGNORED update table_a set column=#{columnProperty}

whereStrategy

Enum

FieldStrategy.DEFAULT

举例:NOT_EMPTY where <if test="columnProperty != null and columnProperty!=''">column=#{columnProperty}</if>

fill

Enum

FieldFill.DEFAULT

字段自动填充策略

select

boolean

true

是否进行 select 查询

keepGlobalFormat

boolean

false

是否保持使用全局的 format 进行处理

jdbcType

JdbcType

JdbcType.UNDEFINED

JDBC 类型 (该默认值不代表会按照该值生效)

typeHandler

TypeHander


类型处理器 (该默认值不代表会按照该值生效)

numericScale

String

""

指定小数点后保留的位数

常见配置

MyBatisPlus的配置项继承了MyBatis原生配置和一些自己特有的配置。例如:

mybatis-plus:type-aliases-package: com.itheima.mp.domain.po      # 别名扫描包mapper-locations: classpath*:/mapper/**/*.xml       # 文件地址 默认值configuration:                                      # 配置map-underscore-to-camel-case: true                # 是否开启下划线和驼峰的映射cache-enabled: false                              # 是否开启二级缓存global-config:                                      #  全局配置db-config:                                      # 数据库配置id-type: assign_id                            # id生成策略update-strategy: not_null                     # 更新策略:只更新非空字段

具体可参考官方文档:使用配置 | MyBatis-Plus

核心功能

条件构造器

除了BaseMapper提供的单表CRUD以外,有时候我们需要构建我们自己的条件,MyBatisPlus给我

们提供了条件构造器Wrapper来构建条件。

开发中用LambdaWrapper条件构造器即可,QueryWrapper和UpdateWrapper硬编码有魔法值,不

适合在开发中使用。

MyBatis-Plus全方位通关指南:从零基础到高效开发实战_ORM框架_02

Wrapper的子类AbstractWrapper提供了where中包含的所有条件构造方法:

MyBatis-Plus全方位通关指南:从零基础到高效开发实战_mybatisplus_03

 而QueryWrapper在AbstractWrapper的基础上拓展了一个select方法,允许指定查询字段:

MyBatis-Plus全方位通关指南:从零基础到高效开发实战_ORM框架_04

 而UpdateWrapper在AbstractWrapper的基础上拓展了一个set方法,允许指定SQL中的SET部分:

MyBatis-Plus全方位通关指南:从零基础到高效开发实战_mybatisplus_05

使用方式如下:

@Test
void testLambdaQueryWrapper() {// 1.构建条件 WHERE username LIKE "%o%" AND balance >= 1000QueryWrapper<User> wrapper = new QueryWrapper<>();wrapper.lambda().select(User::getId, User::getUsername, User::getInfo, User::getBalance).like(User::getUsername, "o").ge(User::getBalance, 1000);// 2.查询List<User> users = userMapper.selectList(wrapper);users.forEach(System.out::println);
}

自定义SQL

具体可参考官方文档:条件构造器 | MyBatis-Plus

在开发过程中,条件构造器基本满足大部分业务,但是也有一些复杂的业务需要我们自定义SQL。

自定义SQL查询:

@Test
void testCustomWrapper() {// 1.准备自定义查询条件List<Long> ids = List.of(1L, 2L, 4L);QueryWrapper<User> wrapper = new QueryWrapper<User>().in("id", ids);// 2.调用mapper的自定义方法,直接传递WrapperuserMapper.deductBalanceByIds(200, wrapper);
}

然后在UserMapper中自定义SQL:

package com.itheima.mp.mapper;import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.itheima.mp.domain.po.User;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Update;
import org.apache.ibatis.annotations.Param;public interface UserMapper extends BaseMapper<User> {@Select("UPDATE user SET balance = balance - #{money} ${ew.customSqlSegment}")void deductBalanceByIds(@Param("money") int money, @Param("ew") QueryWrapper<User> wrapper);
}

Service接口

MybatisPlus不仅提供了BaseMapper,还提供了通用的Service接口及默认实现,封装了一些常用的service模板方法。 通用接口为IService,默认实现为ServiceImpl,其中封装的方法可以分为以下几类:

  • save:新增
  • remove:删除
  • update:更新
  • get:查询单个结果
  • list:查询集合结果
  • count:计数
  • page:分页查询

基本用法

Service中经常需要定义与业务有关的自定义方法,因此我们不能直接使用IService,而是自定

Service接口,然后继承IService以拓展方法。同时,让自定义的Service实现类继承

ServiceImpl,这样就不用自己实现IService中的接口了。

首先,定义IUserService,继承IService

package com.itheima.mp.service;import com.baomidou.mybatisplus.extension.service.IService;
import com.itheima.mp.domain.po.User;public interface IUserService extends IService<User> {// 拓展自定义方法
}

然后,编写UserServiceImpl类,继承ServiceImpl,实现UserService

package com.itheima.mp.service.impl;import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.itheima.mp.domain.po.User;
import com.itheima.mp.domain.po.service.IUserService;
import com.itheima.mp.mapper.UserMapper;
import org.springframework.stereotype.Service;@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User>implements IUserService {
}

扩展功能

代码生成

官方MyBatisX插件:Mybatis X 插件 | MyBatis-Plus

由于Controller、Mapper、Service、PO这些比较固定我们可以使用代码生成器来帮我们生成代

码,官方提供MyBatisX插件,这里我使用MyBatisPluls插件来进行代码生成。

IDEA插件市场搜索MybatisPlus插件下载

MyBatis-Plus全方位通关指南:从零基础到高效开发实战_ORM框架_06

配置数据库连接地址

MyBatis-Plus全方位通关指南:从零基础到高效开发实战_User_07

 

MyBatis-Plus全方位通关指南:从零基础到高效开发实战_User_08

选择工具 选择Code Generator

MyBatis-Plus全方位通关指南:从零基础到高效开发实战_ORM框架_09

MyBatis-Plus全方位通关指南:从零基础到高效开发实战_java_10

 

MyBatis-Plus全方位通关指南:从零基础到高效开发实战_java_11

静态工具

具体可以参考官网:持久层接口 | MyBatis-Plus

有的时候Service之间也会相互调用,为了避免出现循环依赖问题,MybatisPlus提供一个静态工具

类:Db,其中的一些静态方法与IService中方法签名基本一致,也可以帮助我们实现CRUD功能:

MyBatis-Plus全方位通关指南:从零基础到高效开发实战_mybatisplus_12

逻辑删除

具体可参考官方文档:逻辑删除支持 | MyBatis-Plus

在做删除的时候,我们不想做物理删除,而是想逻辑删除,用一个字段标识他是否删除了,但是数

还留在数据库了,MyBatisPlus给我们提供了逻辑删除功能。

配置文件中配置逻辑删除字段和配置逻辑已删除值(默认为1) 逻辑未删除值(默认为0)

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全方位通关指南:从零基础到高效开发实战_User_13

枚举处理器

具体可参考官网文档:自动映射枚举 | MyBatis-Plus

MyBatisPlus提供了一个处理枚举类型转换器,可以帮我们把枚举类型与数据库类型自动转换。

定义一个用户状态的枚举:

@EnumValue注解来标记枚举属性

@JsonValue注解标记JSON序列化时展示的字段

@Getter
public enum UserStatus {NORMAL(1, "正常"),FREEZE(2, "冻结"),;@EnumValueprivate final int value;@JsonValueprivate final String desc;UserStatus(int value, String desc) {this.value = value;this.desc = desc;}
}

然后把User类中的status字段改为UserStatus 类型:

MyBatis-Plus全方位通关指南:从零基础到高效开发实战_mybatisplus_14

 

配置枚举处理器

mybatis-plus:configuration:default-enum-type-handler: com.baomidou.mybatisplus.core.handlers.MybatisEnumTypeHandler

JSON处理器

具体可参考官方文档:字段类型处理器 | MyBatis-Plus

在表中有时候存储的是JSON字段,我们实体一般用String存储,后续在将JSON转换为对象,

MyBatisPlus给我们提供JSON处理器能自动转换为对象。 

选择一个JSON类型处理器

MyBatis-Plus全方位通关指南:从零基础到高效开发实战_ORM框架_15

开启自动映射

MyBatis-Plus全方位通关指南:从零基础到高效开发实战_mybatisplus_16

 

MyBatis-Plus全方位通关指南:从零基础到高效开发实战_java_17

插件功能

具体可参考官方文档:插件主体 | MyBatis-Plus

分页插件

⚠注意,于 v3.5.9 起,PaginationInnerInterceptor 已分离出来。如需使用,则需单独引入

mybatis-plus-jsqlparser 依赖

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

配置分页插件

@Configuration
public class MyBatisConfig {public MybatisPlusInterceptor mybatisPlusInterceptor(){MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));return interceptor;}
}

测试代码

@Testvoid testPageQuery(){Page<User> p = userService.page(new Page<>(1, 2));System.out.println("p.getTotal() = " + p.getTotal());System.out.println("p.getPages() = " + p.getPages());List<User> records = p.getRecords();records.forEach(System.out::println);}

通用分页实体

PageQuery实体

package com.itheima.mp.domain.query;import com.baomidou.mybatisplus.core.metadata.OrderItem;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.Data;@Data
public class PageQuery {private Integer pageNo;private Integer pageSize;private String sortBy;private Boolean isAsc;public <T>  Page<T> toMpPage(OrderItem ... orders){// 1.分页条件Page<T> p = Page.of(pageNo, pageSize);// 2.排序条件// 2.1.先看前端有没有传排序字段if (sortBy != null) {p.addOrder(new OrderItem(sortBy, isAsc));return p;}// 2.2.再看有没有手动指定排序字段if(orders != null){p.addOrder(orders);}return p;}public <T> Page<T> toMpPage(String defaultSortBy, boolean isAsc){return this.toMpPage(new OrderItem(defaultSortBy, isAsc));}public <T> Page<T> toMpPageDefaultSortByCreateTimeDesc() {return toMpPage("create_time", false);}public <T> Page<T> toMpPageDefaultSortByUpdateTimeDesc() {return toMpPage("update_time", false);}
}

样我们在开发也时就可以省去对从PageQueryPage的的转换:

// 1.构建条件
Page<User> page = query.toMpPageDefaultSortByCreateTimeDesc();

 PageDTO实体

package com.itheima.mp.domain.dto;import cn.hutool.core.bean.BeanUtil;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;import java.util.Collections;
import java.util.List;
import java.util.function.Function;
import java.util.stream.Collectors;@Data
@NoArgsConstructor
@AllArgsConstructor
public class PageDTO<V> {private Long total;private Long pages;private List<V> list;/*** 返回空分页结果* @param p MybatisPlus的分页结果* @param <V> 目标VO类型* @param <P> 原始PO类型* @return VO的分页对象*/public static <V, P> PageDTO<V> empty(Page<P> p){return new PageDTO<>(p.getTotal(), p.getPages(), Collections.emptyList());}/*** 将MybatisPlus分页结果转为 VO分页结果* @param p MybatisPlus的分页结果* @param voClass 目标VO类型的字节码* @param <V> 目标VO类型* @param <P> 原始PO类型* @return VO的分页对象*/public static <V, P> PageDTO<V> of(Page<P> p, Class<V> voClass) {// 1.非空校验List<P> records = p.getRecords();if (records == null || records.size() <= 0) {// 无数据,返回空结果return empty(p);}// 2.数据转换List<V> vos = BeanUtil.copyToList(records, voClass);// 3.封装返回return new PageDTO<>(p.getTotal(), p.getPages(), vos);}/*** 将MybatisPlus分页结果转为 VO分页结果,允许用户自定义PO到VO的转换方式* @param p MybatisPlus的分页结果* @param convertor PO到VO的转换函数* @param <V> 目标VO类型* @param <P> 原始PO类型* @return VO的分页对象*/public static <V, P> PageDTO<V> of(Page<P> p, Function<P, V> convertor) {// 1.非空校验List<P> records = p.getRecords();if (records == null || records.size() <= 0) {// 无数据,返回空结果return empty(p);}// 2.数据转换List<V> vos = records.stream().map(convertor).collect(Collectors.toList());// 3.封装返回return new PageDTO<>(p.getTotal(), p.getPages(), vos);}
}

最终,业务层的代码可以简化为:

@Override
public PageDTO<UserVO> queryUserByPage(PageQuery query) {// 1.构建条件Page<User> page = query.toMpPageDefaultSortByCreateTimeDesc();// 2.查询page(page);// 3.封装返回return PageDTO.of(page, UserVO.class);
}

如果是希望自定义PO到VO的转换过程,可以这样做:

@Override
public PageDTO<UserVO> queryUserByPage(PageQuery query) {// 1.构建条件Page<User> page = query.toMpPageDefaultSortByCreateTimeDesc();// 2.查询page(page);// 3.封装返回return PageDTO.of(page, user -> {// 拷贝属性到VOUserVO vo = BeanUtil.copyProperties(user, UserVO.class);// 用户名脱敏String username = vo.getUsername();vo.setUsername(username.substring(0, username.length() - 2) + "**");return vo;});
}