当前位置: 首页 > news >正文

Mybatis相关面试题

MyBatis高频面试题全解析(含原理+实战+避坑)

MyBatis作为Java后端核心持久层框架,是面试中必问的重点模块。本文整理了MyBatis从基础概念、核心原理实战应用、性能优化的全维度高频面试题,结合业务场景给出精准回答思路,既覆盖入门级考点,也包含中高级面试的深度问题。

一、基础概念类(入门必答)

1. 什么是MyBatis?它与JDBC、Hibernate的核心区别是什么?

核心回答

MyBatis是一款轻量级的半自动化ORM持久层框架,通过XML/注解将SQL与Java代码解耦,支持自定义SQL、存储过程和高级映射,既保留了JDBC的灵活性,又简化了数据库操作。

特性JDBCMyBatisHibernate
SQL控制手动编写,繁琐易出错自定义编写,灵活可控自动生成,无需手动编写(全自动化)
开发效率低(需手动处理连接、结果集)中(简化连接/结果映射)高(全自动化ORM)
性能高(无额外封装)较高(轻量封装,接近JDBC)较低(自动SQL优化差,重量级)
学习成本高(需掌握HQL、缓存、关联映射)
适用场景简单小项目互联网高并发、需定制SQL的场景企业级应用、非高并发场景

核心区别总结

  • 对比JDBC:MyBatis封装了连接管理、结果集映射,消除重复代码,保留SQL编写灵活性;
  • 对比Hibernate:MyBatis是“半自动化”,需手动写SQL,适合需要精细优化SQL的场景;Hibernate是“全自动化”,无需写SQL,适合快速开发但SQL优化能力弱。

2. MyBatis的核心组件有哪些?各自的作用是什么?

核心回答

MyBatis的核心组件围绕“SQL执行流程”设计,核心组件及作用如下:

  1. SqlSessionFactory:会话工厂,MyBatis的核心入口,通过SqlSessionFactoryBuilder从配置文件/代码构建,单例设计,负责创建SqlSession
  2. SqlSession:会话对象,封装了数据库操作(CRUD),是MyBatis与数据库交互的核心接口,线程不安全,需每次使用后关闭;
  3. Mapper接口:自定义的DAO接口,MyBatis通过动态代理将接口与XML/注解中的SQL绑定,无需编写实现类;
  4. Executor:执行器,SqlSession的底层执行引擎,负责SQL执行和缓存管理,分为:
    • SimpleExecutor:默认执行器,每次执行新建Statement;
    • ReuseExecutor:复用Statement执行器;
    • BatchExecutor:批量执行器;
  5. StatementHandler:处理Statement(PreparedStatement/Statement)的创建、参数设置、SQL执行;
  6. ParameterHandler:处理SQL参数的映射,将Java参数转换为JDBC参数;
  7. ResultSetHandler:处理SQL结果集,将JDBC结果集映射为Java对象;
  8. Configuration:MyBatis的全局配置对象,存储所有配置信息(数据源、映射器、插件等)。

3. MyBatis中#{ }和${ }的区别?使用时需要注意什么?

核心回答

两者都是参数占位符,核心差异在于底层实现和安全特性

特性#{ }${ }
底层实现JDBC预编译(PreparedStatement)字符串直接拼接
SQL注入防护支持(参数作为占位符,自动转义)不支持(直接拼接,存在注入风险)
引号自动添加是(字符串自动加单引号)否(需手动添加)
适用场景普通参数注入(条件、字段值)动态SQL拼接(表名、排序字段)

使用注意事项

  1. 优先使用#{ },覆盖99%的常规场景,杜绝SQL注入;
  2. 仅在动态拼接SQL(如动态表名、动态排序字段)时使用${ },且必须对入参做严格合法性校验(如限制排序字段只能是id、user_name等固定值);
  3. 示例:
    <!-- 安全:普通参数注入 -->SELECT * FROM t_user WHERE id = #{id}<!-- 谨慎使用:动态表名,需校验tableName的合法性 -->SELECT * FROM ${tableName} WHERE id = #{id}

二、核心原理类(中高级必答)

1. MyBatis的Mapper接口为什么不需要实现类?底层动态代理原理是什么?

核心回答

MyBatis通过JDK动态代理为Mapper接口生成代理实现类,核心流程如下:

  1. 当调用SqlSession.getMapper(UserMapper.class)时,MyBatis会为Mapper接口创建动态代理对象;
  2. 代理对象拦截接口方法调用,获取方法对应的namespace + methodName,匹配Mapper.xml中的SQL;
  3. 封装方法入参为SQL参数,通过Executor执行SQL,处理结果集并映射为返回值;
  4. 返回处理后的结果给调用方。

核心源码逻辑(简化)

// MapperProxy是动态代理的核心类,实现InvocationHandlerpublicclassMapperProxy<T>implementsInvocationHandler{@OverridepublicObjectinvoke(Objectproxy,Methodmethod,Object[]args)throwsThrowable{// 1. 获取方法对应的MappedStatement(封装SQL的对象)StringstatementId=mapperInterface.getName()+"."+method.getName();MappedStatementms=configuration.getMappedStatement(statementId);// 2. 执行SQL并处理结果returnsqlSession.selectOne(statementId,args);}}

总结:Mapper接口仅作为“SQL映射标识”,MyBatis通过动态代理将接口方法与SQL绑定,无需手动编写实现类,简化开发。

2. MyBatis的一级缓存和二级缓存有什么区别?如何使用?

核心回答

MyBatis的缓存分为一级缓存(本地缓存)和二级缓存(全局缓存),核心作用是减少数据库查询次数,提升性能:

特性一级缓存二级缓存
作用域SqlSession(会话级别)Mapper(命名空间级别)
生命周期随SqlSession创建/关闭而变化随SqlSessionFactory生命周期存在
默认状态开启(无法关闭)关闭(需手动开启)
数据共享仅当前SqlSession可见所有SqlSession共享
存储介质内存内存/磁盘(可自定义)

使用方式

  1. 一级缓存:无需配置,默认开启。同一SqlSession中,相同SQL查询会先从一级缓存获取,若缓存未失效(无更新/删除操作),则不查数据库;
    • 失效场景:SqlSession关闭/提交/回滚、执行update/delete/insert操作、手动清除缓存;
  2. 二级缓存
    • 步骤1:全局配置开启二级缓存(MyBatis-config.xml):
      <settings><settingname="cacheEnabled"value="true"/><!-- 默认true,可省略 --></settings>
    • 步骤2:在Mapper.xml中开启缓存:
      <mappernamespace="com.example.dao.UserMapper"><cache/><!-- 开启当前Mapper的二级缓存 --><!-- 或自定义缓存配置 --><cacheeviction="LRU"flushInterval="60000"size="1024"readOnly="true"/></mapper>
    • 步骤3:实体类需实现Serializable接口(缓存序列化需要)。

注意事项

  • 二级缓存适用于读多写少的场景,写操作频繁时会导致缓存频繁失效,反而降低性能;
  • 多表关联查询时,二级缓存易出现数据不一致(如User和Order关联,更新Order后User缓存未更新),需谨慎使用。

3. MyBatis的动态SQL有哪些标签?各自的作用是什么?

核心回答

动态SQL是MyBatis的核心特性,通过标签实现SQL的灵活拼接,解决手动拼接SQL的繁琐与易错问题,核心标签及作用:

标签核心作用示例场景
<if>单条件判断,动态添加SQL片段根据入参是否非空,添加查询条件
<where>自动处理WHERE关键字和多余AND/OR结合使用,避免无条件时出现WHERE 1=1
<set>自动处理SET关键字和多余逗号动态更新非空字段,剔除最后一个逗号
<choose>多条件分支(if-else)多个条件中仅满足一个(如按名称或ID查询)
<foreach>循环遍历集合批量查询(IN条件)、批量新增/删除
<trim>自定义字符串截取替代/,灵活处理前缀/后缀
<sql>抽取公共SQL片段复用字段列表(如SELECT id,user_name FROM)
<include>引用定义的公共片段配合实现SQL复用

核心示例

<!-- <if>+<where>:动态查询 --><selectid="selectUser"resultType="User">SELECT * FROM t_user<where><iftest="userName != null">AND user_name LIKE #{userName}</if><iftest="age != null">AND age = #{age}</if></where></select><!-- <foreach>:批量删除 --><deleteid="deleteByIds">DELETE FROM t_user WHERE id IN<foreachcollection="ids"item="id"open="("separator=","close=")">#{id}</foreach></delete>

4. MyBatis如何解决字段名与实体类属性名不一致的问题?

核心回答

有3种主流解决方案,按推荐优先级排序:

  1. 开启驼峰命名自动映射(推荐)
    在MyBatis-config.xml中配置,自动将数据库下划线命名(如user_name)映射为Java驼峰命名(如userName):
    <settings><settingname="mapUnderscoreToCamelCase"value="true"/></settings>
  2. 使用resultMap手动映射
    适用于字段名与属性名差异较大的场景,自定义字段与属性的映射关系:
    <resultMapid="userResultMap"type="User"><idcolumn="user_id"property="id"/><!-- 数据库user_id → 实体id --><resultcolumn="user_name"property="userName"/><!-- user_name → userName --></resultMap><selectid="selectById"resultMap="userResultMap">SELECT user_id, user_name FROM t_user WHERE user_id = #{id}</select>
  3. SQL别名
    在SQL中为字段添加别名,匹配实体类属性名:
    <selectid="selectById"resultType="User">SELECT user_id AS id, user_name AS userName FROM t_user WHERE user_id = #{id}</select>

三、实战应用类(高频业务场景)

1. MyBatis中updateByPrimaryKey和updateByPrimaryKeySelective的区别?实际开发中选哪个?

核心回答

两者均为按主键更新单条数据,核心差异在于更新字段范围

方法名更新字段范围风险点适用场景
updateByPrimaryKey全字段更新空字段会覆盖数据库原有值单条全字段更新(极少用)
updateByPrimaryKeySelective仅更新非空字段无(空字段不更新)单条部分字段更新(日常主流)

实战建议

  • 优先使用updateByPrimaryKeySelective,避免因入参空字段导致数据库数据丢失;
  • 示例:入参仅封装userNameid,则仅更新user_name字段,ageemail等字段保持原有值。

2. MyBatis如何实现分页?有哪些方式?

核心回答

主流有3种分页方式,按推荐优先级排序:

  1. MyBatis-Plus分页插件(推荐)
    基于MyBatis扩展,配置简单,支持物理分页:
    // 配置分页插件@BeanpublicMybatisPlusInterceptormybatisPlusInterceptor(){MybatisPlusInterceptorinterceptor=newMybatisPlusInterceptor();interceptor.addInnerInterceptor(newPaginationInnerInterceptor(DbType.MYSQL));returninterceptor;}// 使用Page<User>page=newPage<>(1,10);// 第1页,每页10条userMapper.selectPage(page,null);
  2. 手动编写分页SQL(原生MyBatis)
    基于数据库方言实现(MySQL用LIMIT,Oracle用ROWNUM):
    <selectid="selectUserPage"resultType="User">SELECT * FROM t_user LIMIT #{offset}, #{size}</select>
  3. PageHelper分页插件
    第三方插件,拦截SQL自动添加分页条件:
    PageHelper.startPage(1,10);List<User>userList=userMapper.selectAll();PageInfo<User>pageInfo=newPageInfo<>(userList);

注意:避免使用“内存分页”(查询所有数据后在Java端分页),数据量大时会导致内存溢出。

3. MyBatis如何实现批量操作(新增/更新/删除)?

核心回答
  1. 批量新增(推荐)
    <insertid="batchInsert">INSERT INTO t_user (user_name, age) VALUES<foreachcollection="list"item="user"separator=",">(#{user.userName}, #{user.age})</foreach></insert>
  2. 批量更新
    • 方式1:拼接UPDATE(MySQL支持):
      <updateid="batchUpdate">UPDATE t_user SET user_name = CASE id<foreachcollection="list"item="user">WHEN #{user.id} THEN #{user.userName}</foreach>END WHERE id IN<foreachcollection="list"item="user"open="("separator=","close=")">#{user.id}</foreach></update>
    • 方式2:通过ExecutorType.BATCH批量执行:
      SqlSessionsqlSession=sqlSessionFactory.openSession(ExecutorType.BATCH);UserMappermapper=sqlSession.getMapper(UserMapper.class);for(Useruser:list){mapper.updateByPrimaryKeySelective(user);}sqlSession.commit();sqlSession.close();
  3. 批量删除
    <deleteid="batchDelete">DELETE FROM t_user WHERE id IN<foreachcollection="ids"item="id"open="("separator=","close=")">#{id}</foreach></delete>

实战注意:批量操作需控制数据量(建议每批次1000条以内),避免单条SQL过长导致数据库锁表。

四、性能优化类(高级面试)

1. MyBatis的性能优化有哪些常用手段?

核心回答

从“SQL、缓存、执行、配置”4个维度优化:

  1. SQL优化
    • 避免SELECT *,只查询需要的字段;
    • 为查询条件字段建立索引;
    • 减少关联查询,拆分复杂SQL;
    • 批量操作替代单条循环操作;
  2. 缓存优化
    • 合理使用一级缓存(默认开启);
    • 读多写少场景开启二级缓存;
    • 避免多表关联查询使用二级缓存(防止数据不一致);
  3. 执行优化
    • 使用ReuseExecutor/BatchExecutor提升执行效率;
    • 关闭不必要的日志(如DEBUG级别);
    • 使用连接池(如Druid)管理数据库连接,避免频繁创建/关闭连接;
  4. 配置优化
    • 开启驼峰命名自动映射,减少resultMap配置;
    • 配置延迟加载(association/collection的lazyLoad),减少不必要的关联查询;
    • 禁用二级缓存的脏读保护(按需),提升缓存命中率。

2. MyBatis的延迟加载是什么?如何实现?

核心回答

延迟加载(懒加载)指关联查询时,先加载主表数据,关联表数据在真正使用时才查询,核心作用是减少不必要的数据库查询,提升性能。

实现方式

  1. 全局开启延迟加载(MyBatis-config.xml):
    <settings><settingname="lazyLoadingEnabled"value="true"/><!-- 开启延迟加载 --><settingname="aggressiveLazyLoading"value="false"/><!-- 关闭积极懒加载 --></setting>
  2. 在resultMap中配置关联查询的延迟加载:
    <resultMapid="userWithOrderMap"type="User"><idcolumn="id"property="id"/><!-- 一对多关联,延迟加载订单数据 --><collectionproperty="orderList"javaType="List"ofType="Order"select="com.example.dao.OrderMapper.selectByUserId"column="id"fetchType="lazy"/></resultMap>

注意:延迟加载基于动态代理实现,仅在调用关联属性(如user.getOrderList())时才执行关联查询;若关联数据必用,建议关闭延迟加载,避免多次查询数据库。

五、总结

关键点回顾

  1. MyBatis核心优势是SQL灵活可控,对比JDBC简化开发,对比Hibernate更适合高并发、需定制SQL的场景;
  2. 核心原理围绕动态代理(Mapper接口)、执行器(Executor)、缓存(一级/二级)展开,是中高级面试的核心考点;
  3. 实战中优先使用#{ }updateByPrimaryKeySelective、驼峰命名映射,避免SQL注入和数据丢失;
  4. 性能优化需结合“SQL、缓存、执行、配置”多维度,核心是减少数据库查询次数和优化SQL执行效率。
http://www.jsqmd.com/news/436533/

相关文章:

  • 实战指南|XSS攻击完整防御方案(前端+后端,零基础也能上手)
  • 2026年口碑好的家具拉手 工厂推荐:意法式家具拉手/高端定制家具拉手/衣柜橱柜家具拉手长期合作厂家推荐 - 行业平台推荐
  • 2026年口碑好的上海轻便婴儿车 品牌推荐:上海双胞胎婴儿车/上海遛娃神器婴儿车优质供应商推荐参考 - 行业平台推荐
  • 如何把千问(Qwen)用出“200%”的效果?
  • SpringBoot 统一功能处理!
  • 2025板材厂家排名 - 品牌推荐(官方)
  • 2026年热门的六角网眼布 工厂推荐:透气网眼布/三明治网眼布直销厂家选哪家 - 行业平台推荐
  • 价值投资新方向!AI应用架构师的多智能体系统精准分析思路
  • 巴甫洛夫-经典条件作用理论
  • 养虾盛行,AI焦虑症蔓延,六个普通人和AI扛过的这一年
  • [AI智能体与提效-147] - 编排的含义、实现方法与示例
  • 【OpenClaw学习笔记】第二天:认识Ollama
  • 数据科学与大数据技术专业毕业设计选题方向全汇总(2026最新版)
  • windows系统官网下载以及制作U盘启动盘
  • 通义千问Qwen的核心能力,适用场景与优势是哪些?
  • 2026年知名的塑料粉碎机 工厂推荐:边料粉碎机/机边粉碎机/注塑机边粉碎机稳定供应商推荐 - 行业平台推荐
  • 10大美国展会搭建服务商推荐,专注展台设计与搭建好评如潮
  • Hootime 的难度评价体系
  • 2026年山东聊城发电机租赁标杆厂家最新推荐:发电机租赁,发电车出租、电源车出租、UPS电源出租、山东京荣机械设备,临时供电服务新标杆 - 海棠依旧大
  • 2026年沈阳口碑好的汽车贴膜专业店选哪家,玻璃膜/沈北车衣/太阳膜/贴车衣/车衣改色/改色膜,汽车贴膜品牌有哪些 - 品牌推荐师
  • 离子通道稳定细胞系终极指南:从瞬时转染到可重复模型的科研跃迁
  • [AI智能体与提效-143] - 编程的三次范式跃迁:从死板合规到灵活共生
  • 2026年评价高的矿石吨袋 工厂推荐:航吊吨袋/危化品吨袋/运输包装吨袋优质供应商推荐 - 行业平台推荐
  • 使用curl持续测试接口响应时间
  • 2026年隐形车衣改色优选:国内服务优质车衣店,车衣改色/改色膜/沈北车衣/沈北贴膜/太阳膜,隐形车衣店铺怎么选择 - 品牌推荐师
  • 2026年比较好的船舶高压直流继电器 工厂推荐:充电桩高压直流继电器/航空航天高压直流继电器/军工设备高压直流继电器高口碑品牌推荐 - 行业平台推荐
  • 765525
  • 神秘闲话
  • 张大头闭环步进电机恢复出厂设置
  • 9、传统 CV 之图像分割(大津算法)