Spring+SpringMVC+MyBatis深入学习及搭建(四)——MyBatis输入映射与输出映射

在MyBatis的核心工作流程中,输入映射(Parameter Mapping)和输出映射(Result Mapping)是连接Java代码与SQL语句的关键环节:

  • 输入映射:将Java方法的参数转换为SQL语句中的占位符(如#{param})值,解决"如何把Java参数传递给SQL"的问题。
  • 输出映射:将SQL查询的结果集转换为Java对象(或集合),解决"如何把数据库结果转换为Java对象"的问题。

理解并掌握这两种映射机制,是写出高效、可维护MyBatis代码的基础。本文将深入讲解MyBatis的输入/输出映射类型、使用场景、最佳实践,并通过大量示例帮助你快速上手。

目录#

  1. MyBatis输入映射:参数传递的艺术
    • 1.1 简单类型参数
    • 1.2 POJO参数
    • 1.3 Map参数
    • 1.4 List/Array参数
    • 1.5 @Param注解传递多参数
  2. MyBatis输出映射:结果转换的魔法
    • 2.1 resultType:简单结果映射
    • 2.2 resultMap:复杂结果映射的核心
    • 2.3 高级ResultMap:关联关系映射
      • 2.3.1 一对一(Association)
      • 2.3.2 一对多(Collection)
      • 2.3.3 鉴别器(Discriminator): polymorphic映射
  3. MyBatis映射最佳实践
  4. 常见陷阱与避坑指南
  5. 总结
  6. 参考资料

1. MyBatis输入映射:参数传递的艺术#

输入映射的核心是将Java方法的参数绑定到SQL语句的占位符。MyBatis支持多种参数类型,适用于不同场景。

1.1 简单类型参数#

适用场景:方法只有1个参数(如intStringlong等)。
规则:MyBatis会自动将参数绑定到SQL中的#{任意名称}(名称不影响,通常与参数名一致)。

示例#

Mapper接口

// 根据用户ID查询用户
User getUserById(int userId);

Mapper XML

<select id="getUserById" resultMap="UserResultMap">
  SELECT user_id, user_name, age FROM user WHERE user_id = #{userId}
</select>

1.2 POJO参数#

适用场景:方法参数是一个Java Bean(POJO),需要传递多个关联参数(如用户注册时传递User对象)。
规则:SQL中通过#{POJO字段名}绑定参数,MyBatis会自动调用POJO的getter方法获取值。

示例#

POJO类

@Data // Lombok注解,生成getter/setter
public class User {
  private int userId;
  private String userName;
  private int age;
}

Mapper接口

// 新增用户
int addUser(User user);

Mapper XML

<insert id="addUser" parameterType="User"> <!-- parameterType指定POJO类型 -->
  INSERT INTO user (user_name, age) VALUES (#{userName}, #{age})
</insert>

1.3 Map参数#

适用场景:参数个数不确定(如动态查询条件),或不想为少量参数创建POJO。
规则:SQL中通过#{Map的key}绑定参数,MyBatis从Map中读取对应key的值。

示例#

Mapper接口

// 动态条件查询用户
List<User> findUsersByMap(Map<String, Object> params);

调用代码

Map<String, Object> params = new HashMap<>();
params.put("userName", "张三");
params.put("age", 25);
List<User> users = userMapper.findUsersByMap(params);

Mapper XML

<select id="findUsersByMap" resultMap="UserResultMap">
  SELECT * FROM user 
  WHERE 1=1
  <if test="userName != null">AND user_name LIKE CONCAT('%', #{userName}, '%')</if>
  <if test="age != null">AND age = #{age}</if>
</select>

1.4 List/Array参数#

适用场景:需要执行IN查询(如根据多个ID查询用户)。
规则

  • List参数:SQL中通过#{list[索引]}访问,或用<foreach>遍历。
  • Array参数:SQL中通过#{array[索引]}访问,或用<foreach>遍历。

示例(List参数)#

Mapper接口

// 根据ID列表查询用户
List<User> findUsersByIds(List<Integer> ids);

Mapper XML

<select id="findUsersByIds" resultMap="UserResultMap">
  SELECT * FROM user WHERE user_id IN
  <foreach collection="list" item="id" open="(" separator="," close=")">
    #{id}
  </foreach>
</select>

说明:<foreach>标签属性#

  • collection:要遍历的集合(List用list,Array用array,@Param注解用注解值)。
  • item:遍历的当前元素别名。
  • open:遍历结果的开头字符(如()。
  • separator:元素之间的分隔符(如,)。
  • close:遍历结果的结尾字符(如))。

1.5 @Param注解传递多参数#

适用场景:方法有多个独立参数(如findUsers(String name, int age)),比Map更类型安全。
规则:在方法参数前加@Param("别名"),SQL中通过#{别名}绑定参数。

示例#

Mapper接口

// 根据姓名和年龄查询用户
List<User> findUsersByCondition(@Param("userName") String name, @Param("age") int age);

Mapper XML

<select id="findUsersByCondition" resultMap="UserResultMap">
  SELECT * FROM user WHERE user_name LIKE CONCAT('%', #{userName}, '%') AND age = #{age}
</select>

2. MyBatis输出映射:结果转换的魔法#

输出映射的核心是将SQL结果集转换为Java对象。MyBatis提供两种方式:resultType(简单映射)和resultMap(复杂映射)。

2.1 resultType:简单结果映射#

适用场景

  • 结果是简单类型(如count(*)、单个字段)。
  • 结果是POJO,且数据库列名与POJO字段名完全一致(或通过SQL别名一致)。

示例1:简单类型结果#

Mapper接口

// 查询用户总数
int countUsers();

Mapper XML

<select id="countUsers" resultType="int"> <!-- resultType指定返回类型 -->
  SELECT COUNT(*) FROM user
</select>

示例2:POJO结果(列名一致)#

数据库表结构user表有user_id(主键)、user_nameage列。
POJO字段userIduserNameage(注意驼峰命名)。
SQL别名:通过AS将列名转换为驼峰,匹配POJO字段。

Mapper XML

<select id="getUserById" resultType="User">
  SELECT 
    user_id AS userId, 
    user_name AS userName, 
    age 
  FROM user WHERE user_id = #{userId}
</select>

2.2 resultMap:复杂结果映射的核心#

适用场景

  • 数据库列名与POJO字段名不一致(无需SQL别名)。
  • 结果包含嵌套对象(如User包含Address)。
  • 结果包含集合(如User包含List<Order>)。
  • 复用映射规则(多个SQL共享同一个resultMap)。

定义resultMap的语法

<resultMap id="唯一标识" type="目标POJO类型">
  <!-- 主键列映射(必填,用于MyBatis识别唯一行) -->
  <id column="数据库列名" property="POJO字段名"/>
  <!-- 普通列映射 -->
  <result column="数据库列名" property="POJO字段名"/>
  <!-- 嵌套对象映射(一对一) -->
  <association property="POJO中的嵌套对象字段" resultMap="嵌套对象的resultMap"/>
  <!-- 集合映射(一对多) -->
  <collection property="POJO中的集合字段" ofType="集合元素类型" resultMap="集合元素的resultMap"/>
</resultMap>

示例:基础resultMap#

数据库列user_iduser_nameage
POJO字段userIduserNameage

定义resultMap

<resultMap id="UserResultMap" type="User">
  <id column="user_id" property="userId"/> <!-- 主键映射 -->
  <result column="user_name" property="userName"/> <!-- 普通列映射 -->
  <result column="age" property="age"/>
</resultMap>

使用resultMap

<select id="getUserById" resultMap="UserResultMap"> <!-- 引用resultMap -->
  SELECT user_id, user_name, age FROM user WHERE user_id = #{userId}
</select>

2.3 高级ResultMap:关联关系映射#

实际开发中,数据通常有关联关系(如用户有一个地址、多个订单)。MyBatis通过resultMapassociation(一对一)和collection(一对多)元素处理这些关系。

2.3.1 一对一(Association)#

场景User有一个Address(用户对应一个地址)。

步骤1:定义POJO

@Data
public class User {
  private int userId;
  private String userName;
  private Address address; // 一对一关联
}
 
@Data
public class Address {
  private int addressId;
  private String street;
  private String city;
}

步骤2:定义resultMap

<!-- Address的resultMap -->
<resultMap id="AddressResultMap" type="Address">
  <id column="address_id" property="addressId"/>
  <result column="street" property="street"/>
  <result column="city" property="city"/>
</resultMap>
 
<!-- User的resultMap,关联Address -->
<resultMap id="UserWithAddressResultMap" type="User" extends="UserResultMap"> <!-- 继承基础UserResultMap -->
  <association property="address" resultMap="AddressResultMap"/>
</resultMap>

步骤3:编写SQL(联合查询)

<select id="getUserWithAddress" resultMap="UserWithAddressResultMap">
  SELECT 
    u.user_id, u.user_name,
    a.address_id, a.street, a.city
  FROM user u
  LEFT JOIN address a ON u.address_id = a.address_id
  WHERE u.user_id = #{userId}
</select>

2.3.2 一对多(Collection)#

场景User有多个Order(用户对应多个订单)。

步骤1:定义POJO

@Data
public class User {
  private int userId;
  private String userName;
  private List<Order> orders; // 一对多关联
}
 
@Data
public class Order {
  private int orderId;
  private String orderNo;
  private Date createTime;
}

步骤2:定义resultMap

<!-- Order的resultMap -->
<resultMap id="OrderResultMap" type="Order">
  <id column="order_id" property="orderId"/>
  <result column="order_no" property="orderNo"/>
  <result column="create_time" property="createTime"/>
</resultMap>
 
<!-- User的resultMap,关联Order集合 -->
<resultMap id="UserWithOrdersResultMap" type="User" extends="UserResultMap">
  <collection property="orders" ofType="Order" resultMap="OrderResultMap"/>
</resultMap>

说明

  • property:POJO中的集合字段名(orders)。
  • ofType:集合中元素的类型(Order)。

步骤3:编写SQL(联合查询)

<select id="getUserWithOrders" resultMap="UserWithOrdersResultMap">
  SELECT 
    u.user_id, u.user_name,
    o.order_id, o.order_no, o.create_time
  FROM user u
  LEFT JOIN `order` o ON u.user_id = o.user_id
  WHERE u.user_id = #{userId}
</select>

2.3.3 鉴别器(Discriminator): polymorphic映射#

场景User有子类AdminUser(管理员)和NormalUser(普通用户),根据user_type字段区分。

步骤1:定义POJO

// 父类
@Data
public class User {
  private int userId;
  private String userName;
  private int userType; // 1=管理员,2=普通用户
}
 
// 子类:管理员
@Data
public class AdminUser extends User {
  private int adminLevel; // 管理员等级
}
 
// 子类:普通用户
@Data
public class NormalUser extends User {
  private int memberLevel; // 会员等级
}

步骤2:定义resultMap

<resultMap id="UserDiscriminatorResultMap" type="User">
  <id column="user_id" property="userId"/>
  <result column="user_name" property="userName"/>
  <result column="user_type" property="userType"/>
  
  <!-- 鉴别器:根据user_type的值映射到不同子类 -->
  <discriminator javaType="int" column="user_type">
    <!-- user_type=1 → AdminUser -->
    <case value="1" resultType="AdminUser">
      <result column="admin_level" property="adminLevel"/>
    </case>
    <!-- user_type=2 → NormalUser -->
    <case value="2" resultType="NormalUser">
      <result column="member_level" property="memberLevel"/>
    </case>
  </discriminator>
</resultMap>

步骤3:编写SQL

<select id="getUserByType" resultMap="UserDiscriminatorResultMap">
  SELECT 
    user_id, user_name, user_type,
    admin_level, member_level
  FROM user WHERE user_id = #{userId}
</select>

3. MyBatis映射最佳实践#

  1. 优先使用@Param传递多参数:比Map更类型安全,可读性更好(如findUsers(@Param("name") String name, @Param("age") int age))。
  2. 复杂映射用resultMapresultMap可重用、支持嵌套关系,比resultType更灵活。
  3. 为POJO配置类型别名:在mybatis-config.xml中配置typeAliases,减少XML中的类名长度:
    <typeAliases>
      <package name="com.example.entity"/> <!-- 扫描包下所有POJO,别名默认为类名(如User) -->
    </typeAliases>
  4. 避免select *:明确指定需要的列,减少数据传输量,避免映射错误。
  5. id元素优化resultMapid是MyBatis识别唯一行的关键,能提升缓存和嵌套查询的性能。
  6. 避免N+1查询:嵌套查询(如associationselect属性)会导致1+N次查询,优先用联合查询resultMap属性)。
    • 反例(N+1):
      <association property="address" select="getAddressById" column="address_id"/>
    • 正例(联合查询):
      <association property="address" resultMap="AddressResultMap"/>
  7. Immutable对象用构造器注入:对不可变对象(如final字段),用resultMapconstructor元素:
    <resultMap id="UserResultMap" type="User">
      <constructor>
        <arg column="user_id" javaType="int"/>
        <arg column="user_name" javaType="string"/>
      </constructor>
    </resultMap>

4. 常见陷阱与避坑指南#

  1. 列名与字段名不匹配:忘记用resultMap或SQL别名,导致POJO字段为null
    解决:用resultMap映射列名与字段名,或在SQL中加AS别名。
  2. N+1查询问题:查询所有用户后,每个用户查询订单(1+N次查询)。
    解决:用联合查询(collectionresultMap属性),或开启延迟加载(需配置lazyLoadingEnabled=true)。
  3. 集合映射忘记ofTypecollection未指定ofType,默认是Object,导致ClassCastException
    解决:明确ofType(如ofType="Order")。
  4. 参数类型错误:传递ListparameterType指定为User,导致绑定错误。
    解决:parameterType无需指定(MyBatis 3.4+自动推断),或正确指定类型。
  5. 延迟加载未配置association/collectionfetchType="lazy"不生效。
    解决:在mybatis-config.xml中开启:
    <settings>
      <setting name="lazyLoadingEnabled" value="true"/>
      <setting name="aggressiveLazyLoading" value="false"/> <!-- 3.4.1+默认false -->
    </settings>

5. 总结#

MyBatis的输入/输出映射是连接Java与SQL的桥梁:

  • 输入映射:支持简单类型、POJO、Map、List/Array、@Param,覆盖了几乎所有参数传递场景。
  • 输出映射resultType处理简单结果,resultMap处理复杂结果(嵌套、集合、 polymorphic)。
  • 最佳实践:优先用@ParamresultMap、类型别名,避免N+1查询,能写出高效、可维护的代码。

掌握这些映射规则,你就能灵活应对MyBatis的各种场景,为后续的动态SQL、缓存等高级功能打下基础。

6. 参考资料#

  1. MyBatis官方文档:
  2. 《MyBatis从入门到精通》(刘增辉):深入讲解MyBatis的核心机制。
  3. MyBatis GitHub:https://github.com/mybatis/mybatis-3(查看最新特性)。