MyBatis 数据封装全攻略(告别空值与映射混乱问题)

 更新时间:2025年09月28日 11:39:21   作者:lsp程序员010  
本文系统介绍MyBatis数据封装的常见问题及解决方案,涵盖resultType、resultMap、驼峰转换、嵌套处理、懒加载等核心机制,并推荐MyBatis-Plus简化开发,提升效率与可维护性,感兴趣的朋友跟随小编一起看看吧

MyBatis 数据封装全攻略:告别空值与映射混乱

在日常开发中,使用 MyBatis 进行数据库操作时,你是否经常遇到以下问题?

  • 查询结果部分字段为 null,导致前端显示异常?
  • 多表联查时对象嵌套关系映射失败?
  • 字段名和属性名不一致,结果集无法正确赋值?
  • 集合类型(如 List)属性始终为空?
  • 使用 resultType 返回 Map 时结构混乱、不易维护?

这些问题本质上都是 数据封装(Result Mapping) 的问题。本文将系统性地为你梳理 MyBatis 中数据封装的核心机制,并提供最佳实践方案,助你彻底解决封装难题!

一、MyBatis 数据封装的两种方式

1. resultType —— 简单粗暴

适用于字段名与 Java 属性名完全匹配的情况。

<select id="getUserById" resultType="com.example.User">
    SELECT id, username, email FROM user WHERE id = #{id}
</select>

✅ 优点:简洁、易用
❌ 缺点:无法处理驼峰命名、多表关联、嵌套对象等复杂场景

⚠️ 注意:若数据库字段是 user_name,而 Java 属性是 userName,则不会自动映射!

二、开启驼峰命名转换(推荐基础配置)

mybatis-config.xml 或 Spring Boot 配置中开启:

# application.yml (Spring Boot)
mybatis:
  configuration:
    map-underscore-to-camel-case: true

或 XML 配置:

<settings>
    <setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>

开启后,user_nameuserNamecreate_timecreateTime 自动映射。

三、resultMap —— 精准控制映射关系

resultType 无法满足需求时,必须使用 <resultMap>

基础字段映射

<resultMap id="UserResultMap" type="com.example.User">
    <id property="id" column="user_id"/> <!-- 主键建议用 id 标签 -->
    <result property="username" column="user_name"/>
    <result property="email" column="email_addr"/>
    <result property="createTime" column="gmt_create"/>
</resultMap>
<select id="getUserById" resultMap="UserResultMap">
    SELECT user_id, user_name, email_addr, gmt_create 
    FROM user_info 
    WHERE user_id = #{id}
</select>

💡 id 标签用于主键,有助于性能优化(缓存、嵌套查询去重)

四、处理对象嵌套 —— association

当 User 包含一个 Profile 对象:

public class User {
    private Long id;
    private String username;
    private Profile profile; // 嵌套对象
}
public class Profile {
    private String avatar;
    private String bio;
}

XML 映射:

<resultMap id="UserWithProfileResultMap" type="User">
    <id property="id" column="user_id"/>
    <result property="username" column="username"/>
    <!-- 嵌套对象映射 -->
    <association property="profile" javaType="Profile">
        <result property="avatar" column="avatar_url"/>
        <result property="bio" column="user_bio"/>
    </association>
</resultMap>
<select id="getUserWithProfile" resultMap="UserWithProfileResultMap">
    SELECT u.id as user_id, u.username,
           p.avatar as avatar_url, p.bio as user_bio
    FROM user u
    LEFT JOIN profile p ON u.id = p.user_id
    WHERE u.id = #{id}
</select>

五、处理集合嵌套 —— collection

当 User 包含多个 Role:

public class User {
    private Long id;
    private String username;
    private List<Role> roles; // 集合
}
public class Role {
    private Long id;
    private String roleName;
}

XML 映射:

<resultMap id="UserWithRolesResultMap" type="User">
    <id property="id" column="user_id"/>
    <result property="username" column="username"/>
    <!-- 集合映射 -->
    <collection property="roles" ofType="Role">
        <id property="id" column="role_id"/>
        <result property="roleName" column="role_name"/>
    </collection>
</resultMap>
<select id="getUserWithRoles" resultMap="UserWithRolesResultMap">
    SELECT u.id as user_id, u.username,
           r.id as role_id, r.name as role_name
    FROM user u
    LEFT JOIN user_role ur ON u.id = ur.user_id
    LEFT JOIN role r ON ur.role_id = r.id
    WHERE u.id = #{id}
</select>

📌 注意:使用 ofType 指定集合元素类型,而不是 javaType

六、避免 N+1 查询问题

上面的写法虽然能正确封装,但可能引发 N+1 查询。推荐使用 JOIN 查询 + 分组封装分步查询(懒加载)

方案一:分步查询(推荐用于懒加载)

<resultMap id="UserLazyLoadResultMap" type="User">
    <id property="id" column="id"/>
    <result property="username" column="username"/>
    <collection property="roles" 
                select="selectRolesByUserId" 
                column="id" 
                fetchType="lazy"/>
</resultMap>
<select id="selectUserById" resultMap="UserLazyLoadResultMap">
    SELECT id, username FROM user WHERE id = #{id}
</select>
<select id="selectRolesByUserId" resultType="Role">
    SELECT id, name as roleName FROM role 
    WHERE id IN (SELECT role_id FROM user_role WHERE user_id = #{userId})
</select>

在配置中开启懒加载:

mybatis:
  configuration:
    lazy-loading-enabled: true
    aggressive-lazy-loading: false

七、使用注解简化映射(可选)

对于简单场景,也可使用注解:

@Results(id = "UserResult", value = {
    @Result(property = "id", column = "user_id"),
    @Result(property = "username", column = "user_name"),
    @Result(property = "profile", column = "user_id",
            one = @One(select = "selectProfileByUserId"))
})
@Select("SELECT user_id, user_name FROM user WHERE id = #{id}")
User getUserById(Long id);

八、实战技巧 & 避坑指南

✅ 技巧 1:统一别名规范

在 SQL 中使用 AS 给字段起别名,避免列名冲突:

SELECT u.id AS user_id, r.id AS role_id, ...

✅ 技巧 2:复用 ResultMap

使用 <extend> 继承已有的 ResultMap:

<resultMap id="BaseUserMap" type="User">
    <id property="id" column="id"/>
    <result property="username" column="username"/>
</resultMap>
<resultMap id="UserWithProfileMap" extends="BaseUserMap">
    <association property="profile" ... />
</resultMap>

✅ 技巧 3:自动映射策略

<resultMap autoMapping="true" ...>

开启后,未明确指定的匹配字段也会自动映射(需配合驼峰转换)。

❌ 避坑 1:不要混用 resultType 和 resultMap

在一个 select 标签内只能使用其一。

❌ 避坑 2:集合属性未初始化

确保 Java 实体中的 List 属性已初始化:

private List<Role> roles = new ArrayList<>(); // 避免 null

九、终极解决方案:MyBatis-Plus(可选进阶)

如果你觉得原生 MyBatis 配置繁琐,可以考虑 MyBatis-Plus

  • 无需 XML,注解/条件构造器即可完成复杂查询
  • 内置 ResultMap 自动构建
  • 支持连表查询插件(如 @TableField(select = false) + QueryWrapper 联查)
  • 分页、乐观锁、代码生成器一应俱全

示例:

// MP 自动处理字段映射 + 驼峰
List<User> users = userMapper.selectList(
    Wrappers.<User>lambdaQuery()
        .eq(User::getUsername, "admin")
);

总结

问题类型解决方案
字段名不匹配开启驼峰 / 使用 <result>
对象嵌套<association>
集合嵌套<collection>
性能优化分步查询 + 懒加载
复杂联查ResultMap + SQL 别名
快速开发MyBatis-Plus

掌握以上方法,你将能从容应对 MyBatis 中 99% 的数据封装问题!记得收藏本文,下次遇到映射失败时,按图索骥,轻松解决!

到此这篇关于MyBatis 数据封装全攻略(告别空值与映射混乱问题)的文章就介绍到这了,更多相关MyBatis 数据封装内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • java Mail邮件接收工具类

    java Mail邮件接收工具类

    这篇文章主要介绍了java Mail邮件接收工具类,本文直接给出类实现代码和使用示例,需要的朋友可以参考下
    2015-02-02
  • 一文带你了解Java排序算法

    一文带你了解Java排序算法

    这篇文章主要为大家详细介绍了Java中常见的三个排序算法:选择排序,冒泡排序和插入排序,文中的示例代码讲解详细,感兴趣的可以了解一下
    2022-08-08
  • springboot全局异常处理代码实例

    springboot全局异常处理代码实例

    这篇文章主要介绍了springboot全局异常处理代码实例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-01-01
  • SpringBoot事务钩子函数的使用方式

    SpringBoot事务钩子函数的使用方式

    本文介绍了SpringBoot中事务钩子函数的使用方式,包括常见场景、使用方式等,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2024-11-11
  • 浅析Java中的 new 关键字

    浅析Java中的 new 关键字

    java中的new关键字是实例化对象,接下来本文通过一个案例给大家讲解Java中的 new 关键字,感兴趣的朋友可以参考下
    2016-08-08
  • Java Swing实现扫雷小游戏

    Java Swing实现扫雷小游戏

    这篇文章主要为大家详细介绍了Java Swing实现扫雷小游戏,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2018-07-07
  • hadoop 全面解读自定义分区

    hadoop 全面解读自定义分区

    Hadoop是一个由Apache基金会所开发的分布式系统基础架构。用户可以在不了解分布式底层细节的情况下,开发分布式程序。充分利用集群的威力进行高速运算和存储
    2022-02-02
  • 在项目中集成jetty server步骤解析

    在项目中集成jetty server步骤解析

    这篇文章主要介绍了在项目中集成jetty server步骤解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-02-02
  • Java中使用数组实现栈数据结构实例

    Java中使用数组实现栈数据结构实例

    这篇文章主要介绍了Java中使用数组实现栈数据结构实例,本文先是讲解了实现栈至少应该包括以下几个方法等知识,然后给出代码实例,需要的朋友可以参考下
    2015-01-01
  • 详解Spring中的AOP及AspectJ五大通知注解

    详解Spring中的AOP及AspectJ五大通知注解

    这篇文章主要介绍了详解Spring中的AOP及AspectJ五大通知注解,AOP面向切面编程是一种新的方法论,是对传统OOP面向对象编程的补充,AOP 的主要编程对象是切面(aspect),切面模块化横切关注点,需要的朋友可以参考下
    2023-08-08

最新评论