Mybatis-plus

目录

1、MybatisPlus入门

1、MP入门案例

2、MP常见注解

3、MP常用配置

2、MP核心功能

1、条件构造器(如果不是按id来处理呢?)​编辑

QueryWrapper相关(构建select、delete、update的where条件部分时使用)

UpdateWrapper相关(在set语句比较特殊时使用)

2、自定义SQL

3、Iservice接口基本用法

4、Iservice开发基础业务接口

5、Iservice开发复杂业务接口(具有业务逻辑时)

6、Iservice的Lambda查询(当业务逻辑很复杂时用Lambda,复杂查询LambdaQuery和复杂更新LambdaUpdate)

使用Iservice的Lambda查询完成以下SQL语句。(复杂查询)

新问题(复杂更新)

7、Iservice的批量新增

普通for循环对大量数据插入

使用MP自带的批处理

3、MP的拓展功能

1、代码生成(一个插件)

2、静态工具(避免循环依赖用的)

3、逻辑删除(不推荐,会导致内容堆积)

4、枚举处理器

5、JSON处理器

6、分页插件

通用分页查询

通用分页实体与MP转换(优雅)

一些疑惑:

1、ID自增有什么问题?UUID为啥不行?雪花算法的优点、缺点?如何避免缺点?

[2、为什么没有 @Mapper 注解的接口会被注入到 IOC 容器](#2、为什么没有 @Mapper 注解的接口会被注入到 IOC 容器)

3、通过构造函数注入Bean对象

[4、为什么 IService 的 lambdaQuery 能直接返回结果而不用调用 Mapper](#4、为什么 IService 的 lambdaQuery 能直接返回结果而不用调用 Mapper)

[1. 内部封装了 Mapper 调用](#1. 内部封装了 Mapper 调用)

[2. 链式调用最终会触发 Mapper 查询](#2. 链式调用最终会触发 Mapper 查询)

[3. 查询执行流程](#3. 查询执行流程)

[4. 与直接使用 Mapper 的区别](#4. 与直接使用 Mapper 的区别)

[5. 设计优势](#5. 设计优势)

5、mybatis怎么处理枚举字段的?

[1. ​​默认枚举映射(无需配置)​​](#1. 默认枚举映射(无需配置))

[2、使用注解简化配置(MyBatis 3.4.5+)​​](#2、使用注解简化配置(MyBatis 3.4.5+))

[3、枚举与动态 SQL​​](#3、枚举与动态 SQL)

[6、 那为什么需要在返回值前写泛型呢?](#6、 那为什么需要在返回值前写泛型呢?)


学习将一个单体项目逐步拆分成微服务。

学习路径:

1、MybatisPlus入门

mp对Mybatis是无侵入的,并且MP可以简化Mybatis。很显而易见,如果我们要写update语句,那么Mybatis的if test判断语句会很多,但是如果使用mp可以智能的判断。

1、MP入门案例

具体实现:

注意在继承BaseMapper<T>时要指定泛型,T为要操作的实体类。

注入Bean对象:

使用:

2、MP常见注解

MP是怎么知道该去操作哪个表?怎么操作表的呢?答:根据匹配规则,约定大于配置!只要你能遵守约定,那么mp就能通过反射得到实体类的信息,从而得知表的信息。

如果实体类和数据库数据约定不一致呢?那我们就需要通过注解去指明对应表的信息。

TableId

可以指明为雪花算法,让mp帮我们填id,这样分库分表时就有优势(默认就是雪花算法)。也可以选择set方法自己填,或者选Auto让数据库填。

TableField

驼峰转化后名称不一致要用TableField重新命名。

可以指定名称,注意要是成员变量是is开头且是布偶值时一定要加,因为MP会把is去掉。

与关键字冲突时加''转义字符来指定名称。

非数据库字段时通过exist指明非必须。

3、MP常用配置

MP同样是支持xml文件的,MP适用的场景是单表操作,如果是复杂的操作还是要写在xml文件中。通过mapper-location指明xml文件位置。

MP的mapper-locations更强大,它写的mapper/**/*.xml可以扫描mapper下除xml文件外的子文件。

2、MP核心功能

1、条件构造器(如果不是按id来处理呢?)

QueryWrapper相关(构建select、delete、update的where条件部分时使用)

原SQL语句:

通过MP简化后的SQL语句:

注意由于update更新的是查询出来的语句,所以使用Querywrapper先实现查询。

UpdateWrapper相关(在set语句比较特殊时使用)

原SQL语句:

MP精简后的:

以上两种属于硬编码,它把字段部分写死了。避免这种情况可以使用属lambda的条件构造器。这样当实体类发生字段修改时,它不会因为定死的字段而报错。具体实现就是将字段改成User::getId这种。

2、自定义SQL

案例:

我们之前写的.setSql是在业务层写的SQL语句,这在很多公司的规范中是不被允许的。用MP方便了,但有的时候要去拼接SQL,违反企业开发规范。比如我们这里的setSql(...)语句。

自定义SQL就是我们在service和mapper层只构建条件然后逐步向下传递,然后在xml文件里对SQL语句进行拼接。

3、Iservice接口基本用法

对于增删改而言,MP在service接口的主要优势是可以实现批量的增删改,saveBatch和removeBatchByIds和updateBatchById这种批量的增删改。而查询包含单个(getById)、数量(count)、多个(listByIds)、分页(page)、复杂条件(lambdaQuery)

service接口要继承IService接口,service实现类也要继承ServiceImpl类。

其实service最终还是基于mapper层来实现的,所以在extend ServiceImpl<UserMapper,User>需要指明对应的mapper类和操作的实体类。

具体实现:

4、Iservice开发基础业务接口

注意有关@Api的东西是swager依赖的东西,编写它是用于测试。

BeanUtil.copyProperties是hutool依赖项中的方法,它可以将差不多的实体类进行转换,比如vo和po几乎一模一样,可能少了一些属性,它就能通过拷贝进行转化。(这是一个很常见的场景)

5、Iservice开发复杂业务接口(具有业务逻辑时)

Controller:

service:

6、Iservice的Lambda查询(当业务逻辑很复杂时用Lambda,复杂查询LambdaQuery和复杂更新LambdaUpdate)

使用Iservice的Lambda查询完成以下SQL语句。(复杂查询)

注意Controller层的queryUsers方法是一个get请求,它不包含请求体所以没办法使用@RequestBody,直接用UserQuery这个实体类去接收就好。

问:为什么 IService 的 lambdaQuery 能直接返回结果而不用调用 Mapper

因为已经隐式调用,详情看一些疑惑中的回答四。

新问题(复杂更新)

由于可能出现线程安全的问题,要加锁。或者我们通过逻辑加一个乐观锁。即只有当数据库中对应id的值和我刚刚查到的值相同时才修改,否则不修改。

7、Iservice的批量新增

普通for循环对大量数据插入

使用MP自带的批处理

它的实现主要是通过每次批量插入1000条数据,插100次即可插10w条数据。每次只插1000条是因为List对象如果为10w的长度的话可能会内存溢出。

为什么它仍然可以优化呢? 因为MP自带的批量新增是对添加语句进行预编译,它会预编译1000条新增SQL语句。所以我们可以设置MySQL的jdbc驱动的rewriteBatchedStatements属性为true,它会重写1000插入语句,让其变成一条插入语句,实现真正的批量插入。

**为什么JDBC 默认关闭 rewriteBatchedStatements?**因为兼容性问题,一些古早的服务器或者其他版本服务器对执行重写后的语句有歧义。并且重写SQL语句有安全分险,可能SQL注入。再就是少量的数据在插入时如果也重写的话会浪费时间,甚至可能性能还会下降。重写SQL语句也会导致日志审查困难。

在配置文件中让这个属性为true

3、MP的拓展功能

1、代码生成(一个插件)

通过插件生成基础的代码

具体位置在工具栏

2、静态工具(避免循环依赖用的)

Iservice是非静态的,所以需要指明实体类和mapper文件,通过反射操作。而Db是静态的,所以它的方法需要传一个Class字节码参数(似乎更麻烦)

静态工具Db,主要是当出现两个Service比如user和address的Service,出现互相依赖也就是循环依赖时使用,这样当我们user需要去查询address时,不需要注入address的Bean对象来调用方法,直接用Db传入address的字节码Class文件就好。

实现

首先给userVO实体类添加addresses字段

Controller

Service

通过Db.lambdaQuery(Address.class),我们不用注入Address的Iservice也可以使用它的LambdaQuery,这样避免了循环依赖。

还有就是hutool工具包的BeanUtil.copyProperties可以互相转实体类,BeanUtil.copyToList可以将List对象转为实体类对象。

更复杂的实现

1、为什么要查询完用户再来获取用户id呢?因为查询的ids可能为空。

2、流处理主要包含中间操作和终止操作两部分,中间操作有filter(Predicate) 过滤符合条件的元素、map(Function) 将元素转换为其他形式、distinct() 去重、sorted() 自然排序还有limit限制个数、skip跳过前几个元素。终止操作一般为collect和foreach两个。

3、获取userId后通过静态工具Db调用Address服务获取地址List<Addressov>。注意这个集合包含查询的所有地址,再通过流处理collectors.groupingBy()对List进行分类。

1、为什么不是先set在list.add呢?因为指向的是引用,顺序影响不大。

3、逻辑删除(不推荐,会导致内容堆积)

4、枚举处理器

我们创建枚举类后,就可以在实体类中创建枚举类对象,这样我们在代码中就可以给他赋NORMAL和FREEZE这样的属性,而java字段的枚举类型和数据库的枚举类型的相互转化是由Mybatis的Typehandler

这是Mybatis的Typehandler

MP多了两个类,这两个类主要是实现和Mybatis的自定义枚举处理器一样的效果,也就是枚举某个属性(如 NORMAL、FREEZE)而非名称或序号存储时,需自定义 TypeHandler

使用@EnumValue注解

选定默认的枚举处理器(这里我们选的自然是枚举类型而非json类型)

实现:

1、写枚举类

2、配置枚举处理器

3、修改字段类型

4、枚举字段可以直接用==比较,我们在业务代码也要修改,可读性变得非常好

5、默认给前端返回的是NORMAL、FREEZE字段,要想返回的是其他字段用@JsonValue注解

5、JSON处理器

枚举通常被视为简单值类型,不需要复杂的嵌套映射。JSON处理器需要明确知道如何处理这些嵌套关系,autoResultMap=true会触发MP使用配置的类型处理器。

1、添加JSON字段的实体类,在这个对象上面@TableField注解里选择TypeHandler="JacksonTypeHandler.class"

2、TableName设置autoResultMap=true,处理嵌套映射。

6、分页插件

1、分页插件需要在MP的拦截器中配置了才可以使用。

2、这个主要关注一下page的两个参数,一个page 包含分页的参数(页码、每页大小、排序字段等),一个querywrapper包含查询条件

3、准备分页条件时用Page.of方法创建page分页条件对象。注意新版在使用OrderItem()指定排序方式时,需要用静态方法asc(String)来创建对象。true代表升序。

通用分页查询

1、写一个统一的分页查询文件

2、其他业务继承和拓展即可。@EqualsAndHashCode(...)注解就是说,当判断相等时先考虑父类属性再考虑子类属性,就是分页的时候把分页条件作为数据请求的的前提,然后再考虑查到了哪些匹配的数据

3、分页查询结果返回的实体类

4、Controller

5、service

5.1 这个地方和上面直接用page查询又不同,因为它有条件查询(要求status是1),所以我们使用Lambdaquery复杂查询实现。还要去判断它是否传了排序字段,没有的话我们用更新时间字段排序

5.2 对返回的结果进行处理,这里我们可以学到如果我们要返回的是一个空集合,我们可以用collection.emptyList() 创建一个空集合。

通用分页实体与MP转换(优雅)

直接将page对象在pagequery实体类中就用方法生成

由于我们默认排序的字段不知道,我们使用可变参数OrderItem ... items,可变参数支持从零到多个参数,这样它排序的字段就可以为多个。

方法重载一下,考虑其他参数情况。

pageDto中呢由于要通用的让vo转成po,所以我们使用泛型作为参数,在实例化的时候才指明类型

由此,我们业务代码可以简化为:

我们在最后想让代码更优雅一点,我们不用hutool工具包的copy方法去将po转vo,我们自定义一个方法,通过流处理的方式将他转为vo

那我们在使用的时候就要给他传一个方法,一个Lambda表达式,相当于在map()里的执行的方法

一些疑惑:

1、ID自增有什么问题?UUID为啥不行?雪花算法的优点、缺点?如何避免缺点?

2、为什么没有 @Mapper 注解的接口会被注入到 IOC 容器

即使你没有在接口上显式添加 @Mapper 注解,这个 MyBatis Mapper 接口仍然可能被注入到 Spring IOC 容器中,主要有以下几种可能原因:

  1. 使用了 @MapperScan 注解

最常见的情况是你在配置类或启动类上使用了 @MapperScan 注解:

复制代码
@SpringBootApplication
@MapperScan("com.example.mapper")  // 扫描指定包下的所有接口
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

@MapperScan 会扫描指定包路径下的所有接口,并将它们注册为 MyBatis Mapper,无需在每个接口上单独添加 @Mapper 注解。

  1. MyBatis-Spring-Boot-Starter 自动配置

如果你使用的是 Spring Boot 并添加了 mybatis-spring-boot-starter 依赖,它会自动配置 MyBatis 相关组件:

复制代码
<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>版本号</version>
</dependency>

这个 starter 会自动扫描标注了 @Mapper 的接口,或者 在配置文件中通过 mybatis.mapper-locations 指定了 XML 文件位置时,也会自动关联对应的接口。

  1. XML 文件与接口的对应关系

MyBatis 通过以下方式将接口与 XML 映射文件关联:

命名空间匹配:XML 中的 <mapper namespace> 必须与接口的全限定名一致

方法名匹配:XML 中的 SQL 语句 id 必须与接口方法名一致

当这些条件满足时,即使没有 @Mapper 注解,MyBatis 也能正确识别并创建接口的代理实现。

  1. Spring 的组件扫描

虽然不太常见,但如果你在接口上添加了 @Repository 或其他 Spring 的组件注解,Spring 也会将其注册为 Bean,但这通常不是推荐的做法。

总结

没有 @Mapper 注解但接口仍被注入 IOC 容器的原因通常是:

使用了 @MapperScan 进行批量扫描

MyBatis-Spring-Boot-Starter 的自动配置

正确的 XML 文件与接口的对应关系

这种设计提供了灵活性,你可以选择在单个接口上使用 @Mapper,或者使用 @MapperScan 进行批量注册。

3、通过构造函数注入Bean对象

在IuserService对象前添加final,由于@RequiredArgsConstructor它会构造那些必要的参数,如添加了final的IuserService,这样就不用@Autowired注入了。

4、为什么 IService 的 lambdaQuery 能直接返回结果而不用调用 Mapper

MyBatis-Plus 的 IService 接口及其实现类 ServiceImpl 在使用 lambdaQuery() 方法时能够直接返回结果而不需要显式调用 Mapper 方法,这是因为 MyBatis-Plus 在内部已经完成了整个查询流程的封装。以下是具体原因:

1. 内部封装了 Mapper 调用

ServiceImpl 内部已经持有了对应的 Mapper 实例(通过泛型注入),当调用 lambdaQuery() 时:

复制代码
// 简化的实现逻辑
public class ServiceImpl<M extends BaseMapper<T>, T> implements IService<T> {
    
    @Autowired
    protected M baseMapper; // 已经注入了对应的Mapper
    
    public LambdaQueryChainWrapper<T> lambdaQuery() {
        return new LambdaQueryChainWrapper<>(baseMapper, entityClass);
    }
}

2. 链式调用最终会触发 Mapper 查询

LambdaQueryChainWrapper 采用链式编程设计,最终的方法调用(如 .list().one())会触发真正的查询:

复制代码
// 示例用法
List<User> users = userService.lambdaQuery()
    .eq(User::getName, "张三")
    .gt(User::getAge, 18)
    .list(); // 这里最终会调用baseMapper.selectList(wrapper)

3. 查询执行流程

  1. 构建条件.eq().gt() 等方法构建查询条件

  2. 转换为 Wrapper :条件被转换为 LambdaQueryWrapper

  3. 自动调用 Mapper :当调用终止方法(如 .list())时:

    • 自动调用 baseMapper.selectList(wrapper)

    • baseMapper.selectOne(wrapper) 等对应方法

  4. 返回结果:直接返回查询结果

4. 与直接使用 Mapper 的区别

方式 使用 Mapper 使用 IService.lambdaQuery()
代码示例 mapper.selectList(wrapper) service.lambdaQuery().eq().list()
条件构造 需要先创建 Wrapper 链式直接构造
Mapper 调用 显式调用 隐式自动调用
返回结果 直接返回 通过终止方法返回
适用场景 复杂SQL/自定义SQL 简单条件查询

5. 设计优势

  1. 简化代码:避免手动创建 Wrapper 和调用 Mapper

  2. 链式编程:更流畅的API体验

  3. 保持一致性 :与其他 MyBatis-Plus 特性(如 LambdaUpdateWrapper)风格统一

  4. 降低学习成本:对新手更友好

这种设计让 Service 层能够更自然地表达查询意图,而不需要关心底层的 Mapper 调用细节,是 MyBatis-Plus 对经典三层架构的一种优雅改进。

5、mybatis怎么处理枚举字段的?

MyBatis 提供了多种灵活的方式来处理枚举字段的映射,以下是主要的处理方式及实现方法:


1. ​​默认枚举映射(无需配置)​

MyBatis 内置了 EnumTypeHandler,默认将枚举的 ​​名称(name())​​ 与数据库字段(字符串类型)直接映射。例如:

复制代码
public enum Gender { MALE, FEMALE }

数据库字段为 VARCHAR 时,MALE 会存储为 "MALE",查询时自动转换回枚举

2、使用注解简化配置(MyBatis 3.4.5+)​

通过 @EnumValue 注解指定枚举的存储属性,无需自定义 TypeHandler

复制代码
public enum UserStatus { @EnumValue(1) ACTIVE, @EnumValue(0) INACTIVE; }

MyBatis 会自动识别注解并处理映射

3、枚举与动态 SQL​

在动态 SQL 中直接使用枚举实例,MyBatis 会自动调用对应的 TypeHandler

复制代码
<select id="selectUsers" resultType="User"> SELECT * FROM user WHERE 
status = #{status} 
</select>

6、 那为什么需要在返回值前写<T>呢?

1、区分泛型方法与泛型类​

​泛型类​ ​的类型参数(如 class Box<T>)作用于整个类,但​​泛型方法​ ​的类型参数(如 <T> void print(T t))仅作用于当前方法。

若方法需要独立的泛型类型(与类泛型无关),必须在返回值前声明 <T>,否则编译器会认为 T 是类定义的泛型参数

复制代码
public class Box<T> { // 类泛型
    public <U> void inspect(U u) { // 方法泛型,U 独立于 T
        System.out.println("U: " + u.getClass());
    }
}

2、静态方法必须声明泛型​​

静态方法无法访问类的泛型参数(因为类泛型在实例化时确定),若需使用泛型,必须单独声明 <T>

复制代码
public static <T> T getFirst(List<T> list) { // 必须声明 <T> return list.get(0); }
相关推荐
杨不易呀1 小时前
Java面试全记录:Spring Cloud+Kafka+Redis实战解析
redis·spring cloud·微服务·kafka·高并发·java面试·面试技巧
hnlucky2 小时前
《基于 Kubernetes 的 WordPress 高可用部署实践:从 MariaDB 到 Nginx 反向代理》
运维·数据库·nginx·云原生·容器·kubernetes·mariadb
_板栗_2 小时前
livenessProbe 和 readinessProbe 最佳实践
云原生·容器·kubernetes
镜舟科技4 小时前
湖仓一体架构在金融典型数据分析场景中的实践
starrocks·金融·架构·数据分析·湖仓一体·物化视图·lakehouse
luck_me55 小时前
K8s 图形界面管理kubesphere
云原生·容器·kubernetes
charlie1145141915 小时前
内核深入学习3——分析ARM32和ARM64体系架构下的Linux内存区域示意图与页表的建立流程
linux·学习·架构·内存管理
堕落年代6 小时前
SpringBoot的单体和分布式的任务架构
spring boot·分布式·架构
慧一居士6 小时前
Memcached 服务搭建和集成使用的详细步骤示例
数据库·架构·nosql·memcached
码农飞哥6 小时前
互联网大厂Java求职面试实战:Spring Boot与微服务场景深度解析
java·数据库·spring boot·安全·微服务·消息队列·互联网医疗