Spring boot 4 搞懂MyBatis-Plus的用法解析
投稿:mrr
MyBatis-Plus 是一个 MyBatis 的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生
Spring boot 4如何集成
增加依赖
Add MyBatis-Plus dependency
<mybatisplus.version>3.5.15</mybatisplus.version>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-spring-boot4-starter</artifactId>
<version>${mybatisplus.version}</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-jsqlparser</artifactId>
<version>${mybatisplus.version}</version>
</dependency>配置
@Configuration
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
// 数据权限
mybatisPlusInterceptor.addInnerInterceptor(new DataFilterInterceptor());
// 分页插件
mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor());
// 乐观锁
mybatisPlusInterceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
// 防止全表更新与删除
mybatisPlusInterceptor.addInnerInterceptor(new BlockAttackInnerInterceptor());
return mybatisPlusInterceptor;
}
}基础特性
- “零SQL” CRUD
- 核心点:业务模块的Mapper 接口继承
BaseMapper<T>,即可拥有insert、selectById、updateById、deleteBatchIds等 17 个常用方法。 - Service CRUD:除了 Mapper 层,MP 还提供了
IService和ServiceImpl,封装了更多的业务逻辑方法(如saveBatch批量插入)。 - 提供SqlHelper工具类实现批量写入或更新操作
- 核心点:业务模块的Mapper 接口继承
- 条件构造器(Wrapper)
- 三板斧:
QueryWrapper(用于查询,支持lambda避免字段名写错)、UpdateWrapper(用于更新,支持链式设置 set 值)。 - Lambda 表达式:推荐使用
LambdaQueryWrapper,利用方法引用(如User::getName)来指定字段,编译期检查,防止 SQL 拼写错误。
- 三板斧:
- 常用注解
@TableName:指定表名(如果类名与表名不一致)。@TableId:指定主键策略(如IdType.AUTO自增,IdType.ASSIGN_ID雪花算法)。@TableField:填充字段(如创建时间、更新时间自动填充FieldFill.INSERT_UPDATE),或者逻辑删除字段@TableLogic
增强功能
- 自动填充
- 自动填充:实现
MetaObjectHandler接口,重写insertFill和updateFill方法,统一处理create_time、update_time等字段。
- 自动填充:实现
- 逻辑删除:配置后,
delete语句会自动转化为UPDATE语句(设置删除标记),查询时自动追加WHERE deleted = 0条件。 【在实体类中,对应数据库表的逻辑删除字段上添加@TableLogic注解】- 插入:逻辑删除字段的值不受限制。
- 查找:自动添加条件,过滤掉标记为已删除的记录。
- 更新:防止更新已删除的记录。
- 删除:将删除操作转换为更新操作,标记记录为已删除
- 自定义ID生成器
IdentifierGenerator主要用于生成数据库表的主键IDKeyGenerator是MyBatis框架中的一个接口,用于在执行SQL语句时生成键值,通常用于生成自增主键或者在执行INSERT语句后获取新生成的ID- MyBatis-Plus 内置支持多种数据库的主键生成策略,如:
- H2KeyGenerator
- OracleKeyGenerator
- PostgreKeyGenerator
- 多数据源支持:详细使用参见https://github.com/baomidou/dynamic-datasource
- Spring Boot 1.5.x ~ 2.x.x 使用
dynamic-datasource-spring-boot-starter,支持 JDK 8 及以上版本 - Spring Boot 3.x.x 使用
dynamic-datasource-spring-boot3-starter,要求 JDK 17 及以上 - Spring Boot 4.x.x 使用
dynamic-datasource-spring-boot4-starter,要求 JDK 17 及以上
- Spring Boot 1.5.x ~ 2.x.x 使用
插件机制
常用插件
- 分页插件(PaginationInnerInterceptor)
- 原理:MP 的分页不是内存分页,而是物理分页。它通过 MyBatis 的拦截器(Interceptor)机制,在 SQL 执行前重写 SQL(如加上
LIMIT)。 - 使用:配置
MybatisPlusInterceptor,注入PaginationInnerInterceptor。代码中使用Page<T>对象接收结果。
- 原理:MP 的分页不是内存分页,而是物理分页。它通过 MyBatis 的拦截器(Interceptor)机制,在 SQL 执行前重写 SQL(如加上
- 乐观锁插件
OptimisticLockerInnerInterceptor- 读取记录时,获取当前的版本号(version)。
- 在更新记录时,将这个版本号一同传递。
- 执行更新操作时,设置
version = newVersion的条件为version = oldVersion。 - 如果版本号不匹配,则更新失败。
- 多租户插件
TenantLineInnerInterceptor- 是 MyBatis-Plus 提供的一个插件,用于实现多租户的数据隔离。通过这个插件,可以确保每个租户只能访问自己的数据,从而实现数据的安全隔离
- 默认插入 SQL 是需要判断租户条件,因此需要配合自动填充字段功能填充租户字段,否则租户字段不会自动保存到数据库
- 非法SQL拦截插件
IllegalSQLInnerInterceptor- 用于拦截和检查非法SQL语句。该插件旨在帮助开发者在SQL执行前发现并解决潜在的安全问题,如全表更新、删除操作,以及对索引的检查等
- 防全表更新与删除插件
BlockAttackInnerInterceptor- 专门用于防止恶意的全表更新和删除操作。该插件通过拦截
update和delete语句,确保这些操作不会无意中影响到整个数据表,从而保护数据的完整性和安全性
- 专门用于防止恶意的全表更新和删除操作。该插件通过拦截
执行流程
MyBatis-Plus(MP)插件的执行流程,本质上是基于 MyBatis 的插件(Plugin)机制实现的。MP 利用这一机制,在 MyBatis 的核心执行流程中“插入”自己的逻辑,从而实现分页、性能分析、SQL 注入防护等功能。
MybatisPlusInterceptor 是“包工头”,它实现了 MyBatis 的接口并拦下所有活;而 List<InnerInterceptor>是“工人”,实现具体的功能
| 组件层级 | 类/接口名称 | 职责 | 实现关系 |
|---|---|---|---|
| 顶层门面 | MybatisPlusInterceptor | 实现 MyBatis 原生 Interceptor,作为唯一入口,管理插件列表。 | 实现org.apache.ibatis.plugin.Interceptor |
| 插件容器 | List<InnerInterceptor> | 存储具体的增强逻辑(如分页、乐观锁),由顶层门面调用。 | 内部持有 |
| 具体逻辑 | PaginationInnerInterceptor等 | 实现具体的业务逻辑(如重写 SQL、计算耗时)。 | 实现InnerInterceptor |
如何自定义SQL
通过如下注解,可以实现自定义SQL
@Select: 查询@Insert: 插入@Update: 更新@Delete: 删除
@Mapper
public interface UserMapper extends BaseMapper<User> {
// 简单查询
@Select("SELECT * FROM user WHERE email = #{email}")
User selectByEmail(@Param("email") String email);
// 复杂一点的条件查询
@Select("SELECT * FROM user WHERE status = #{status} AND age > #{minAge}")
List<User> selectByStatusAndAge(@Param("status") Integer status, @Param("minAge") Integer minAge);
}mybatis知识
# {}和${}的区别
#{}:是预编译处理(PreparedStatement)。MyBatis 会将它替换为?,能有效防止 SQL 注入${}:是字符串替换。直接将变量拼接到 SQL 中,存在注入风险,通常用于传入数据库表名或排序字段(ORDER BY)
MyBatis 的一级缓存和二级缓存
- 一级缓存: 作用域为
SqlSession。在同一个会话中,相同的查询不再查数据库。默认开启。 - 二级缓存: 作用域为
Namespace(多个会话间共享)。需要手动开启,且实体类必须实现序列化接口。 - 失效场景: 任何的
INSERT,UPDATE,DELETE操作都会刷新缓存
实体类属性名和表中字段名不一致怎么办?
- 在 SQL 语句中使用别名(
AS)。 - 使用
ResultMap进行映射(最常用)。 - 开启驼峰命名自动转换配置(
mapUnderscoreToCamelCase)
Mybatis的缓存机制
一级缓存建议保持默认(开启),而二级缓存通常不建议开启(默认也是关闭的),尤其是在复杂的业务或分布式系统中
一级缓存
一级缓存的实现参见:BaseExecutor、PerpetualCache
- 一级缓存的生命周期由
Executor(执行器)管理。在BaseExecutor(所有执行器的基类)中,定义了本地缓存对象 SqlSession每次创建都会生成一个新的Executor,所以一级缓存是线程隔离的【参见DefaultSqlSessionFactory】
二级缓存
二级缓存的实现参见:CachingExecutor、SynchronizedCache
当开启二级缓存后,MyBatis 会使用 CachingExecutor 包装原来的 Executor
装饰器模式: 二级缓存的实现非常优雅,PerpetualCache 是基础,通过 SynchronizedCache(同步)、LoggingCache(日志)、ScheduledCache(定时)、SerializedCache(序列化)、LruCache(回收策略)等装饰器层层包装,组合出最终需要的功能
| 特性 | 一级缓存 (Local) | 二级缓存 (Global) |
|---|---|---|
| 作用范围 | 单个 SqlSession 内 | 同一个 Mapper 命名空间内 |
| 数据共享 | 独享,不跨会话 | 共享,跨 SqlSession |
| 默认状态 | 开启 | 关闭 (需手动配置) |
| 生命周期 | 伴随 SqlSession 的创建与关闭 | 伴随 SqlSessionFactory 的生命周期 |
| 清空时机 | 增删改操作、close()、commit() | 对应 Mapper 的增删改操作、commit() |
是否开启
| 缓存类型 | 建议配置 | 理由 | 适用场景 |
|---|---|---|---|
| 一级缓存 | 开启 (默认) | 安全、自动管理、提升单次会话性能 | 所有场景(无需干预) |
| 二级缓存 | 关闭 (默认) | 避免脏读、避免分布式不一致 | 复杂业务、分布式系统 |
| 二级缓存 | 开启 (需评估) | 需处理好序列化和跨表失效问题 | 单机应用、数据字典、纯读场景 |
一句话建议: 用好一级缓存,忘掉二级缓存,用 Redis 来做全局缓存
加载流程
MapperImpl方法调用过程
- 代理拦截(MybatisMapperProxy)
MybatisMapperProxy是在 Spring 容器进行依赖注入(@Autowired)时,由MapperProxyFactory调用getObject方法实例化的
- 命令执行(MapperMethod):
MybatisMapperProxy会拿到一个MapperMethod实例。- 调用
mapperMethod.execute(sqlSession, args)。 - 此时,控制权从 “代理层” 转移到了 “命令层”
- SQL 执行(SqlSession/Executor):
MapperMethod内部会根据 SQL 类型,调用sqlSession.selectOne()或insert()等方法。- 最终由
Executor去 JDBC 层面执行 SQL
public class MapperRegistry {
private final Configuration config;
private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new ConcurrentHashMap();
public MapperRegistry(Configuration config) {
this.config = config;
}
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory)this.knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
} else {
try {
return (T)mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}
}
Spring boot启动时初始化过程
- SqlSessionFactoryp初始化
MybatisAutoConfiguration->MybatisSqlSessionFactoryBean->SqlSessionFactory
- Mapper 注册与扫描
AutoConfiguredMapperScannerRegistrar->AutoConfiguredMapperScannerRegistrar->MapperScannerConfigurer->ClassPathMapperScanner->ClassPathBeanDefinitionScanner->MapperFactoryBean
- MybatisMapperProxy 实例化
- 获取 Mapper Bean:
- 当 Spring 容器尝试填充
@Autowired private UserMapper userMapper;时。 - 容器发现这是一个
FactoryBean,于是调用MapperFactoryBean.getObject()。
- 当 Spring 容器尝试填充
- 获取 SqlSession:
MapperFactoryBean.getObject()内部会调用this.getSqlSession().getMapper(this.mapperInterface);。
- 最终实例化 (核心点):
sqlSession.getMapper()会委托给(T)this.getConfiguration().getMapper(type, this);。Configuration持有一个this.mapperRegistry.getMapper(type, sqlSession)(注册中心)。MapperRegistry里存着Class -> MapperProxyFactory的映射
- 获取 Mapper Bean:
public class MapperRegistry {
private final Configuration config;
private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new ConcurrentHashMap();
public MapperRegistry(Configuration config) {
this.config = config;
}
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory)this.knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
} else {
try {
return (T)mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}
}
到此这篇关于Spring boot 4 搞懂MyBatis-Plus的用法解析的文章就介绍到这了,更多相关Spring boot mybatis-plus用法内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
