跳到主要内容

映射器原理

问题

MyBatis 的 Mapper 接口如何工作?resultMap 和 resultType 有什么区别?关联查询如何实现?

答案

Mapper 接口代理原理

Mapper 接口没有实现类,MyBatis 通过 JDK 动态代理生成代理对象:

方法全限定名 = 接口全限定名.方法名,对应 XML 中的 namespace.id

resultType vs resultMap

resultType — 简单映射
<!-- 列名和属性名一致时使用 -->
<select id="getById" resultType="com.example.entity.User">
SELECT id, name, age, email FROM user WHERE id = #{id}
</select>

<!-- 列名和属性名不一致时,用 AS 别名解决 -->
<select id="getById" resultType="User">
SELECT id, user_name AS userName, create_time AS createTime FROM user WHERE id = #{id}
</select>
resultMap — 复杂映射
<resultMap id="userMap" type="User">
<id property="id" column="id"/>
<result property="userName" column="user_name"/>
<result property="createTime" column="create_time"/>
</resultMap>

<select id="getById" resultMap="userMap">
SELECT id, user_name, create_time FROM user WHERE id = #{id}
</select>
对比resultTyperesultMap
适用场景列名与属性名一致列名与属性名不一致、复杂映射
关联查询不支持支持(association、collection)
继承不支持支持(extends)
使用方式指定类的全限定名引用已定义的 resultMap id
开启驼峰映射

大多数场景下开启驼峰自动映射即可,无需每次写 resultMap:

mybatis:
configuration:
map-underscore-to-camel-case: true # user_name → userName

关联查询(一对一、一对多)

一对一:association
<resultMap id="orderMap" type="Order">
<id property="id" column="order_id"/>
<result property="orderNo" column="order_no"/>
<!-- 嵌套结果映射(一条 SQL + JOIN) -->
<association property="user" javaType="User">
<id property="id" column="user_id"/>
<result property="name" column="user_name"/>
</association>
</resultMap>

<select id="getOrderWithUser" resultMap="orderMap">
SELECT o.id AS order_id, o.order_no, u.id AS user_id, u.name AS user_name
FROM orders o
LEFT JOIN user u ON o.user_id = u.id
WHERE o.id = #{id}
</select>
一对多:collection
<resultMap id="userWithOrders" type="User">
<id property="id" column="id"/>
<result property="name" column="name"/>
<!-- 嵌套查询(两条 SQL,支持延迟加载) -->
<collection property="orders" ofType="Order"
select="com.example.mapper.OrderMapper.getByUserId"
column="id" fetchType="lazy"/>
</resultMap>

嵌套结果 vs 嵌套查询

方式嵌套结果(JOIN)嵌套查询(子查询)
SQL 数量1 条(JOIN)N+1 条
性能较好有 N+1 问题
延迟加载不支持支持
适用场景数据量小、必须一起使用按需加载、数据独立

延迟加载(懒加载)

application.yml
mybatis:
configuration:
lazy-loading-enabled: true # 全局开启延迟加载
aggressive-lazy-loading: false # 关闭积极加载

延迟加载基于动态代理:返回的关联对象是代理对象,当访问其属性时才触发实际的 SQL 查询。


常见面试问题

Q1: Mapper 接口的工作原理?

答案

MyBatis 通过 JDK 动态代理 为 Mapper 接口生成代理对象(MapperProxy)。调用接口方法时,代理根据方法的全限定名(接口全名.方法名)找到对应的 MappedStatement,获取 SQL、参数映射和结果映射信息,委托给 SqlSession 执行。

Q2: resultMap 和 resultType 的区别?

答案

resultType 直接指定返回类型,列名需与属性名一致(或开启驼峰映射)。resultMap 可以自定义列名到属性名的映射,支持关联查询(association/collection)、继承鉴别器等复杂映射。简单查询用 resultType,复杂映射用 resultMap。

Q3: MyBatis 如何解决 N+1 查询问题?

答案

N+1 问题来自嵌套查询(collection 的 select 方式):1 次主查询 + N 次子查询。解决方案:

  1. 使用嵌套结果映射(JOIN 一次查出所有数据)
  2. 开启延迟加载(只在需要时才查询)
  3. 使用BatchExecutor批量执行

Q4: #的底层实现原理?

答案

#{} 在 MyBatis 解析阶段被替换为 ? 占位符,对应 JDBC 的 PreparedStatement。在执行阶段,ParameterHandler 通过 TypeHandler 将 Java 参数按正确的 JDBC 类型设置到 PreparedStatement 中(ps.setString(1, value))。预编译机制防止了 SQL 注入。

相关链接