MyBatis面试核心考点与实战解析
1. MyBatis面试核心考点解析作为Java生态中最受欢迎的ORM框架之一MyBatis在技术面试中的考察频率居高不下。根据笔者参与近百场技术面试的经验面试官通常会从框架原理、动态SQL、缓存机制、插件开发等维度展开考察。以下是经过整理的20个高频核心问题及其深度解析覆盖了初级到高级工程师的考察范围。重要提示MyBatis面试往往不是单纯考察API使用而是通过使用场景考察候选人对其设计思想的理解。建议在准备时多思考为什么这样设计。1.1 基础架构与核心组件问题1请描述MyBatis的核心架构组成及其交互流程标准答案通常会提到SqlSessionFactoryBuilder、SqlSessionFactory、SqlSession等组件但高级开发者需要理解更深层的设计配置体系XML配置与注解配置的加载优先级实测中注解优先级更高映射器代理Mapper接口通过JDK动态代理生成实现类背后是MapperProxy执行器分层BaseExecutor - SimpleExecutor/ReuseExecutor/BatchExecutor语句处理器StatementHandler处理参数设置和结果映射// 典型初始化流程示例 String resource mybatis-config.xml; InputStream inputStream Resources.getResourceAsStream(resource); SqlSessionFactory sqlSessionFactory new SqlSessionFactoryBuilder().build(inputStream);问题2#{}和${}的区别及使用场景这是引发SQL注入的高危考点#{}预编译处理底层使用PreparedStatement安全${}字符串替换直接拼接到SQL中需手动过滤动态表名场景必须用${}时应做白名单校验!-- 错误用法 -- SELECT * FROM ${tableName} WHERE id ${id} !-- 正确做法 -- SELECT * FROM ${tableName} (需校验tableName合法性) WHERE id #{id}1.2 动态SQL深度剖析问题3动态SQL的实现原理是什么MyBatis通过OGNL表达式和XML标签实现动态SQL解析阶段XMLScriptBuilder将标签转换为SqlNode树执行阶段DynamicSqlSource.getBoundSql()动态生成SQL典型标签if test...条件判断choose/when/otherwise多路选择foreach集合遍历注意collection属性的三种写法!-- 动态字段查询示例 -- select idselectByCondition SELECT foreach collectioncolumns itemcol separator, ${col} /foreach FROM users where if testname ! null AND name #{name} /if /where /select问题4如何解决动态SQL中的SQL注入问题当使用${}动态拼接时需特别注意字段名动态化时建立字段白名单使用SqlInjectionValidator工具类校验表名动态化时限制表名前缀如只允许tbl_开头使用正则表达式校验合法性// 安全校验工具示例 public class SqlValidator { private static final Pattern TABLE_PATTERN Pattern.compile(^[a-zA-Z_][a-zA-Z0-9_]{0,63}$); public static void checkTableName(String name) { if (!TABLE_PATTERN.matcher(name).matches()) { throw new IllegalArgumentException(Invalid table name); } } }2. 高级特性与性能优化2.1 缓存机制详解问题5MyBatis的一级缓存和二级缓存有什么区别特性一级缓存二级缓存作用范围SqlSession级别Mapper级别默认开启是否需 配置存储结构PerpetualCache可配置(Redis,Ehcache等)失效条件update/commit/close所有SqlSession的update操作踩坑提示分布式环境下二级缓存建议使用Redis实现本地缓存会导致数据不一致。我曾遇到过一个生产事故两个节点缓存不同步导致显示数据错乱。问题6如何实现自定义缓存通过实现Cache接口并配置public class RedisCache implements Cache { private final String id; private final JedisPool jedisPool; public RedisCache(String id) { this.id id; this.jedisPool new JedisPool(redis-server); } Override public String getId() { return id; } Override public void putObject(Object key, Object value) { try (Jedis jedis jedisPool.getResource()) { jedis.hset(id, key.toString(), serialize(value)); } } // 其他方法实现... }!-- 在mapper.xml中配置 -- cache typecom.example.RedisCache/2.2 插件开发实战问题7请描述MyBatis插件原理及实现步骤基于责任链模式的拦截器机制拦截点ParameterHandler、ResultSetHandler、StatementHandler、Executor实现步骤实现Interceptor接口用Intercepts注解指定拦截目标在mybatis-config.xml中注册插件Intercepts({ Signature(typeExecutor.class, methodupdate, args{MappedStatement.class,Object.class}) }) public class SlowQueryMonitor implements Interceptor { Override public Object intercept(Invocation invocation) throws Throwable { long start System.currentTimeMillis(); Object result invocation.proceed(); long cost System.currentTimeMillis() - start; if (cost 1000) { log.warn(Slow query detected: {}ms, cost); } return result; } }问题8插件执行顺序如何控制通过Interceptors注解的order属性控制值越小优先级越高但更常见的做法是在配置文件中声明顺序plugins !-- 先执行的插件放前面 -- plugin interceptorcom.plugin.A property nameorder value1/ /plugin plugin interceptorcom.plugin.B property nameorder value2/ /plugin /plugins3. 疑难问题排查方案3.1 典型异常处理问题9常见的MyBatis映射异常有哪些如何解决BindingException检查mapper.xml的namespace是否对应接口全限定名确认方法名与xml中的id一致TypeException参数类型不匹配检查#{param}的jdbcType结果集映射错误检查resultMap定义TooManyResultsExceptionselectOne方法返回了多行结果应改用selectList或添加LIMIT 1问题10如何排查动态SQL生成问题开启MyBatis日志logging.level.org.mybatisDEBUG使用SQL注入检查工具如阿里云Druid的WallFilter打印BoundSqlMappedStatement ms sqlSession.getConfiguration() .getMappedStatement(statementId); BoundSql boundSql ms.getBoundSql(parameterObject); System.out.println(boundSql.getSql());3.2 性能优化技巧问题11大数据量查询如何优化流式查询Select(SELECT * FROM large_table) Options(resultSetType FORWARD_ONLY, fetchSize 1000) void streamResults(ResultHandlerRow handler);分页优化避免使用limit 10000,20这种写法改用条件分页where id last_id limit 20结果集处理使用ResultHandler逐行处理关闭自动映射autoMappingBehaviorPARTIAL问题12批量插入的最佳实践使用BatchExecutorSqlSession session sqlSessionFactory.openSession(ExecutorType.BATCH); try { Mapper mapper session.getMapper(Mapper.class); for (Item item : items) { mapper.insert(item); } session.commit(); } finally { session.close(); }批量语法MySQLINSERT INTO table (col1,col2) VALUES (?,?),(?,?)参数化写法insert idbatchInsert INSERT INTO table (col1,col2) VALUES foreach collectionlist itemitem separator, (#{item.val1},#{item.val2}) /foreach /insert4. 架构设计与扩展实践4.1 多数据源整合方案问题13如何实现MyBatis多数据源Spring Boot下的标准实现配置多个DataSourceBean ConfigurationProperties(app.datasource.primary) public DataSource primaryDataSource() { return DataSourceBuilder.create().build(); } Bean ConfigurationProperties(app.datasource.secondary) public DataSource secondaryDataSource() { return DruidDataSourceBuilder.create().build(); }分别创建SqlSessionTemplateBean public SqlSessionTemplate primaryTemplate( Qualifier(primaryDataSource) DataSource dataSource) { return new SqlSessionTemplate( new SqlSessionFactoryBean() {{ setDataSource(dataSource); }}.getObject() ); }使用DS注解切换需集成dynamic-datasource问题14分库分表如何与MyBatis整合推荐方案使用ShardingSphere-JDBCspring: shardingsphere: datasource: names: ds0,ds1 sharding: tables: t_order: actual-data-nodes: ds$-{0..1}.t_order_$-{0..15} table-strategy: inline: sharding-column: order_id algorithm-expression: t_order_$-{order_id % 16}自定义路由注解Target({ElementType.METHOD}) Retention(RetentionPolicy.RUNTIME) public interface RouteBy { String shardingKey(); }4.2 与新技术栈整合问题15如何与Spring Boot深度集成自动配置要点MybatisAutoConfigurationMapperScan注解扫描路径配置文件前缀mybatis.configuration常用扩展mybatis: configuration: default-enum-type-handler: org.apache.ibatis.type.EnumOrdinalTypeHandler map-underscore-to-camel-case: true type-aliases-package: com.example.model mapper-locations: classpath*:mapper/**/*.xml健康检查Component public class MyBatisHealthIndicator implements HealthIndicator { Autowired private SqlSessionFactory sqlSessionFactory; Override public Health health() { try (SqlSession session sqlSessionFactory.openSession()) { session.selectOne(SELECT 1); return Health.up().build(); } catch (Exception e) { return Health.down(e).build(); } } }问题16MyBatis-Plus的核心优势是什么相较于原生MyBatis的增强CRUD增强内置通用MapperLambda条件构造器queryWrapper.lambda() .eq(User::getName, 张三) .gt(User::getAge, 18);代码生成自动生成Entity/Mapper/Service支持Freemarker/Velocity模板扩展功能乐观锁Version逻辑删除TableLogic自动填充TableField(fill FieldFill.INSERT)经验之谈MyBatis-Plus虽方便但复杂查询仍建议使用原生XML方式。我曾见过一个项目把所有SQL都写在Wrapper里导致后期难以维护。5. 源码级面试题精讲5.1 核心流程源码分析问题17描述SqlSession执行查询的完整流程关键调用链以selectOne为例DefaultSqlSession.selectOne()CachingExecutor.query()二级缓存检查BaseExecutor.query()一级缓存检查queryFromDatabase()SimpleExecutor.doQuery()创建StatementHandler参数处理ParameterHandler执行查询Statement.execute()结果映射ResultSetHandler问题18MyBatis如何实现延迟加载通过Javassist/CGLIB创建代理对象配置开启settings setting namelazyLoadingEnabled valuetrue/ setting nameaggressiveLazyLoading valuefalse/ /settings代理触发条件访问非主键字段时通过ProxyFactory.createProxy()生成代理实现原理ResultLoaderMap存储待加载属性ProxyFactory创建代理对象实际访问时通过ResultLoader.loadResult()触发查询5.2 设计模式应用问题19MyBatis中使用了哪些设计模式Builder模式SqlSessionFactoryBuilderXMLConfigBuilder工厂模式SqlSessionFactoryObjectFactory代理模式Mapper接口代理延迟加载代理责任链模式插件拦截器链模板方法BaseExecutor定义执行流程问题20插件机制是如何实现的核心类关系InterceptorChain维护拦截器列表Plugin使用JDK动态代理创建代理对象Invocation封装被拦截方法信息// 典型插件执行流程 public Object pluginAll(Object target) { for (Interceptor interceptor : interceptors) { target interceptor.plugin(target); if (target null) { throw new...; } } return target; }6. 实战经验与避坑指南6.1 生产环境常见问题类型转换陷阱枚举处理默认按名称存储EnumTypeHandler建议使用EnumOrdinalTypeHandler存序号日期处理JDBCType需明确指定#{createTime,jdbcTypeTIMESTAMP}集合参数List传参需用Param命名ListUser findByIds(Param(ids) ListLong ids);XML配置易错点特殊符号转义!-- 错误 -- WHERE age 18 !-- 正确 -- WHERE age lt; 18 或 ![CDATA[ WHERE age 18 ]]动态SQL中的and/or处理where if testname ! null name #{name} !-- 自动去除前导AND -- /if /where6.2 性能监控方案慢查询监控实现通过插件拦截Override public Object intercept(Invocation invocation) { long start System.nanoTime(); try { return invocation.proceed(); } finally { long cost (System.nanoTime() - start)/1000; if (cost slowThreshold) { logSlowQuery(invocation, cost); } } }集成SkyWalking/Arthas配置MyBatis探针监控SQL执行时间分布连接泄露检测重写PooledDataSourcepublic class LeakDetectDataSource extends PooledDataSource { private final ThreadLocalLong lastBorrowTime new ThreadLocal(); Override public PooledConnection getConnection() throws SQLException { PooledConnection conn super.getConnection(); lastBorrowTime.set(System.currentTimeMillis()); return new LeakAwareConnection(conn, () - { long holdTime System.currentTimeMillis() - lastBorrowTime.get(); if (holdTime 10000) { log.warn(Connection held too long: {}ms, holdTime); } }); } }在实际项目开发中我发现很多团队只关注MyBatis的基础使用而忽略了其设计精髓。建议读者在准备面试时多从框架设计者的角度思考各组件的关系这能帮助你在架构设计类问题中脱颖而出。对于常见的使用问题最好的学习方式是通过单元测试模拟各种边界场景这比单纯背诵面试题效果要好得多。

相关新闻

最新新闻

日新闻

周新闻

月新闻