MyBatis魔法堂:结果集映射

一、ResultMap 的定义

  在当今的软件开发领域,MyBatis 作为一款优秀的持久层框架,以其简洁的配置和强大的功能,深受广大开发者的喜爱。然而,在实际的项目开发中,我们常常会遇到数据模型与数据库表结构不一致的情况,这时就需要 MyBatis 的 resultMap 功能来帮助我们实现复杂的映射关系。想象一下,一个典型的业务场景:一个电商系统中的订单表,其字段包括订单ID、用户ID、商品ID、订单金额等。然而,在业务逻辑层,我们可能需要将订单信息与对应的用户信息和商品信息结合起来,以便于进行后续的业务处理。如果直接使用 MyBatis 的基本映射功能,我们只能获取到订单的基本信息,而无法获取到与之关联的用户和商品信息。这时,ResultMap 就显得尤为重要。

  在 MyBatis 中,ResultMap 是核心概念之一,用于定义复杂映射关系的关键功能,它定义了 SQL 查询结果与 Java 对象之间的映射关系。它允许开发者将数据库表中的字段映射到实体类的属性上,同时也可以实现多表联合查询,将多个表的数据映射到一个实体类中。通过 ResultMap,我们可以轻松地实现数据模型与数据库表结构之间的映射,从而简化了数据访问层的开发。

二、基本配置

  在开发过程中,我们经常会遇到数据库表与实体类(Entity)结构不一致的情况。例如,一个数据库表可能包含多个字段,而实体类只包含其中的一部分。在这种情况下,如果直接使用 MyBatis 的自动映射功能,可能会导致数据丢失或错误。映射关系是 ResultMap 的核心概念,它定义了数据库表字段与 Java 对象属性之间的对应关系。

标签 映射类型 简要说明
id 主键映射 将数据库中的主键字段映射到Java对象的属性上,用于唯一标识记录
result 普通属性映射 将数据库中的普通字段映射到Java对象的属性上
association 关联映射 将查询结果中的关联对象映射到Java对象的属性上
collection 集合映射 将查询结果中的集合对象映射到Java对象的属性上
select 查询语句 定义查询操作,指定查询结果映射规则
resultMap 映射配置 定义查询结果的映射规则,包括字段映射、关联映射和集合映射

2.1 resultMap:结果映射

  ResultMap 是 MyBatis 框架中用于将数据库查询结果映射为 Java 对象的一种配置,它定义了如何将查询结果集中的列映射到 Java 对象的属性上。通过 resultMap,我们可以自定义字段与实体类属性的映射关系,从而实现灵活的数据转换。

xml 复制代码
<resultMap id="BaseResultMap" type="com.lilith.entity.User">
  <id column="id" property="id" />
  <result column="user_name" property="userName" />
  <result column="real_name" property="realName" />
  <result column="sex" property="sex" />
</resultMap>

  一个标准的 resultMap 配置通常包含以下核心属性:

属性 是否必须 简要说明
id 当前命名空间中的唯一标识符
type 指定映射的目标 Java 类的全限定名或别名,指定 MyBatis 将查询结果封装成哪种类型的对象
extends 用于继承其他resultMap,减少重复代码并提高代码的可维护性
autoMapping 是否启用自动映射功能。 如果开启(默认情况),自动将结果集中与 Java 对象属性同名(或符合驼峰命名规则)的列进行映射 若设置为 false,则关闭自动映射,必须显式配置所有字段的映射关系

💡 根据 MyBatis 的 DTD 约束,resultMap 元素内的子元素必须按照特定顺序排列:constructor ➡️ id ➡️ result ➡️ association ➡️ collection ➡️ discriminator。

2.2 id:主键映射

  id 标签是 resultMap 中的一个重要属性,用于指定实体类的主键字段,确保每次操作都能准确找到对应的实体对象。在查询数据时,MyBatis 会将查询结果中的主键值与实体类的主键字段进行映射。id 属性的配置对于正确实现结果映射至关重要,MyBatis 会用其值作为对象标识,提升缓存效率和对象比较准确性。

xml 复制代码
<id property="id" column="user_id" javaType="int" jdbcType="INTEGER" />
属性 是否必须 简要说明
property 指定实体类中对应的主键属性
column 指定数据库表中的主键字段
javaType 指定Java对象属性的数据类型
jdbcType 指定SQL语句中占位符的JDBC类型
typeHandler 实现Java类型与JDBC类型之间的自定义转换逻辑

2.3 result:普通字段映射

  ResultMap 是一个 XML 元素,通常包含了一个或多个 Result 元素,每个 Result 元素用于指定实体类中的非主键字段与数据库表字段的映射关系。

xml 复制代码
<result property="name" column="user_name" javaType="String" jdbcType="VARCHAR" />
属性 是否必须 简要说明
column 对应数据库中的字段名。如果 SQL 中使用了别名,这里需填写别名
property 对应 Java 实体类中的属性名
javaType 一个 Java 类的全限定名,或一个类型别名
jdbcType 指定数据库列类型,避免类型转换错误(如 NULL 值处理)
typeHandler 指定类型处理器,用于处理Java类型与数据库类型之间的转换

2.4 association:一对一关联

  一对一关联是指一个实体类对应数据库中的一张表,而另一个实体类与第一个实体类存在一对一的关系。在 MyBatis 中,可以使用 ResultMap 的子标签 association 来实现一对一映射,允许我们在查询一个对象时,同时获取其关联的对象。

xml 复制代码
<association property="address" javaType="Address" column="address_id" select="selectAddressById" />
属性 是否必须 简要说明
property 指定映射到实体类中的对象属性名称
column 指定数据库表中对应的字段名或列别名,该字段的值将作为参数传递给嵌套查询
javaType 指定该属性对应的 Java 类型
jdbcType
select 指定另一个映射查询的 ID,MyBatis 会额外执行该 SQL 语句获取关联对象的结果
resultMap 指定关联实体的映射配置
typeHandler
notNullColumn 如果设置了该属性,只有当指定列的值为非 NULL 时才会执行关联查询,用于优化查询效率
columnPrefix 当关联表的列名与主表的列名冲突时,可以为关联表的列名添加前缀以区分
resultSet
foreignColumn
autoMapping 指定是否启用自动映射。默认为true,表示自动映射列到Java对象的属性忽略大小写
fetchType 控制关联数据的加载方式,可选值为 lazy(延迟加载)或 eager(积极加载)

2.5 collection:一对多关联

  一对多关联是指一个实体类对应数据库中的一张表,而另一个实体类与第一个实体类存在一对多的关系。在 MyBatis 中,可以使用 ResultMap 的子标签 collection 来实现一对多关联,允许我们在查询一个对象时,同时获取其关联的集合对象。

xml 复制代码
<collection property="posts" javaType="ArrayList" column="id" ofType="Post" select="selectPostsForBlog"/>
属性 是否必须 简要说明
property 指定映射到实体类中的对象属性名称
column 指定数据库表中对应的字段名(或列别名),该字段的值将作为参数传递给嵌套查询
ofType 指定集合中元素的Java类型
jdbcType 指定该属性对应的 Java 类型
select 指定另一个映射查询的 ID,MyBatis 会额外执行该 SQL 语句获取关联对象的结果
resultMap 指定集合中元素的映射配置
typeHandler
notNullColumn 如果设置了该属性,只有当指定列的值为非 NULL 时才会执行关联查询,用于优化查询效率。‌
columnPrefix 当关联表的列名与主表的列名冲突时,可以为关联表的列名添加前缀以区分
resultSet
foreignColumn
autoMapping 指定是否启用自动映射。默认为true,表示自动映射列到Java对象的属性忽略大小写
fetchType 控制关联数据的加载方式,可选值为 lazy(延迟加载)或 eager(积极加载)

三、基本用法

3.1 简单映射

  我们可以使用 resultMap 标签自定义结果集和实体类属性的映射规则,假设有一个 User 类,其属性与数据库中的字段不完全对应,可以这样定义 resultMap:

xml 复制代码
<!-- 1️⃣、定义 resultMap -->
<resultMap id="userResultMap" type="User">
	<id property="id" column="user_id"/>
	<result property="userName" column="user_name"/>
	<result property="password" column="user_pwd"/>
</resultMap>

<!-- 2️⃣、在查询中引用 resultMap -->
<select id="selectUserById" resultMap="userResultMap" parameterType="int">
	SELECT user_id, user_name, user_pwd FROM users WHERE user_id = #{id}
</select>

  这样,无论字段名如何不同,MyBatis 都能根据 resultMap 将数据正确赋值到 User 对象上。

3.2 多表查询

  有的时候需要查询多张表的数据才可以得到我们想要的结果,为此,可以直接写一个多表关联的SQL进行查询,也可以分步进行多次的查询来拿到我们需要的结果。Mybatis 就提供了对应的配置,可以让我们去更方便的进行相应的查询和对应的结果集处理。

关联映射(一对一)

  假设每个用户都有一个详细地址信息,这里的地址信息相对于用户是一对一。在查询用户的同时查询用户的地址,可以在 resultMap 中配置如下:

xml 复制代码
<resultMap id="addressMap" type="org.dllwh.mybatis.model.Address">
  <id property="id" column="addr_id"/>
  <result property="street" column="street"/>
  <result property="city" column="city"/>
  <result property="zipCode" column="zipCode"/>
</resultMap>

<resultMap id="userWithAddressMap" type="User">
  <id property="id" column="id"/>
  <result property="userName" column="userName"/>
  <result property="nickName" column="nickName"/>
  <!-- 一对一关联:使用 association 标签 -->
  <association property="address" resultMap="addressMap" />
</resultMap>

<select id="selectUserWithAddress" resultMap="userWithAddressMap" parameterType="int">
	select suu.*, a.* from sys_upms_user suu
	left join address a on suu.id = a.user_id
	where id = #{userId}
</select>

  如果有需要多表查询的需求,也可以选择用多次查询的方式来查询出想要的数据,Mybatis 也提供了对应的配置。例如获取查询用户时,还需查询出该用户所具有的地址信息。我们可以选择先查询User表查询用户信息,然后在去查询关联的地址信息。

xml 复制代码
<!--
	select属性:指定用哪个查询来查询当前属性的数据
	column属性:设置当前结果集中哪列的数据作为select属性指定的查询方法需要参数
-->
<resultMap id="userWithAddressMap" type="User">
  <id property="id" column="user_id"/>
  <result property="userName" column="user_name"/>
  <result property="password" column="user_pwd"/>
  <!-- 关联映射,通过 user_id 关联地址 -->
  <association property="address" column="id" select="selectAddressByUserId"/>
</resultMap>

<!-- 1️⃣、根据userId查询用户 -->
<select id="selectUserWithAddress" resultMap="userWithAddressMap" parameterType="int">
    SELECT user_id, user_name, user_pwd FROM users WHERE user_id = #{id}
</select>

<!-- 2️⃣、根据userId查询所具有的地址信息 -->
<!-- 定义查询地址的 SQL -->
<select id="selectAddressByUserId" resultType="Address" parameterType="int">
    SELECT addr_id AS id, street, city, zip_code AS zipCode FROM address WHERE user_id = #{userId}
</select>

集合映射(一对多)

  假设每个用户可以有多个角色,两个实体之间是一对多的关系。例如在查询用户的同时,还需要该用户所具有的角色信息,可以在 resultMap 中配置如下:

xml 复制代码
<!--定义User基本属性映射规则-->
<resultMap id="userMap" type="org.dllwh.mybatis.model.SysUser">
  <id property="id" column="id"/>
  <result property="username" column="username" />
	<result property="age" column="age" />
	<result property="address" column="address" />
  <collection property="roles" resultMap="userRoleMap"/>
</resultMap>

<resultMap id="userRoleMap" type="org.dllwh.mybatis.model.SysRole" >
  <id property="id" column="roleId"/>
  <result property="roleName" column="roleName"/>
  <result property="roleCode" column="roleCode"/>
</resultMap>

<select id="getUserLst" resultMap="userMap">
  SELECT suu.*, sur.*
  from sys_upms_user suu
  LEFT JOIN sys_upms_user_role suur on suur.userId = suu.id
  LEFT JOIN sys_upms_role sur on suur.roleId = sur.id
</select>

3.3 类型处理器

  在 MyBatis 框架中,结果映射(Result Mapping)是核心功能之一,它负责将数据库查询结果映射到 Java 对象的属性上。类型处理器(Type Handler)是 MyBatis 提供的一种机制,用于处理 Java 类型与数据库类型之间的转换。类型处理器的工作原理是,当MyBatis在执行查询操作时,会根据SQL语句的结果集类型,调用相应的类型处理器进行数据转换。类型处理器内部定义了转换规则,将数据库中的数据类型转换为Java对象中的属性类型。

java 复制代码
public interface TypeHandler<T> {
	// 将数据库类型转换为Java类型
	T fromDatabaseType(Object databaseValue);
	// 将Java类型转换为数据库类型
	Object toDatabaseType(T javaObject);
}

  在 MyBatis 配置文件中,类型处理器与数据库类型映射是通过定义 typeHandler 属性来实现。例如,将 VARCHAR 类型的数据库字段映射到 Java 对象的 String 属性:

xml 复制代码
<resultMap id="userResultMap" type="User">
	<result column="name" property="name" typeHandler="com.example.MyCustomTypeHandler"/>
</resultMap>

  自定义类型处理器可以满足特定业务需求,以下是一个简单的自定义类型处理器示例:

java 复制代码
public class MyCustomTypeHandler implements TypeHandler<String> {
  @Override
  public String fromDatabaseType(Object databaseValue) {
    // 将数据库类型转换为Java类型
    return (String) databaseValue;
  }

  @Override
  public Object toDatabaseType(String javaObject) {
    // 将Java类型转换为数据库类型
    return javaObject;
  }
}

四、总结

  MyBatis 的 resultMap 提供了强大而灵活的机制,使得开发者能够精准地控制 SQL 查询结果与 Java 对象之间的映射关系。无论是简单的单表查询还是复杂的多表联合查询,通过合理设计 resultMap,都能大幅提高数据访问层代码的可读性、可维护性和性能。这种灵活性正是 MyBatis 被广泛使用的重要原因之一,也为开发者在实际项目中处理复杂数据模型提供了极大便利。

相关推荐
copyer_xyf2 小时前
LangChain 调用 LLM
后端·python·agent
copyer_xyf3 小时前
Prompt 组织管理
后端·python·agent
摇滚侠4 小时前
SpringMVC 入门到实战 文件上传 75-77
java·后端·spring·maven·intellij-idea
fox_lht6 小时前
15.3.改进我们之前的输入、输出项目
开发语言·后端·学习·rust
大鸡腿同学6 小时前
用 AI 肝了一个星期的智能客服助手,看看怎么个事
后端
IT_陈寒6 小时前
Python的os.path.join居然能这么坑?
前端·人工智能·后端
张忠琳7 小时前
【Go 1.26.4】Golang Channel 深度解析
开发语言·后端·golang
Rain5097 小时前
2.1 Nest.js 项目初始化与模块化架构
开发语言·前端·javascript·后端·架构·数据分析·node.js