MyBatis 数据封装全攻略(告别空值与映射混乱问题)
作者:lsp程序员010
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_name → userName,create_time → createTime 自动映射。
三、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 数据封装内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
