作为一名长期使用MyBatis和MyBatis-Plus进行开发的Java工程师,我经常在面试中被问到相关技术问题。本文将系统整理这些常见面试题,并加入我在实际项目中的使用经验和技巧。
MyBatis的设计哲学是"半自动化ORM",这与Hibernate等全自动ORM框架形成鲜明对比。在实际项目中,这种设计带来了几个显著优势:
SQL可控性:在电商系统的商品搜索功能中,我们经常需要编写复杂的多条件查询SQL。使用MyBatis可以直接编写优化过的SQL,而不用受限于框架自动生成的SQL。
性能优化:在金融交易系统中,我们通过MyBatis直接控制SQL执行计划,针对高频查询添加特定索引提示,性能提升可达30%以上。
灵活的结果映射:在处理遗留系统时,数据库字段命名往往不规范。MyBatis的ResultMap可以完美解决字段名与属性名不一致的问题。
MyBatis-Plus在保持MyBatis所有特性的基础上,增加了许多开箱即用的功能:
通用CRUD:在后台管理系统中,90%的操作都是简单的增删改查。继承BaseMapper后,这些操作都不需要写任何SQL。
条件构造器:LambdaQueryWrapper让我们可以用Java8的Lambda表达式安全地构建查询条件,避免了字段名的硬编码。
自动填充:通过@TableField注解,可以实现创建时间、更新时间等字段的自动填充,大大减少了样板代码。
理解MyBatis的内部组件对排查问题非常有帮助。以下是一个典型查询的执行过程:
SqlSessionFactory:这是整个MyBatis的入口点。在我们的Spring Boot项目中,通常通过@Bean方式配置。
SqlSession:每个请求应该使用独立的SqlSession。实践中我们使用SqlSessionTemplate配合Spring的事务管理。
Executor:批量处理场景下,我们会显式使用BatchExecutor,将多个操作合并提交,性能提升显著。
StatementHandler:自定义插件经常通过拦截这个组件来实现SQL改写或性能监控。
MyBatis-Plus的BaseMapper之所以能提供通用方法,核心在于SQL注入器机制:
SqlInjector:负责将通用方法的SQL注入到MyBatis的MappedStatement中。
AbstractMethod:每个通用方法对应一个实现类,如SelectById、DeleteById等。
动态表名:通过@TableName注解和动态表名插件,我们实现了按月分表的订单查询功能。
在安全审计中,我们发现很多团队滥用${}导致SQL注入风险。我们的最佳实践是:
永远优先使用#{}:只有在动态表名、动态排序字段等必须使用字符串替换的场景才考虑${}。
严格的输入过滤:即使使用${},也要对输入值进行白名单校验。例如,表名只能包含字母数字和下划线。
MyBatis-Plus的安全封装:LambdaQueryWrapper内部全部使用#{},从根本上避免了注入风险。
在多表关联查询时,ResultMap的强大功能就显现出来了:
嵌套查询:适合关联数据量大且不总是需要的情况,配合延迟加载减少不必要查询。
嵌套结果:适合关联数据量小且总是需要的情况,一次JOIN查询获取所有数据。
鉴别器:在处理继承关系时特别有用,可以根据某字段值决定使用哪个结果映射。
在复杂的查询场景中,我们总结了一些经验:
MyBatis-Plus的Wrapper功能强大但容易误用:
Lambda表达式:优先使用LambdaQueryWrapper,编译期就能发现字段名错误。
条件优先级:复杂的AND/OR组合要合理使用nested()方法。
自定义SQL片段:对于Wrapper无法表达的复杂条件,可以使用apply()方法嵌入SQL片段。
一级缓存虽然能提升性能,但也带来了一些问题:
脏读风险:同一个SqlSession中,更新操作不会立即反映到后续查询中。
大对象内存占用:缓存大对象会导致内存压力,可以通过clearCache()手动清理。
分布式环境无效:在集群环境中,一级缓存的作用非常有限。
二级缓存需要谨慎使用:
序列化要求:缓存对象必须实现Serializable接口。
缓存策略:对于读多写少的数据比较适合,如省份字典表。
Redis集成:通过实现Cache接口,我们成功将二级缓存迁移到Redis集群。
MyBatis-Plus的分页插件非常方便,但要注意:
count查询优化:复杂查询的count语句可能需要重写。
内存分页:对于小结果集,有时直接内存分页性能更好。
前端参数安全:要校验pageSize最大值,防止恶意请求。
我们开发了几个实用的插件:
SQL审计插件:记录慢查询和敏感操作。
多租户插件:自动添加租户ID条件。
字段加密插件:透明加解密敏感数据。
MyBatis-Plus的乐观锁非常实用:
@Version注解:简单声明即可启用。
重试机制:冲突时要实现合理的重试策略。
版本号生成:可以使用时间戳或随机数,避免ABA问题。
逻辑删除虽然方便,但也有代价:
唯一约束:需要把删除标记字段加入唯一索引。
查询影响:所有查询都要自动过滤已删除记录。
级联删除:需要特别处理关联表的逻辑删除。
Mapper找不到:检查namespace是否与接口全限定名一致。
参数绑定失败:检查#{ }中的参数名是否与方法参数对应。
懒加载异常:确保在Session关闭前完成所有数据加载。
SQL日志分析:开启mybatis.sql日志级别。
执行计划检查:复杂SQL要查看数据库执行计划。
缓存命中率:监控二级缓存效果,调整缓存策略。
在实际项目中,MyBatis和MyBatis-Plus的组合使用效果非常好。对于简单的CRUD操作使用MyBatis-Plus提高效率,对于复杂查询则直接使用MyBatis的灵活SQL能力。这种混合模式既保证了开发效率,又不失灵活性。