摘要:
嘿,各位追求卓越的工程师们,我是默语 !欢迎来到《MyBatis-Plus保姆级教程》的第七章,也是我们深入架构与底层原理的进阶篇!前面的章节我们学会了如何“用”,而这一章我们将探讨如何“用得好、用得安全”。 你是否担心过在生产环境误执行了没有
WHERE
条件的UPDATE
或DELETE
?你是否想知道哪条SQL执行得最慢,拖累了系统性能?你是否有一些特殊的、全局性的需求(如数据加解密)不知如何优雅地实现?本章将为你揭晓答案!我们将深入MP的插件机制,学习如何配置SQL分析打印、如何启用“救命稻草”般的防止全表更新删除插件,并探讨性能分析与慢SQL监控的现代方案。最后,我将手把手带你实战开发一个自定义插件,让你真正具备扩展框架的能力!
博主 默语带您 Go to New World.
✍ 个人主页—— 默语 的博客 优秀内容
《java 面试题大全》
《java 专栏》
《idea技术专区》
《spring boot 技术专区》
《MyBatis从入门到精通》
《23种设计模式》
《经典算法学习》
《spring 学习》
《MYSQL从入门到精通》数据库是开发者必会基础之一~
惟余辈才疏学浅,临摹之作或有不妥之处,还请读者海涵指正。☕
吾期望此文有资助于尔,即使粗浅难及深广,亦备添少许微薄之助。苟未尽善尽美,敬请批评指正,以资改进。!⌨
默语是谁?
大家好,我是 默语,别名默语博主,擅长的技术领域包括Java、运维和人工智能。我的技术背景扎实,涵盖了从后端开发到前端框架的各个方面,特别是在Java 性能优化、多线程编程、算法优化等领域有深厚造诣。
目前,我活跃在CSDN、掘金、阿里云和 51CTO等平台,全网拥有超过20万的粉丝,总阅读量超过2000 万。统一 IP 名称为 默语 或者 默语博主。我是 CSDN 博客专家、阿里云专家博主和掘金博客专家,曾获博客专家、优秀社区主理人等多项荣誉,并在 2023 年度博客之星评选中名列前 50,2024年博客之星TOP5。我还是 Java 高级工程师、自媒体博主,北京城市开发者社区的主理人,拥有丰富的项目开发经验和产品设计能力。希望通过我的分享,帮助大家更好地了解和使用各类技术产品,在不断的学习过程中,可以帮助到更多的人,结交更多的朋友.
我的博客内容涵盖广泛,主要分享技术教程、Bug解决方案、开发工具使用、前沿科技资讯、产品评测与使用体验。我特别关注云服务产品评测、AI 产品对比、开发板性能测试以及技术报告,同时也会提供产品优缺点分析、横向对比,并分享技术沙龙与行业大会的参会体验。我的目标是为读者提供有深度、有实用价值的技术洞察与分析。
默语:您的前沿技术领航员
大家好,我是默语!
全网搜索“默语”,即可纵览我在各大平台的知识足迹。公众号“默语摸鱼”,每周定时推送干货满满的技术长文,从新兴框架的剖析到运维实战的复盘,助您技术进阶之路畅通无阻。
微信端添加好友“Solitudemind”,与我直接交流,不管是项目瓶颈的求助,还是行业趋势的探讨,随时畅所欲言。
快来加入技术社区,一起挖掘技术的无限潜能,携手迈向数字化新征程!
搜索词条: MyBatis-Plus教程, MyBatis-Plus插件, 性能优化, SQL分析, 慢SQL监控, 防止全表更新, BlockAttackInnerInterceptor, 自定义插件, MyBatis Interceptor
到目前为止,我们已经能够利用MyBatis-Plus高效地完成绝大部分业务功能的开发。然而,一个专业的开发者不仅要关注功能的实现,更要对代码的安全性、健壮性和性能负责。在数据库交互层面,一些小小的疏忽,比如一条忘记加WHERE
条件的UPDATE
语句,就可能在生产环境中引发灾难性的后果。
MyBatis-Plus深知这一点,它基于MyBatis强大的拦截器(Interceptor)机制,提供了一系列“守护插件”,旨在帮助开发者在开发阶段就发现问题、规避风险,并对SQL执行情况进行分析。这些插件就像是为我们的应用装上了一道道安全防火墙和性能监控器。
本章,我们将一起探索这些强大的“守护神”,学习如何配置和使用它们来保障我们的应用质量。更进一步,我们将揭开MyBatis插件机制的神秘面纱,通过实战开发一个自定义插件,让你具备根据特殊业务需求扩展框架的能力,真正迈向高手之路。
在开发和调试阶段,清晰地看到MyBatis-Plus最终执行的SQL语句、参数以及执行结果,是至关重要的第一步。
虽然MP早期版本提供过专门的SQL打印插件,但现在最推荐、最灵活的方式是直接利用你项目中的日志框架(如Logback、Log4j2)。
如何配置?
只需在你的application.yml
中,将MyBatis-Plus操作的Mapper接口所在的包的日志级别设置为DEBUG
。
# application.yml
logging:
level:
# 将你的mapper包路径的日志级别设为DEBUG
com.example.mybatisplusdemo.mapper: DEBUG
效果演示
当你执行一个userMapper.selectById(1L)
操作时,控制台将会打印出类似如下的详细日志:
DEBUG com.example.mybatisplusdemo.mapper.UserMapper.selectById - ==> Preparing: SELECT id,user_name,age,email,create_time,update_time,deleted,version FROM t_user WHERE id = ? AND deleted=0
DEBUG com.example.mybatisplusdemo.mapper.UserMapper.selectById - ==> Parameters: 1(Long)
DEBUG com.example.mybatisplusdemo.mapper.UserMapper.selectById - <== Total: 1
==> Preparing
: 显示了即将执行的、带?
占位符的SQL语句。==> Parameters
: 显示了传入的参数值和类型。<== Total
: 显示了查询返回的记录总数。这种方式简单、直接,并且能与你的项目日志系统完美融合,是现代开发的首选。
这是一个强烈建议在所有项目中开启的“救命”插件。它能有效防止因代码失误(例如,UpdateWrapper
为空)而导致对整张表进行更新或删除的毁灭性操作。
BlockAttackInnerInterceptor
该插件的作用是,在执行UPDATE
或DELETE
语句时,检查其是否带有WHERE
条件。如果没有,则直接抛出异常,阻止该SQL的执行。
在我们的MybatisPlusConfig
配置类中,将其作为内部拦截器添加到MybatisPlusInterceptor
中。
// config/MyBatisPlusConfig.java
@Configuration
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 1. 添加【防止全表更新与删除】插件
interceptor.addInnerInterceptor(new BlockAttackInnerInterceptor());
// 2. 添加分页插件 (如果需要)
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
}
让我们故意写一个错误的代码,尝试更新所有用户的邮箱。
@Test
void testBlockAttack() {
// 错误示范:创建一个空的UpdateWrapper,没有指定任何WHERE条件
UpdateWrapper<User> updateWrapper = new UpdateWrapper<>();
updateWrapper.set("email", "[email protected]");
User user = new User(); // entity参数可以为null
// 尝试执行更新
// 期望抛出MyBatisPlusException异常
assertThrows(MyBatisPlusException.class, () -> {
userMapper.update(user, updateWrapper);
});
System.out.println("操作已被成功拦截,数据库安然无恙!");
}
当你运行这个测试时,程序会立即抛出MyBatisPlusException
,控制台会打印出类似Prohibition of full table update
的错误信息。你的数据库因此幸免于难!这个插件就像一个永远警惕的哨兵,强烈推荐大家始终开启它。
在开发阶段及时发现并优化慢查询,是保证应用性能的关键。
PerformanceInterceptor
(已废弃)在旧版本中,MP提供了一个PerformanceInterceptor
,它可以打印每条SQL的执行耗时,并在超过设定的阈值时抛出异常。但请注意:这个插件在新版本中已被标记为@Deprecated
(废弃),不推荐使用。 因为它的功能在生产环境中应该由更专业的工具来承担。
slow_query_log
,它可以记录所有执行时间超过指定阈值的SQL,是定位生产环境慢查询最直接的方式。p6spy
或datasource-proxy
这样的库。它们能代理你的数据源,详细记录每一条SQL的执行耗时、参数等信息,非常适合在开发和测试阶段进行性能分析。结论:对于性能监控,我们应该采用更专业的、与框架解耦的工具,而不是依赖MP中一个已废弃的开发期插件。
这是本章的重头戏,也是最能体现你对框架理解深度的部分。当MP的内置功能无法满足我们某些特殊、全局性的需求时,我们就可以通过实现MyBatis的Interceptor
接口来开发自己的插件。
需求:我们希望User
实体中的email
字段在存入数据库时自动加密,在从数据库中查询出来时自动解密。整个过程对业务代码透明。
第一步:创建自定义注解@EncryptField
这个注解将用于标记哪些字段需要被我们的插件处理。
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface EncryptField {}
第二步:在实体类上使用注解
public class User {
// ...
@EncryptField
private String email;
}
第三步:开发核心的EncryptInterceptor
插件
我们将拦截ParameterHandler
(处理入参,用于加密)和ResultSetHandler
(处理出参,用于解密)。
import org.apache.ibatis.executor.parameter.ParameterHandler;
import org.apache.ibatis.executor.resultset.ResultSetHandler;
import org.apache.ibatis.plugin.*;
import java.sql.Statement;
import java.util.Properties;
// ... 其他import
@Slf4j
// @Intercepts注解声明这是一个拦截器
@Intercepts({
// @Signature定义要拦截的目标、方法和参数类型
@Signature(type = ParameterHandler.class, method = "setParameters", args = {java.sql.PreparedStatement.class}),
@Signature(type = ResultSetHandler.class, method = "handleResultSets", args = {Statement.class})
})
public class EncryptInterceptor implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
// 1. 获取目标对象
Object target = invocation.getTarget();
// 2. 根据目标类型,执行不同逻辑
if (target instanceof ParameterHandler) {
// --- 参数处理(加密) ---
ParameterHandler parameterHandler = (ParameterHandler) target;
// 获取参数对象 (通常是Entity或Map)
Object parameterObject = parameterHandler.getParameterObject();
if (parameterObject != null) {
// 遍历对象的字段,找到标记了@EncryptField的进行加密
handleEncryption(parameterObject);
}
} else if (target instanceof ResultSetHandler) {
// --- 结果集处理(解密) ---
// 先执行原方法,得到结果集
Object result = invocation.proceed();
if (result != null) {
// 遍历结果集,对每个对象找到标记了@EncryptField的进行解密
handleDecryption(result);
}
return result;
}
// 3. 执行原方法
return invocation.proceed();
}
// ... 加密 handleEncryption 和解密 handleDecryption 的具体实现 ...
// (通常使用反射遍历字段,判断注解,然后调用加密/解密工具类)
// 省略加解密具体实现细节,仅作演示
private void handleEncryption(Object object) { /* ... */ }
private void handleDecryption(Object object) { /* ... */ }
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
@Override
public void setProperties(Properties properties) {
// 可用于接收插件配置
}
}
第四步:注册自定义插件
和注册其他插件一样,在MybatisPlusConfig
中将其加入。
@Configuration
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() { /* ... */ }
@Bean
public EncryptInterceptor encryptInterceptor() {
return new EncryptInterceptor();
}
}
注意: 自定义的MyBatis原生插件需要独立注册为Bean,它不会通过
MybatisPlusInterceptor.addInnerInterceptor()
添加。Spring Boot会自动发现Interceptor
类型的Bean并应用它。
通过以上步骤,我们就实现了一个对业务代码完全透明的自动加解密插件。这个例子充分展示了MyBatis插件机制的强大与灵活。
在本章中,我们从“功能实现”的层面,上升到了“质量保障”和“底层扩展”的层面。我们学到了:
BlockAttackInnerInterceptor
的重要性,并学会了如何配置它来防止灾难性的全表更新/删除操作。至此,你不仅是一个MyBatis-Plus的使用者,更具备了守护其运行、优化其性能、扩展其功能的能力。你已经是一位能够驾驭复杂生产环境的、真正的MyBatis-Plus专家!
如对本文内容有任何疑问、建议或意见,请联系作者,作者将尽力回复并改进;( 联系微信:Solitudemind )
点击下方名片,加入 IT 技术核心学习团队。一起探索科技的未来,共同成长。
为了让您拥有更好的交互体验,特将这行文字设置为可点击样式:点击下方名片,加入 IT
技术核心学习团队。一起探索科技的未来,共同成长。