
你是否曾在编写 MyBatis Mapper 接口时,为返回值类型犹豫不决?是返回实体、DTO、Map,还是一个简单的 int?这看似是个人编码习惯问题,实则深刻影响着上层业务逻辑(Service 层)的书写体验和整个工程的长期可维护性。一个恰当的返回值,能让业务逻辑清晰明了;而一个糟糕的选择,则会让代码充满“猜测”和“硬编码”。
本文将深入探讨 Mapper 层返回值的最佳实践,帮助你做出明智的选择,从而写出更优雅、更健壮的 Java 业务代码。
一、查询方法:优先选择“有结构”的返回值
查询操作的返回值是后续逻辑处理的起点,其类型设计直接决定了数据的可操作性和类型安全性。
1. 返回实体或 List<Entity>(默认选择)
User selectById(Long id);
List<User> selectList(UserQuery query);
适用场景:
- 单表查询。
- 返回业务核心实体,且需要在 Service 层继续进行处理、转换或作为其他操作的入参。
- 数据后续可能被修改并用于更新操作。
这是最安全、最不容易出问题的写法。它保证了完整的类型信息,IDE 可以提供代码补全和编译时检查,极大地减少了运行时错误。当你没有特殊理由时,这应该是你的第一选择。
2. 返回 DTO / VO(仅在特定场景使用)
List<UserVO> selectUserList(UserQuery query);
适合场景:
- 复杂的多表关联查询(JOIN),结果集无法直接映射到单个实体。
- 查询结果明确且仅用于前端展示,不需要(也不应该)参与后续的业务计算或写操作。
- 需要聚合多个实体的字段,或者对字段进行额外加工(如格式化日期、计算衍生字段)。
核心原则:Mapper 返回的 DTO/VO 应是只读的。它的使命在 Service 层组装完最终响应体后就结束了,不应再被传递回 Mapper 用于更新数据。
3. 返回 Map / List<Map>(尽量避免)
Map<String, Object> selectDetail(Long id);
Map 并非绝对不可用,但其带来的问题远多于便利:
- 依赖约定:键(Key)的名称是字符串硬编码,容易拼写错误,且无法通过编译器发现。
- 类型不安全:值(Value)是
Object 类型,在 Service 层使用时需要进行大量的强制类型转换,代码冗长且易出现 ClassCastException。
- 可读性差:无法直观了解返回的数据结构,增加了后续开发者的理解成本。
因此,除非是处理极其动态、结构不定的元数据,否则能不用 Map,就尽量不要用 Map。
二、写操作:返回值必须具备“判断价值”
对于 insert、update、delete 操作,返回值的关键作用在于向调用方清晰地传递操作执行的结果信息。
1. insert / update / delete 返回 int(强烈推荐)
int updateById(User user);
返回 int 类型,表示数据库受该操作影响的行数,这是最具工程语义的返回值。
> 0:表示操作成功,并影响了相应的行数(例如,更新了1条数据)。
= 0:表示操作未命中任何数据(例如,根据一个不存在的ID进行更新)。
这使得 Service 层可以基于明确的事实进行判断和决策:
if (rows == 0) {
throw new BizException("更新失败,记录可能不存在");
}
2. 返回 boolean?不推荐
boolean update(User user);
返回 boolean (true/false) 看似简洁,实则削弱了 Service 层的判断能力:
- 信息丢失:无法区分“成功更新0行”(未找到数据)和“成功更新1行”。
- 语义模糊:
false 可能代表执行失败(异常),也可能仅代表没有数据被更新,这迫使开发者需要查阅文档或源码才能确定其含义。
3. insert 返回对象?通常没必要
User insert(User user);
现代持久层框架(如 MyBatis)普遍支持主键回填功能。你传入的对象在插入后,其 ID 等自增字段会被自动填充。因此,在绝大多数场景下,不需要通过返回一个新对象来获取生成的主键。

三、一套可直接套用的最佳实践规则
为了方便记忆和应用,这里总结一套可以直接作为团队规范使用的规则:
- 单表/核心查询:优先返回 实体(Entity) 或
List<Entity>。
- 复杂查询与展示:返回专用的 DTO 或 VO,并保持其只读性。
- 所有写操作:统一返回
int(受影响行数),提供最清晰的执行反馈。
Map:非必要,不使用。牺牲一点便利性,换取长久的可维护性。
- 统一响应体:Mapper 层不返回
Result<T>、Response<T> 等统一包装类。这类包装应用于最外层的控制器(Controller)或网关。
一句话总结精髓:Mapper 返回值的核心设计目标,是让 Service 层的代码“不用猜”,能够基于类型明确、语义清晰的结果进行流畅的业务逻辑编排。
遵循以上原则,你的数据持久层将与业务逻辑层形成清晰、高效的协作,大幅提升代码质量。更多关于系统架构和设计模式的深入讨论,欢迎访问 云栈社区 的 数据库与中间件 板块进行交流学习。
