目录
快速入门
快速开始
引入依赖
定义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。
测试
@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 长整型类型(please use ASSIGN_ID) |
| 32 位 UUID 字符串(please use ASSIGN_UUID) |
| 分布式全局唯一 ID 字符串类型(please use ASSIGN_ID) |
这里比较常见的有三种:
AUTO
:利用数据库的id自增长INPUT
:手动生成idASSIGN_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硬编码有魔法值,不
适合在开发中使用。
Wrapper
的子类AbstractWrapper
提供了where中包含的所有条件构造方法:
而QueryWrapper在AbstractWrapper的基础上拓展了一个select方法,允许指定查询字段:
而UpdateWrapper在AbstractWrapper的基础上拓展了一个set方法,允许指定SQL中的SET部分:
使用方式如下:
@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插件下载
配置数据库连接地址
选择工具 选择Code Generator
静态工具
具体可以参考官网:持久层接口 | MyBatis-Plus
有的时候Service之间也会相互调用,为了避免出现循环依赖问题,MybatisPlus提供一个静态工具
类:Db
,其中的一些静态方法与IService
中方法签名基本一致,也可以帮助我们实现CRUD功能:
逻辑删除
具体可参考官方文档:逻辑删除支持 | 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
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:configuration:default-enum-type-handler: com.baomidou.mybatisplus.core.handlers.MybatisEnumTypeHandler
JSON处理器
具体可参考官方文档:字段类型处理器 | MyBatis-Plus
在表中有时候存储的是JSON字段,我们实体一般用String存储,后续在将JSON转换为对象,
MyBatisPlus给我们提供JSON处理器能自动转换为对象。
选择一个JSON类型处理器
开启自动映射
插件功能
具体可参考官方文档:插件主体 | 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);}
}
样我们在开发也时就可以省去对从PageQuery
到Page
的的转换:
// 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;});
}