Spring+SpringMVC+MyBatis深入学习及搭建(四)——MyBatis输入映射与输出映射
在MyBatis的核心工作流程中,输入映射(Parameter Mapping)和输出映射(Result Mapping)是连接Java代码与SQL语句的关键环节:
- 输入映射:将Java方法的参数转换为SQL语句中的占位符(如
#{param})值,解决"如何把Java参数传递给SQL"的问题。 - 输出映射:将SQL查询的结果集转换为Java对象(或集合),解决"如何把数据库结果转换为Java对象"的问题。
理解并掌握这两种映射机制,是写出高效、可维护MyBatis代码的基础。本文将深入讲解MyBatis的输入/输出映射类型、使用场景、最佳实践,并通过大量示例帮助你快速上手。
目录#
- MyBatis输入映射:参数传递的艺术
- 1.1 简单类型参数
- 1.2 POJO参数
- 1.3 Map参数
- 1.4 List/Array参数
- 1.5 @Param注解传递多参数
- MyBatis输出映射:结果转换的魔法
- 2.1 resultType:简单结果映射
- 2.2 resultMap:复杂结果映射的核心
- 2.3 高级ResultMap:关联关系映射
- 2.3.1 一对一(Association)
- 2.3.2 一对多(Collection)
- 2.3.3 鉴别器(Discriminator): polymorphic映射
- MyBatis映射最佳实践
- 常见陷阱与避坑指南
- 总结
- 参考资料
1. MyBatis输入映射:参数传递的艺术#
输入映射的核心是将Java方法的参数绑定到SQL语句的占位符。MyBatis支持多种参数类型,适用于不同场景。
1.1 简单类型参数#
适用场景:方法只有1个参数(如int、String、long等)。
规则: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_name、age列。
POJO字段:userId、userName、age(注意驼峰命名)。
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_id、user_name、age。
POJO字段:userId、userName、age。
定义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通过resultMap的association(一对一)和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映射最佳实践#
- 优先使用
@Param传递多参数:比Map更类型安全,可读性更好(如findUsers(@Param("name") String name, @Param("age") int age))。 - 复杂映射用
resultMap:resultMap可重用、支持嵌套关系,比resultType更灵活。 - 为POJO配置类型别名:在
mybatis-config.xml中配置typeAliases,减少XML中的类名长度:<typeAliases> <package name="com.example.entity"/> <!-- 扫描包下所有POJO,别名默认为类名(如User) --> </typeAliases> - 避免
select *:明确指定需要的列,减少数据传输量,避免映射错误。 - 用
id元素优化resultMap:id是MyBatis识别唯一行的关键,能提升缓存和嵌套查询的性能。 - 避免N+1查询:嵌套查询(如
association的select属性)会导致1+N次查询,优先用联合查询(resultMap属性)。- 反例(N+1):
<association property="address" select="getAddressById" column="address_id"/> - 正例(联合查询):
<association property="address" resultMap="AddressResultMap"/>
- 反例(N+1):
- Immutable对象用构造器注入:对不可变对象(如
final字段),用resultMap的constructor元素:<resultMap id="UserResultMap" type="User"> <constructor> <arg column="user_id" javaType="int"/> <arg column="user_name" javaType="string"/> </constructor> </resultMap>
4. 常见陷阱与避坑指南#
- 列名与字段名不匹配:忘记用
resultMap或SQL别名,导致POJO字段为null。
解决:用resultMap映射列名与字段名,或在SQL中加AS别名。 - N+1查询问题:查询所有用户后,每个用户查询订单(1+N次查询)。
解决:用联合查询(collection的resultMap属性),或开启延迟加载(需配置lazyLoadingEnabled=true)。 - 集合映射忘记
ofType:collection未指定ofType,默认是Object,导致ClassCastException。
解决:明确ofType(如ofType="Order")。 - 参数类型错误:传递
List但parameterType指定为User,导致绑定错误。
解决:parameterType无需指定(MyBatis 3.4+自动推断),或正确指定类型。 - 延迟加载未配置:
association/collection的fetchType="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)。 - 最佳实践:优先用
@Param、resultMap、类型别名,避免N+1查询,能写出高效、可维护的代码。
掌握这些映射规则,你就能灵活应对MyBatis的各种场景,为后续的动态SQL、缓存等高级功能打下基础。
6. 参考资料#
- MyBatis官方文档:
- 《MyBatis从入门到精通》(刘增辉):深入讲解MyBatis的核心机制。
- MyBatis GitHub:https://github.com/mybatis/mybatis-3(查看最新特性)。