Lovi.space

Java 后端

MyBatis 与 JPA:持久层选型不该是一场站队

从设备列表、维修详情和运营报表三种查询形态出发,建立 ORM 与 SQL 控制力的取舍框架。

·1 min read
#Java#MyBatis#JPA#数据库

核心洞察:持久层的关键问题不是“哪个更先进”,而是对象模型、查询复杂度、团队能力与演进成本如何匹配。 霓虹港有三类数据访问:工单写入需要保持聚合一致性;设备详情要沿关联读取;运营看板需要复杂聚合、窗口函数与多维筛选。试图用一种工具解决全部问题,通常会让其中至少一类变得难以维护。

JPA 擅长表达对象生命周期

当业务围绕聚合根展开,JPA 的 Unit of Work、脏检查和关联映射能减少大量样板代码。工单状态变更只需要加载聚合、执行领域方法、在事务结束时提交变化。这种写法让业务语言靠近代码。 但 JPA 不会自动替你做性能决策。惰性加载在详情页可能合理,在列表页却容易演变为 N+1:先查 N 条工单,再为每条工单查一次设备或负责人。解决方式不是把所有关联都设为 EAGER,而是为具体查询使用 fetch join、实体图或投影。

MyBatis 擅长保留 SQL 的形状

报表查询往往关心列、分组、排序和执行计划,而不是对象导航。MyBatis 允许把 SQL 保持为一等公民:CTE、窗口函数、索引提示和数据库方言都能清晰表达。对读模型来说,这种透明度比通用映射更重要。

SELECT device_id, COUNT(*) AS alert_count,
MAX(created_at) AS last_alert_at
FROM alert_event
WHERE created_at >= :startAt
GROUP BY device_id
ORDER BY alert_count DESC
LIMIT :limit

这类结果应映射为查询 DTO,而不是硬塞进一个带生命周期的 Entity。

按查询形态做决定

场景 推荐重点 原因
聚合内状态变更 JPA / ORM 领域行为与事务一致性
简单详情与关联 JPA 投影或 fetch join 降低重复映射
复杂报表与批处理 MyBatis SQL 可控、易看执行计划
极高频热点读取 专用读模型 减少 ORM 开销与对象装配
混用并不等于混乱。关键是规则明确:写模型围绕聚合,读模型围绕查询;不要让同一张表在不同层随意被 Mapper 和 Repository 同时修改。 ## 事务与分页的两个陷阱 第一,事务边界应由用例定义,而不是由 Mapper 方法数量决定。第二,深分页的 `OFFSET` 会随着页码变大而变慢;面向时间线的列表更适合基于稳定排序键的游标分页。 批量写入也需要注意:JPA 的持久化上下文会累积实体,长循环中应分批 `flush` 与 `clear`;MyBatis 批处理则应控制提交批次和失败定位粒度。 ## 结语 JPA 与 MyBatis 的关系不是替代,而是分工。先看业务是在维护对象不变量,还是在回答数据问题;前者让模型主导,后者让 SQL 主导。选型因此成为可解释的工程决策,而不是框架偏好。

读到这里,说明你也在认真对待这个问题。

讨论与反应

⌘ K