前面的数据访问实践篇已经把 JDBC、事务、连接池、MyBatis、JPA 放到了一条主线上。这一篇再往前一步,不再只回答“这些东西分别是什么”,而是重点回答一个更接近项目决策的问题:真实业务里,到底该怎么选,怎么定边界,怎么避免数据层越长越乱。
因为很多 Java 项目到了中后期,数据库访问的混乱通常不是某个 SQL 写错了,而是整体抽象失控:
- 有的查询走 MyBatis,有的走 JPA,但没有统一标准;
- Controller 里直接调 Mapper;
- Service 里既写事务又拼 SQL;
- DTO、Entity、VO、DO 混着用;
- 复杂查询想偷懒用 ORM,最后性能出了问题又回头补原生 SQL。
这些问题并不是“团队不够努力”,而是数据访问边界没有被系统设计过。
先别问谁更高级,先问项目到底需要什么控制力
JDBC、MyBatis、JPA 经常被讨论成一种线性进化关系,好像越往后越先进。这个理解很容易把选型带偏。它们真正的区别,不在于“新旧”,而在于抽象层级和控制力分布不同。
JDBC:最底层、最直接的数据库访问接口
它提供的是最基础的连接、SQL 执行、结果集读取能力。JDBC 的价值在于控制足够直接、行为相对透明,但也意味着你要自己承担更多样板代码、资源管理和映射细节。
MyBatis:SQL 可控优先
MyBatis 适合那些对 SQL 可控性要求很高的场景。它的核心优势不只是“能写 XML”或“能写注解 SQL”,而是让团队始终保持对数据库行为的清晰感知。
JPA:模型抽象优先
JPA 更强调围绕实体关系组织数据访问,让大量常规 CRUD 能以更统一的方式被表达出来。它在模型清晰、实体关系稳定、团队倾向模型驱动的项目里,通常能显著提高开发一致性。
所以真正的选型问题不是“站队”,而是你更需要哪种控制力:SQL 控制力,还是模型抽象力。
JDBC、MyBatis、JPA 三者到底适合什么场景?
JDBC 适合做什么?
在现代项目里,纯 JDBC 通常不再是主流业务层首选,但它依然非常重要。因为很多中间框架的底层最终都落回 JDBC,而你只要开始排查以下问题,就绕不开它:
- SQL 执行细节;
- 连接管理问题;
- 批量写入行为;
- 事务提交与回滚;
- 驱动层异常与性能瓶颈。
所以 JDBC 即便不是业务开发主角,也依然是理解数据访问底座的关键。
MyBatis 适合什么场景?
更典型的包括:
- 查询复杂、联表较多;
- 报表和统计类接口多;
- 团队希望显式掌控 SQL;
- 需要精细调优执行计划;
- 数据库能力使用较重。
在这些场景里,MyBatis 的优势是“可控、直观、容易对性能问题建立共识”。代价则是你要更主动地维护 SQL 质量、参数映射、分页策略和结果映射边界。
JPA 适合什么场景?
更典型的包括:
- 典型 CRUD 较多;
- 实体关系稳定;
- 团队愿意围绕领域模型来组织数据层;
- 业务希望通过仓储接口形成统一访问方式;
- 对快速交付常规数据操作有较高需求。
JPA 的问题不在于“不好”,而在于很多团队把它误当成“数据库问题的通用解法”。实际上,一旦查询复杂度高、性能边界明确、数据访问形态高度定制化,JPA 就未必是最顺手的工具。
为什么说选型本质上是“读写模式”的选型?
真实系统的数据访问很少是单一模式。通常你会同时遇到两种需求:
- 写入侧要求边界稳定、事务一致、模型清晰;
- 读取侧要求查询灵活、性能可控、结果结构多变。
很多团队真正成熟起来,就是从这里开始意识到:读和写未必要用完全相同的抽象层。
这也是为什么一些项目会出现这样的组合:
- 写入侧用 JPA 或仓储抽象,保证模型和事务一致;
- 复杂读取侧用 MyBatis,保证 SQL 可控和性能透明。
这种组合不是“混乱”,前提是你真的把边界想清楚。混乱只会出现在毫无规则地混用,而不是有意识地分工。
仓储边界为什么比“框架名字”更重要?
很多项目数据层失控,并不是因为选错了 MyBatis 或 JPA,而是因为根本没有仓储边界。
什么叫仓储边界?
简单说,就是明确谁负责:
- 发起数据库访问;
- 组织查询语句;
- 映射持久化对象;
- 暴露给业务层稳定的数据能力;
- 隔离底层访问细节。
如果这个边界不存在,结果通常就是:
- 控制器直接写 SQL;
- 服务层拼分页、排序和查询条件;
- 数据模型一路泄漏到接口层;
- 后续任何技术替换都会牵一发而动全身。
有边界,不代表层数越多越好
仓储边界的重点不是堆更多层,而是职责明确。一个成熟的数据访问层,不一定有很多类名,但一定能回答清楚:
- 查询细节主要放在哪;
- 业务层拿到的对象是什么;
- 事务边界落在哪;
- 数据库技术差异是否被合理隔离。
DTO、Entity、DO、VO 为什么经常把团队绕晕?
因为一旦没有边界意识,模型就会一路串用。最典型的后果是:
- 数据库实体直接暴露给接口层;
- 查询结果对象和写入对象混在一起;
- 前端展示字段反向影响数据库表结构;
- 小改动会牵扯多层连锁修改。
更稳的思路通常是:
- 持久化层关注数据库映射;
- 业务层关注业务语义;
- 接口层关注对外结构;
- 查询模型和写入模型按需区分。
这不是为了制造更多类,而是为了让变化不至于互相污染。
事务边界和数据访问框架为什么必须一起看?
这是很多选型讨论里被忽略的一点。你选的不是一个“能查库的框架”,而是一整套围绕一致性展开的能力。
现实项目里真正要回答的是:
- 哪几步操作必须一起成功?
- 事务应该包住哪些仓储调用?
- 是否存在长事务风险?
- 查询和写入是否应该拆开?
- 跨服务或跨库一致性如何处理?
这些问题决定了你的数据访问层是清晰还是混乱。框架只是手段,事务边界才是底层逻辑。
放到真实项目里,怎样做选型更稳?
场景一:典型后台管理系统
如果业务以标准 CRUD 为主,实体关系相对稳定,复杂报表不算多,JPA 或者基于 JPA 的仓储体系通常能提高一致性和开发效率。
场景二:复杂查询较多的业务系统
如果系统里大量接口都需要多条件组合、分页排序、联表统计、定制报表,那么 MyBatis 往往更容易让查询行为保持透明和可调优。
场景三:写入和读取差异明显的系统
可以考虑读写分开看待。写入侧保持稳定抽象,读取侧优先保证 SQL 灵活与性能可控,而不是强行“一把框架走天下”。
场景四:性能和排障压力较高的系统
这时比“开发手感”更重要的,是能不能快速看见 SQL、定位慢点、识别连接和事务问题。抽象再漂亮,如果性能问题一出现就无人能解释,那代价会很高。
为什么说“统一规则”比“绝对标准”更重要?
因为现实项目里很少存在一个放之四海而皆准的答案。真正成熟的团队,不一定所有模块都完全一样,但一定会有一套稳定规则,例如:
- 什么场景优先 MyBatis;
- 什么场景允许 JPA;
- 查询对象和写入对象怎样组织;
- Mapper / Repository 能不能被 Controller 直接调用;
- 复杂查询是否必须显式留在数据层。
这套规则一旦稳定,团队协作成本会大幅下降。最怕的不是混合技术,而是没有判断标准。
最常见的数据访问选型误区
1. 把 ORM 当成数据库复杂度的消失器
ORM 只能帮你管理一部分映射工作,不能替你思考索引、查询路径、事务边界和性能问题。
2. 为了“代码整洁”强行把所有查询都抽象成统一形式
最后往往是简单查询被写复杂,复杂查询又被抽象得看不清。
3. 没有仓储边界,导致任何一层都能直接触库
这会让后续重构和问题定位都变得非常痛苦。
4. 只谈框架体验,不谈运行观测
一旦慢查询、锁等待、连接耗尽出现,团队就会发现自己对数据库实际行为几乎没有感知。
5. 还没想清楚读写模式,就急着定统一技术
这样很容易导致某一侧天然不顺手,最后又偷偷长出旁路写法。
这一篇真正想帮你建立什么判断?
当你下次面对数据访问选型时,优先按下面几个问题思考:
1. 当前模块更偏模型驱动,还是更偏 SQL 驱动?2. 读和写的访问模式是否差异明显?3. 团队最需要的是抽象一致性,还是查询控制力?4. 仓储边界是否已经清楚,模型是否会一路泄漏?5. 事务和性能排查是否已经纳入设计,而不是等出事后再补?
只要这几个问题能答清楚,选型就不会再沦为“技术喜好之争”。
总结
Java 数据访问选型的核心,不是给 MyBatis、JPA、JDBC 排座次,而是围绕控制力、抽象层级、事务边界和仓储边界做稳定决策。JDBC 是底座,MyBatis 更偏 SQL 可控,JPA 更偏模型抽象,它们各有合适的位置。真正让数据层长期稳定的,不是某个框架名字,而是你是否把“读写模式、职责分层、事务边界、模型泄漏”这些关键问题真正想清楚了。