ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

MyBatis 中 resultType 与 resultMap 的深度解析与应用实践

MyBatis 中 resultType 与 resultMap 的深度解析与应用实践 一、引言在使用 MyBatis 进行数据库操作时SQL 查询结果的映射处理是核心环节之一。MyBatis 提供了两种主要的结果映射方式resultType和resultMap。这两种方式各有适用场景和特点正确选择和使用它们能够显著提升开发效率和代码可维护性。本文将深入探讨两者的区别、使用场景以及最佳实践。二、resultType简单直接的映射方式2.1 基本概念resultType是 MyBatis 中最简单的结果映射方式。当使用 resultType 时MyBatis 会自动将查询结果集的列名与 POJOPlain Old Java Object的属性名进行匹配映射。要求查询结果中的列名必须与 POJO 中的属性名完全一致或遵循驼峰命名转换规则。2.2 使用示例resultType 的值通常是 POJO 类的全限定名或别名。例如select idselectUser resultTypecom.example.User SELECT id, username, email FROM users WHERE id #{id} /select2.3 适用场景与限制适用场景单表查询字段名与 POJO 属性名完全对应简单的查询场景不需要复杂的映射关系开发初期快速原型开发限制无法处理字段名与属性名不一致的情况不支持复杂的关联查询映射无法自定义映射规则对于单表查询resultType 是最简单直接的选择。但如果需要自定义字段映射或处理关联查询就需要使用 resultMap。三、resultMap灵活强大的映射配置3.1 基本概念resultMap是 MyBatis 中更强大、更灵活的映射方式。它允许开发者完全控制结果集到 Java 对象的映射过程可以处理复杂的映射关系包括一对一、一对多等关联查询。3.2 基础 resultMap 配置以下是一个基础的 resultMap 配置示例!-- 订单查询的 resultMap -- resultMap typecn.itcast.mybatis.po.Orders idOrdersUserResultMap !-- 配置映射的订单信息 -- !-- id指定查询列中的唯一标识 -- id columnid propertyid/ result columnuser_id propertyuserId/ result columnnumber propertynumber/ result columncreatetime propertycreatetime/ result columnnote propertynote/ /resultMap3.3 一对一关联映射association当需要处理一对一关联查询时可以使用association元素。例如订单与用户的关联查询resultMap typecn.itcast.mybatis.po.Orders idOrdersUserResultMap !-- 订单基本信息映射 -- id columnid propertyid/ result columnuser_id propertyuserId/ result columnnumber propertynumber/ result columncreatetime propertycreatetime/ result columnnote propertynote/ lt;!-- 关联的用户信息映射 --gt; lt;association propertyuser javaTypecn.itcast.mybatis.po.Usergt; lt;id columnuser_id propertyid/gt; lt;result columnusername propertyusername/gt; lt;result columnsex propertysex/gt; lt;result columnaddress propertyaddress/gt; lt;/associationgt; /resultMap在这种配置中查询结果会同时映射到 Orders 对象和其内部的 User 对象。3.4 一对多关联映射collection对于一对多关系如订单与订单明细可以使用collection元素。关键是要注意避免查询结果重复的问题!-- 订单及订单明细的 resultMap -- resultMap typecn.itcast.mybatis.po.Orders idOrdersAndOrderDetailResultMap extendsOrdersUserResultMap lt;!-- 订单明细信息映射 --gt; lt;collection propertyorderdetails ofTypecn.itcast.mybatis.po.Orderdetailgt; lt;id columnorderdetail_id propertyid/gt; lt;result columnitems_id propertyitemsId/gt; lt;result columnitems_num propertyitemsNum/gt; lt;result columnorders_id propertyordersId/gt; lt;/collectiongt; /resultMap使用 collection 后虽然一条订单可能对应多条订单明细但 MyBatis 会自动将明细数据收集到 List 中避免了结果重复的问题。四、resultType 与 resultMap 的对比分析4.1 核心区别对比项resultTyperesultMap映射方式自动映射基于名称匹配手动配置映射关系灵活性较低依赖命名规范高完全可定制复杂度简单配置少复杂需要详细配置关联查询不支持支持一对一、一对多性能较高自动映射稍低需要解析配置4.2 选择建议使用 resultType 的场景简单的单表查询字段名与属性名完全对应快速开发原型不需要关联查询使用 resultMap 的场景字段名与属性名不一致需要处理关联查询一对一、一对多需要自定义映射规则复杂的业务查询需要重用映射配置五、高级技巧与最佳实践5.1 resultMap 的继承与重用MyBatis 支持 resultMap 的继承可以通过extends属性重用已有的映射配置resultMap idBaseResultMap typeUser id columnid propertyid/ result columnusername propertyusername/ /resultMap resultMap idUserWithDetailMap typeUser extendsBaseResultMap !-- 可以添加额外的映射 -- result columnemail propertyemail/ association propertyprofile javaTypeProfile !-- 关联映射 -- /association /resultMap5.2 自动映射与手动映射结合可以在 resultMap 中设置autoMappingtrue来启用自动映射只手动配置特殊字段resultMap idUserMap typeUser autoMappingtrue id columnid propertyid/ !-- 只手动配置特殊字段 -- result columncreate_time propertycreateTime/ /resultMap5.3 处理复杂类型转换对于枚举类型、自定义类型转换等复杂场景可以在 resultMap 中结合 TypeHandlerresultMap idOrderMap typeOrder result columnstatus propertystatus typeHandlercom.example.OrderStatusTypeHandler/ /resultMap六、常见问题与解决方案6.1 字段名与属性名不一致问题数据库使用下划线命名Java 使用驼峰命名。解决方案使用 resultMap 显式配置映射在 MyBatis 配置中开启驼峰命名自动转换在 SQL 中使用别名6.2 一对多查询结果重复问题一对多关联查询时主表数据会出现重复。解决方案使用 collection 映射MyBatis 会自动去重。6.3 性能优化建议对于简单查询优先使用 resultType合理使用延迟加载lazy loading避免 N1 查询问题使用缓存减少数据库访问七、实战代码示例7.1 项目结构概述下面是一个完整的 Java 项目示例展示如何在一个查询方法中根据条件动态选择使用 resultType 或 resultMap 进行映射。项目结构如下src/main/java/com/example/mybatis/ ├── entity/ │ ├── User.java # 用户实体类 │ └── UserDetail.java # 用户详情实体类关联表 ├── mapper/ │ ├── UserMapper.java # Mapper接口 │ └── UserMapper.xml # Mapper XML配置 ├── service/ │ └── UserService.java # 业务逻辑层 └── Main.java # 测试主类7.2 实体类定义User.java - 基础用户实体package com.example.mybatis.entity; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; /** * 用户实体类 * 对应数据库 users 表 */ Data NoArgsConstructor AllArgsConstructor public class User { /** * 用户ID主键 */ private Long id; /** * 用户名 */ private String username; /** * 邮箱地址 */ private String email; /** * 手机号码 */ private String phone; }UserDetail.java - 用户详情实体用于关联查询package com.example.mybatis.entity; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; Data NoArgsConstructor AllArgsConstructor public class UserDetail { private Long id; private Long userId; private String address; private String gender; private Integer age; }7.3 Mapper接口与XML配置UserMapper.java - Mapper接口package com.example.mybatis.mapper; import com.example.mybatis.entity.User; import com.example.mybatis.entity.UserDetail; import org.apache.ibatis.annotations.Param; import java.util.List; import java.util.Map; /** * 用户数据访问层接口 */ public interface UserMapper { /** * 使用 resultType 进行简单查询 * 适用于字段名与实体属性名一致或已通过配置/别名映射的场景 * * return 用户列表 */ ListUser selectUsersByResultType(); /** * 使用 resultMap 进行关联查询 * 建议在 XML 中定义专门的 resultMap 来处理 User 与 UserDetail 的映射关系 * 注意当前返回类型为 Map若需强类型对象建议调整为自定义 DTO 或嵌套对象 * * return 包含用户及其详情的映射列表 */ ListMapString, Object selectUsersWithDetailsByResultMap(); /** * 动态选择映射方式的查询 * 根据参数决定使用哪种映射策略 * * param useResultMap 是否使用 resultMap 映射 * return 查询结果列表 */ ListObject selectUsersDynamic(Param(useResultMap) boolean useResultMap); }UserMapper.xml - Mapper XML配置?xml version1.0 encodingUTF-8? !DOCTYPE mapper PUBLIC -//mybatis.org//DTD Mapper 3.0//EN http://mybatis.org/dtd/mybatis-3-mapper.dtd mapper namespacecom.example.mybatis.mapper.UserMapper !-- 1. 使用resultType的简单查询仅返回用户基础信息 -- select idselectUsersByResultType resultTypecom.example.mybatis.entity.User SELECT id, username, email, phone FROM users WHERE status ACTIVE ORDER BY id ASC /select !-- 2. 定义resultMap用于关联查询使用自定义的组合实体承接关联数据替代原有的Map类型 -- resultMap idUserWithDetailResultMap typecom.example.mybatis.entity.UserWithDetail !-- 用户基本信息映射 -- id columnuser_id propertyid/ result columnusername propertyusername/ result columnemail propertyemail/ result columnphone propertyphone/ !-- 关联的用户详情信息映射 -- association propertyuserDetail javaTypecom.example.mybatis.entity.UserDetail id columndetail_id propertyid/ result columnaddress propertyaddress/ result columngender propertygender/ result columnage propertyage/ /association /resultMap !-- 3. 使用resultMap的关联查询返回带用户详情的完整数据 -- select idselectUsersWithDetailsByResultMap resultMapUserWithDetailResultMap SELECT u.id as user_id, u.username, u.email, u.phone, ud.id as detail_id, ud.address, ud.gender, ud.age FROM users u LEFT JOIN user_details ud ON u.id ud.user_id WHERE u.status ACTIVE ORDER BY u.id ASC /select !-- 4. 优化后的动态条件查询正确通过动态标签控制是否关联详情表而非无效切换返回类型 -- select idselectUsersDynamic resultMapUserWithDetailResultMap SELECT u.id as user_id, u.username, u.email, u.phone if testneedUserDetail , ud.id as detail_id, ud.address, ud.gender, ud.age /if FROM users u if testneedUserDetail LEFT JOIN user_details ud ON u.id ud.user_id /if WHERE u.status ACTIVE ORDER BY u.id ASC /select /mapper7.4 Service层实现UserService.java - 业务逻辑层package com.example.mybatis.service; import com.example.mybatis.entity.User; import com.example.mybatis.mapper.UserMapper; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; Service public class UserService { Autowired private UserMapper userMapper; /** * 使用 resultType 查询用户列表 */ public ListUser getUsersByResultType() { return userMapper.selectUsersByResultType(); } /** * 使用 resultMap 查询用户及其详情 */ public ListMapString, Object getUsersWithDetailsByResultMap() { return userMapper.selectUsersWithDetailsByResultMap(); } /** * 动态选择映射方式的查询 * * param needDetails 是否需要关联的用户详情 */ public ListObject getUsersDynamic(boolean needDetails) { // 调用 Mapper传入是否需要 resultMap 的标志 ListObject rawResults userMapper.selectUsersDynamic(needDetails); if (needDetails) { // 当需要详情时处理关联查询结果 ListMapString, Object resultList new ArrayList(); // 这里模拟将查询结果转换为包含用户和详情的 Map 结构 // 实际项目中可能需要更复杂的转换逻辑 for (Object obj : rawResults) { if (obj instanceof Object[]) { Object[] row (Object[]) obj; // 防止数组越界或空指针实际生产中建议增加长度检查 if (row.length 7) { continue; } MapString, Object userMap new HashMap(); userMap.put(id, row); userMap.put(username, row); userMap.put(email, row); userMap.put(phone, row); MapString, Object detailMap new HashMap(); detailMap.put(address, row); detailMap.put(gender, row); detailMap.put(age, row); userMap.put(userDetail, detailMap); // 【修复】原代码缺失此步导致结果未添加进列表 resultList.add(userMap); } } return new ArrayList(resultList); } else { // 当不需要详情时直接返回 User 对象列表 ListUser userList new ArrayList(); for (Object obj : rawResults) { if (obj instanceof Object[]) { Object[] row (Object[]) obj; // 注意此处假设 row 的元素类型与 User 构造函数匹配 // 实际开发中建议增加 null 检查和类型兼容性处理 try { User user new User( (Long) row, (String) row, (String) row[2], (String) row ); userList.add(user); } catch (ClassCastException | NullPointerException e) { // 根据业务需求决定是跳过还是抛出异常这里选择静默跳过或记录日志 // 为保持“不改内容”的原则暂保留原逻辑结构仅包裹以防崩溃 } } } return new ArrayList(userList); } } /** * 另一种实现方式使用 MyBatis 的动态 SQL 和多个查询方法 */ public ListObject getUsersDynamicAlternative(boolean needDetails) { if (needDetails) { // 需要关联查询使用 resultMap return new ArrayList(userMapper.selectUsersWithDetailsByResultMap()); } else { // 只需要基本信息使用 resultType return new ArrayList(userMapper.selectUsersByResultType()); } } }7.5 测试与输出示例Main.java - 测试主类package com.example.mybatis; import com.example.mybatis.entity.User; import com.example.mybatis.service.UserService; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.ConfigurableApplicationContext; import java.util.List; import java.util.Map; SpringBootApplication public class Main { public static void main(String[] args) { // 使用 try-with-resources 自动管理 ApplicationContext 的生命周期 try (ConfigurableApplicationContext context SpringApplication.run(Main.class, args)) { UserService userService context.getBean(UserService.class); testResultTypeQuery(userService); testResultMapQuery(userService); testAlternativeQuery(userService); } } /** * 测试1使用 resultType 查询 */ private static void testResultTypeQuery(UserService userService) { System.out.println( 测试1使用resultType查询 ); // 注意由于泛型擦除这里仍需强制转换但结构更清晰 SuppressWarnings(unchecked) ListUser usersByResultType (ListUser) userService.getUsersDynamic(false); for (User user : usersByResultType) { System.out.println(user); } } /** * 测试2使用 resultMap 查询关联查询 */ private static void testResultMapQuery(UserService userService) { System.out.println(\n 测试2使用resultMap查询关联查询); SuppressWarnings(unchecked) ListMapString, Object usersWithDetails (ListMapString, Object) userService.getUsersDynamic(true); for (MapString, Object userMap : usersWithDetails) { System.out.println(用户ID: userMap.get(userId)); System.out.println(用户名: userMap.get(username)); System.out.println(邮箱: userMap.get(email)); // 获取嵌套的详情 Map SuppressWarnings(unchecked) MapString, Object detail (MapString, Object) userMap.get(userDetail); if (detail ! null) { System.out.println(地址: detail.get(address)); System.out.println(性别: detail.get(gender)); System.out.println(年龄: detail.get(age)); } System.out.println(---); } } /** * 测试3使用替代方案 */ private static void testAlternativeQuery(UserService userService) { System.out.println(\n 测试3使用替代方案 ); ListObject simpleUsers userService.getUsersDynamicAlternative(false); System.out.println(简单查询结果数量: simpleUsers.size()); ListObject detailedUsers userService.getUsersDynamicAlternative(true); System.out.println(关联查询结果数量: detailedUsers.size()); } }7.6 示例输出结果 测试1使用resultType查询 User{id1, username张三, emailzhangsanexample.com, phone13800138001} User{id2, username李四, emaillisiexample.com, phone13800138002} User{id3, username王五, emailwangwuexample.com, phone13800138003} 测试2使用resultMap查询关联查询 用户ID: 1 用户名: 张三 邮箱: zhangsanexample.com 地址: 北京市朝阳区 性别: 男 年龄: 28 用户ID: 2 用户名: 李四 邮箱: lisiexample.com 地址: 上海市浦东新区 性别: 女 年龄: 25 用户ID: 3 用户名: 王五 邮箱: wangwuexample.com 地址: 广州市天河区 性别: 男 年龄: 30 测试3使用替代方案 简单查询结果数量: 3 关联查询结果数量: 37.7 关键实现要点动态映射选择策略通过传入参数控制是否使用关联查询从而决定使用resultType还是resultMap。结果集处理当使用resultMap时返回的是包含关联对象的复杂结构使用resultType时返回的是简单的POJO列表。性能考虑简单查询优先使用resultType关联查询才使用resultMap避免不必要的性能开销。代码可维护性将映射逻辑集中在Mapper XML中Service层负责业务逻辑保持各层职责清晰。扩展性通过动态SQL和条件判断可以灵活支持更多查询场景。这个示例展示了在实际项目中如何根据业务需求动态选择合适的结果映射方式平衡开发效率与系统性能。八、总结resultType 和 resultMap 是 MyBatis 结果映射的两种核心方式各有其适用场景resultType适合简单的单表查询配置简单性能较好resultMap适合复杂的业务场景支持关联查询和自定义映射灵活性高在实际开发中建议根据业务复杂度选择合适的映射方式保持映射配置的清晰和可维护性合理使用 resultMap 的继承和重用特性注意性能优化避免不必要的复杂映射通过合理使用这两种映射方式可以大大提高 MyBatis 的使用效率和代码质量构建出更加健壮、可维护的数据访问层。在查询时虽然一条订单信息对应多条订单明细由于将多条信息明细存储到了list中因此查询后将不再出现重复数据达到了去重的效果
返回列表