
1. MyBatis面试题深度解析从实际经验出发作为一名长期使用MyBatis的开发者和面试官我整理了一些在实际面试中经常被问到的MyBatis核心问题。这些问题看似基础但能准确回答的人并不多。下面我将结合源码和实际项目经验对这些面试题进行详细解析。1.1 #{}和${}的区别及使用场景这个问题几乎出现在90%的MyBatis面试中但能完整回答清楚的人不到一半。核心区别${}是字符串替换直接拼接SQL语句存在SQL注入风险#{}是参数占位符会预编译处理使用PreparedStatement的set方法安全赋值实际项目中的坑!-- 错误示例使用${}导致SQL注入 -- select idgetUser resultTypeUser SELECT * FROM user WHERE name ${name} /select !-- 正确做法使用#{} -- select idgetUser resultTypeUser SELECT * FROM user WHERE name #{name} /select什么时候必须用${}动态表名场景SELECT * FROM ${tableName}动态排序字段ORDER BY ${column} ${direction}动态GROUP BY字段提示即使必须使用${}也要对传入参数进行严格校验和过滤防止SQL注入1.2 MyBatis的一级缓存和二级缓存缓存问题是MyBatis面试中的高频考点很多开发者对两者的区别理解不清晰。一级缓存特点默认开启作用域为SqlSession级别同一个SqlSession中相同的查询会命中缓存执行update/insert/delete或调用clearCache()会清空缓存二级缓存特点需要手动配置开启作用域为Mapper级别跨SqlSession共享缓存数据需要实体类实现Serializable接口可通过cache-ref实现多个Mapper共享缓存实际项目经验!-- 开启二级缓存配置 -- cache evictionLRU flushInterval60000 size512 readOnlytrue/缓存失效的常见场景执行了增删改操作调用了SqlSession的clearCache()设置了flushCachetrue配置了不同的缓存实现导致冲突2. MyBatis动态SQL的深度应用2.1 动态SQL标签全解析MyBatis提供了9种动态SQL标签但在实际项目中很多开发者只熟悉if和foreach。完整标签列表if条件判断choose/when/otherwise多条件选择trim自定义前后缀where智能添加WHERE子句set智能处理UPDATE语句foreach遍历集合bind创建变量sql定义可重用SQL片段include引用SQL片段实际项目示例select idsearchUsers resultTypeUser SELECT * FROM user where if testname ! null AND name LIKE CONCAT(%, #{name}, %) /if if teststatus ! null AND status #{status} /if if testids ! null and ids.size() 0 AND id IN foreach collectionids itemid open( separator, close) #{id} /foreach /if /where choose when testorderBy ! null ORDER BY ${orderBy} /when otherwise ORDER BY create_time DESC /otherwise /choose /select2.2 动态SQL的性能优化动态SQL虽然灵活但不当使用会导致性能问题避免过度使用if标签过多的条件判断会增加SQL解析开销合理使用sql片段重复的SQL片段应该提取重用注意foreach批量操作一次性插入大量数据时要考虑数据库承受能力慎用${}动态排序可能破坏SQL预编译优势3. MyBatis插件开发与原理3.1 插件实现原理MyBatis插件基于JDK动态代理实现可以拦截以下4种接口的方法Executor (update, query, flushStatements等)StatementHandler (prepare, parameterize等)ParameterHandler (getParameterObject, setParameters)ResultSetHandler (handleResultSets, handleOutputParameters)插件开发步骤实现Interceptor接口使用Intercepts注解指定要拦截的方法在配置文件中注册插件3.2 实际案例SQL执行时间监控插件Intercepts({ Signature(type Executor.class, method update, args {MappedStatement.class, Object.class}), Signature(type Executor.class, method query, args {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}) }) public class SqlExecuteTimeInterceptor implements Interceptor { Override public Object intercept(Invocation invocation) throws Throwable { long start System.currentTimeMillis(); try { return invocation.proceed(); } finally { long time System.currentTimeMillis() - start; String method invocation.getMethod().getName(); System.out.println(method SQL执行耗时: time ms); } } Override public Object plugin(Object target) { return Plugin.wrap(target, this); } Override public void setProperties(Properties properties) { // 可以读取配置参数 } }插件配置plugins plugin interceptorcom.example.SqlExecuteTimeInterceptor property namethreshold value500/ /plugin /plugins4. MyBatis高级特性与最佳实践4.1 延迟加载原理与优化MyBatis的延迟加载通过CGLIB创建代理对象实现配置开启延迟加载settings setting namelazyLoadingEnabled valuetrue/ setting nameaggressiveLazyLoading valuefalse/ /settings关联对象配置resultMap iduserWithOrders typeUser collection propertyorders columnid selectcom.example.mapper.OrderMapper.findByUserId fetchTypelazy/ /resultMap延迟加载的优化建议对于频繁访问的关联对象考虑关闭延迟加载注意N1查询问题必要时使用JOIN查询合理设置fetchType对性能敏感的场景手动控制加载策略4.2 批量操作性能优化MyBatis提供了三种Executor执行器其中BatchExecutor专门用于批量操作// 使用BatchExecutor执行批量插入 try (SqlSession sqlSession sqlSessionFactory.openSession(ExecutorType.BATCH)) { UserMapper mapper sqlSession.getMapper(UserMapper.class); for (int i 0; i 1000; i) { mapper.insert(new User(user i)); if (i % 200 0) { sqlSession.flushStatements(); // 分批提交 } } sqlSession.commit(); }批量操作的最佳实践合理设置batchSize避免单次提交数据量过大对于MySQL考虑使用rewriteBatchedStatementstrue参数提升批量插入性能批量操作后及时清理缓存防止内存溢出4.3 类型处理器(TypeHandler)高级应用自定义TypeHandler可以处理特殊数据类型转换MappedTypes(String.class) MappedJdbcTypes(JdbcType.VARCHAR) public class EncryptTypeHandler extends BaseTypeHandlerString { private final Encryptor encryptor new AESEncryptor(); Override public void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType) { ps.setString(i, encryptor.encrypt(parameter)); } Override public String getNullableResult(ResultSet rs, String columnName) { String value rs.getString(columnName); return value ! null ? encryptor.decrypt(value) : null; } // 其他重载方法... }配置使用自定义TypeHandlerresultMap iduserResult typeUser result columnphone propertyphone typeHandlercom.example.EncryptTypeHandler/ /resultMap在实际项目中我经常使用TypeHandler处理以下场景数据加解密JSON字符串与对象互转枚举类型特殊处理数据库特定格式的解析5. MyBatis源码层面的高频面试题5.1 Mapper接口的工作原理这是面试高级岗位时必问的问题考察候选人对MyBatis核心机制的理解。Mapper执行流程通过JDK动态代理创建Mapper接口的代理对象代理对象拦截方法调用将方法转换为对应的MappedStatement根据SQL类型调用SqlSession的对应方法(select/insert/update/delete)执行SQL并处理结果集关键源码分析// MapperProxy.java - 代理类的核心方法 public Object invoke(Object proxy, Method method, Object[] args) { if (Object.class.equals(method.getDeclaringClass())) { return method.invoke(this, args); } // 将方法调用转换为MapperMethod执行 return cachedMapperMethod(method).execute(sqlSession, args); }5.2 MyBatis的缓存实现原理一级缓存实现使用PerpetualCache实现底层是HashMap在BaseExecutor中维护执行update操作或调用clearCache()时会清空缓存二级缓存实现通过CacheBuilder创建使用装饰器模式实现多种功能(同步、LRU、FIFO等)事务提交时才会将数据放入二级缓存缓存同步问题在分布式环境中MyBatis的二级缓存会存在一致性问题。实际项目中我们通常会禁用二级缓存使用Redis等集中式缓存实现自定义Cache接口集成分布式缓存设置合理的flushInterval和size6. MyBatis与Spring集成中的常见问题6.1 事务管理问题常见问题表现事务不生效嵌套事务处理不当事务传播行为不符合预期解决方案确保配置了事务管理器Bean public PlatformTransactionManager transactionManager(DataSource dataSource) { return new DataSourceTransactionManager(dataSource); }正确使用Transactional注解Service public class UserService { Transactional public void createUser(User user) { // 业务逻辑 } }6.2 多数据源配置实际项目中经常需要配置多个数据源Configuration MapperScan(basePackages com.example.mapper.db1, sqlSessionFactoryRef db1SqlSessionFactory) public class Db1Config { Bean ConfigurationProperties(spring.datasource.db1) public DataSource db1DataSource() { return DataSourceBuilder.create().build(); } Bean public SqlSessionFactory db1SqlSessionFactory() throws Exception { SqlSessionFactoryBean factory new SqlSessionFactoryBean(); factory.setDataSource(db1DataSource()); return factory.getObject(); } }多数据源下的注意事项事务管理器需要明确指定不同数据源的Mapper接口要分开扫描跨数据源事务需要使用JTA等分布式事务方案7. MyBatis性能调优实战经验7.1 SQL执行性能分析使用插件拦截SQL并记录执行时间开启MyBatis的日志输出logging.level.org.mybatisdebug logging.level.jdbc.sqlonlyinfo结合数据库的慢查询日志分析7.2 结果集处理优化常见性能瓶颈大结果集导致内存溢出复杂对象映射耗时延迟加载导致的N1问题优化方案使用分页查询限制结果集大小对于大数据量查询使用ResultHandler流式处理sqlSession.select(queryUsers, parameter, new ResultHandler() { Override public void handleResult(ResultContext context) { User user (User) context.getResultObject(); // 处理单个对象 } });合理使用fetchTypelazy避免不必要的数据加载8. MyBatis与MyBatis-Plus的对比8.1 MyBatis-Plus的核心优势强大的CRUD操作内置通用Mapper减少样板代码条件构造器支持Lambda表达式类型安全的查询条件代码生成器快速生成Entity、Mapper、Service等代码分页插件简化分页操作支持多种数据库性能分析插件内置SQL性能分析功能8.2 迁移注意事项从MyBatis迁移到MyBatis-Plus需要考虑现有Mapper接口的兼容性自定义SQL的调整插件机制的差异事务管理的变化示例使用MyBatis-Plus的条件构造器// 传统MyBatis方式 Select(SELECT * FROM user WHERE name LIKE #{name} AND age #{age}) ListUser findUsers(Param(name) String name, Param(age) int age); // MyBatis-Plus方式 ListUser users userMapper.selectList( new QueryWrapperUser() .like(name, name) .gt(age, age) );在实际项目中我通常会根据项目复杂度选择简单CRUD项目使用MyBatis-Plus提高开发效率复杂SQL项目使用原生MyBatis保持灵活性大型项目混合使用简单操作用MyBatis-Plus复杂SQL用原生方式9. MyBatis在实际项目中的架构设计9.1 分层架构建议推荐的分层结构Controller层处理HTTP请求Service层业务逻辑处理Manager层复杂业务编排DAO层数据库访问Entity层数据模型各层职责划分Controller参数校验、结果封装Service业务规则、事务控制Manager跨Service协调、缓存处理DAO纯粹的数据库操作9.2 复杂查询的设计模式查询对象模式封装复杂查询条件public class UserQuery { private String name; private Integer minAge; private Integer maxAge; private ListInteger statusList; // getters/setters } // Mapper接口 ListUser findByQuery(UserQuery query);构建器模式动态构建复杂查询public class UserQueryBuilder { private String name; private Integer age; public UserQueryBuilder name(String name) { this.name name; return this; } public UserQuery build() { // 构建查询对象 } }策略模式根据不同条件选择不同查询策略10. MyBatis常见问题排查指南10.1 典型错误及解决方案问题1Parameter xxx not found原因Mapper方法参数没有使用Param注解解决添加Param注解或修改参数名问题2Invalid bound statement (not found)原因Mapper接口与XML文件不匹配XML文件未被扫描到方法名不一致解决检查namespace和方法名确认XML文件位置正确清理编译输出重新构建问题3嵌套结果映射导致内存溢出原因一对多查询返回大量数据解决使用分页查询调整fetchType为lazy使用ResultHandler流式处理10.2 性能问题排查流程确认是否SQL执行慢查看数据库慢查询日志使用MyBatis插件记录SQL执行时间分析执行计划在数据库客户端执行EXPLAIN检查是否使用索引检查结果集处理是否返回过多不必要字段是否处理大量数据未分页排查缓存问题缓存命中率缓存失效策略11. MyBatis与微服务架构的适配11.1 在微服务中的最佳实践数据源配置每个微服务使用独立数据库分布式ID生成使用雪花算法等分布式ID方案分库分表支持集成ShardingSphere等中间件多租户实现通过动态数据源或SQL改写实现11.2 与Spring Cloud集成配置示例spring: datasource: url: jdbc:mysql://localhost:3306/user username: root password: 123456 hikari: maximum-pool-size: 20 mybatis: mapper-locations: classpath:mapper/*.xml type-aliases-package: com.example.entity集成要点使用Spring Cloud Config统一管理配置结合Hystrix实现数据库访问熔断通过Sleuth实现SQL调用链路追踪12. MyBatis的未来发展趋势虽然MyBatis已经非常成熟但在云原生时代仍面临挑战和机遇响应式编程支持适应Reactive编程模型云原生适配更好的Kubernetes集成多模数据库支持统一访问关系型和NoSQL数据库AI增强智能SQL优化建议在实际项目技术选型时我通常会考虑项目团队的技术栈熟悉度项目的复杂度和发展预期性能要求和扩展性需求长期维护成本对于新启动的项目如果团队熟悉MyBatis且项目中有复杂SQL需求MyBatis仍然是很好的选择。而对于简单的CRUD项目可以考虑JPA或MyBatis-Plus提高开发效率。