MyBatis核心面试题与高级特性解析

发布时间:2026/8/22 4:41:22
MyBatis核心面试题与高级特性解析 1. MyBatis核心面试题解析作为一名使用MyBatis多年的开发者我整理了一些高频面试题和深度解析这些问题不仅能检验基础掌握程度更能看出对框架原理的理解深度。以下是常见的MyBatis面试题目及其技术要点分析。1.1 #{}和${}的区别这是MyBatis中最基础的面试题但能区分出开发者对SQL注入和安全性的理解程度。${}直接字符串替换将参数原样拼接到SQL中存在SQL注入风险。例如ORDER BY ${columnName}这里的columnName会直接替换为传入的字符串值。#{}参数化查询MyBatis会使用PreparedStatement的?占位符能有效防止SQL注入。例如WHERE name #{name}会被编译为WHERE name ?然后通过预编译设置参数。实际开发中除了动态排序等特殊场景99%的情况都应该使用#{}。我曾见过因为滥用${}导致的生产环境SQL注入案例后果非常严重。1.2 动态SQL标签MyBatis提供了9种动态SQL标签这是其强大灵活性的体现标签作用示例if条件判断if testname ! nullAND name #{name}/ifchoose/when/otherwise多路选择类似Java的switch-caseforeach循环遍历常用于IN条件bind创建变量bind namepattern value% name % /trim智能去除多余关键字替代where/set标签我在实际项目中经常使用foreach处理批量操作insert idbatchInsert INSERT INTO user(name,age) VALUES foreach collectionlist itemuser separator, (#{user.name}, #{user.age}) /foreach /insert1.3 一级缓存与二级缓存缓存机制是MyBatis性能优化的关键点一级缓存默认开启SqlSession级别同一个SqlSession中相同的查询会直接返回缓存结果执行任何UPDATE操作都会清空缓存二级缓存需要手动配置Mapper级别跨SqlSession共享需要实体类实现Serializable接口可通过cache标签精细控制缓存使用不当会导致脏数据问题。我曾遇到过一个典型的案例在批量操作中混用了不同SqlSession由于没有正确配置二级缓存导致数据不一致。解决方案是明确缓存作用域必要时使用CacheNamespace注解控制。2. MyBatis高级特性解析2.1 插件开发原理MyBatis插件是基于JDK动态代理实现的可以拦截四大组件的方法拦截点Executor (update, query, commit等)StatementHandler (prepare, parameterize等)ParameterHandler (setParameters)ResultSetHandler (handleResultSets)实现步骤Intercepts({ Signature(type Executor.class, method query, args {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}) }) public class MyPlugin implements Interceptor { Override public Object intercept(Invocation invocation) throws Throwable { // 前置处理 Object result invocation.proceed(); // 后置处理 return result; } }配置方式plugins plugin interceptorcom.example.MyPlugin property namesomeProperty value100/ /plugin /plugins2.2 分页实现原理MyBatis分页有三种实现方式内存分页使用RowBounds在ResultSet返回后截取SQL分页直接写limit/offset语句分页插件如PageHelper原理是拦截Executor方法重写SQL我比较推荐使用PageHelper它的工作流程// 启动分页 PageHelper.startPage(1, 10); // 接下来执行的查询会自动分页 ListUser users userMapper.selectAll(); // 获取分页信息 PageInfoUser pageInfo new PageInfo(users);注意PageHelper的startPage()必须紧跟在查询语句前调用中间不能有其他SQL执行否则会导致分页失效。3. MyBatis与Spring整合3.1 整合配置要点Spring Boot中配置MyBatis的几种方式注解方式Mapper public interface UserMapper { Select(SELECT * FROM user WHERE id #{id}) User selectById(Param(id) Long id); }XML配置方式mybatis: mapper-locations: classpath:mapper/*.xml type-aliases-package: com.example.model混合模式Mapper public interface UserMapper { Select(SELECT * FROM user WHERE id #{id}) User selectById(Param(id) Long id); // 引用XML中的SQL User selectComplexUser(Param(query) UserQuery query); }对应XMLselect idselectComplexUser resultTypeUser SELECT * FROM user where if testquery.name ! nullAND name LIKE #{query.name}/if if testquery.age ! nullAND age #{query.age}/if /where /select3.2 事务管理MyBatis本身没有事务管理能力需要依赖Spring的事务管理Service Transactional public class UserService { private final UserMapper userMapper; public void updateUser(User user) { userMapper.update(user); // 其他数据库操作 } }常见坑点在同一个Service方法中调用多个Mapper方法时如果没有正确配置事务传播行为可能导致部分操作成功、部分失败的情况。建议明确使用Transactional(propagation Propagation.REQUIRED)。4. 性能优化实践4.1 批量操作优化MyBatis批量操作有三种模式简单模式多次单条操作性能最差Batch模式SqlSession session sqlSessionFactory.openSession(ExecutorType.BATCH); try { UserMapper mapper session.getMapper(UserMapper.class); for (User user : users) { mapper.insert(user); } session.commit(); } finally { session.close(); }批量SQL如前面提到的foreach方式测试数据插入1000条记录简单模式需要3秒Batch模式仅需0.5秒批量SQL仅需0.3秒。但批量SQL有SQL长度限制超长SQL会导致数据库拒绝执行。4.2 延迟加载配置延迟加载可以优化复杂对象的查询性能settings setting namelazyLoadingEnabled valuetrue/ setting nameaggressiveLazyLoading valuefalse/ /settings使用示例resultMap iduserWithOrders typeUser collection propertyorders columnid selectcom.example.mapper.OrderMapper.findByUserId fetchTypelazy/ /resultMap注意延迟加载在Web场景中容易引发N1问题建议在Service层使用DTO模式提前加载所需数据或者在Controller层使用JsonIgnore避免意外触发查询。5. 常见问题排查5.1 参数映射问题现象参数值为null或类型不匹配排查步骤检查Mapper方法参数是否使用Param注解检查XML中的参数名是否一致检查参数类型是否匹配使用日志查看实际参数mybatis: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl5.2 结果映射问题现象查询结果字段为null解决方案检查数据库字段名与Java属性名是否匹配开启驼峰转换mybatis: configuration: map-underscore-to-camel-case: true使用显式结果映射resultMap iduserResult typeUser result propertyuserName columnuser_name/ result propertycreatedAt columncreate_time/ /resultMap6. MyBatis最佳实践SQL管理复杂SQL写在XML中简单SQL可以使用注解使用sql片段复用公共部分结果处理使用DTO而非Entity作为返回类型对于统计查询使用Map或MapKey处理性能监控集成P6Spy打印真实SQL使用Spring Boot Actuator监控SQL执行时间代码生成使用MyBatis Generator生成基础代码自定义CommentGenerator添加中文注释使用Lombok减少样板代码!-- generatorConfig.xml示例 -- context idmysql targetRuntimeMyBatis3 plugin typeorg.mybatis.generator.plugins.LombokPlugin/ commentGenerator typecom.example.CustomCommentGenerator property namesuppressDate valuetrue/ /commentGenerator /context7. MyBatis与MyBatis-Plus对比特性MyBatisMyBatis-PlusCRUD操作需手动编写内置通用Mapper条件构造XML/注解Lambda表达式分页支持需插件内置分页插件代码生成需额外配置内置生成器性能原生轻微损耗学习曲线陡峭平缓选择建议需要高度定制化SQL选MyBatis快速开发CRUD应用选MyBatis-Plus大型复杂系统可以混合使用8. 源码解析要点理解MyBatis源码的几个关键入口启动过程SqlSessionFactoryBuilder.build()XMLConfigBuilder.parse()SQL执行流程SqlSession.selectOne()Executor.query()StatementHandler.prepare()插件机制InterceptorChain.pluginAll()Plugin.invoke()缓存实现PerpetualCache (一级缓存)TransactionalCache (二级缓存)阅读源码时建议从SimpleExecutor这个最简单的执行器入手逐步深入理解插件对执行过程的增强。9. 实际案例分享最近优化过的一个复杂查询案例原始方案ListUser users userMapper.selectAll(); users.forEach(user - { ListOrder orders orderMapper.findByUserId(user.getId()); user.setOrders(orders); });问题N1查询问题性能极差优化方案使用collection实现一对多查询添加二级缓存使用DTO投影只查询必要字段优化后QPS从50提升到1200内存消耗降低60%。10. 面试准备建议基础部分掌握#{}和${}的区别熟悉动态SQL标签理解缓存机制进阶部分能解释插件原理理解延迟加载实现知道不同Executor的区别实战经验准备优化案例熟悉常见问题排查了解源码关键设计综合能力对比MyBatis与其他ORM框架讨论分布式环境下的缓存策略分析MyBatis的设计优缺点最后提醒面试时遇到你还有什么问题时可以询问团队如何使用MyBatis是否有特殊的规范或扩展这能展现你的实际经验和思考深度。