你学了整套 ORM 体系,换来了什么?

ORM 的宣传口径是:你只需要操作 Java 对象,框架自动帮你生成 SQL、管理关系、处理缓存。你不需要写 SQL,不需要关心数据库细节。

这套说辞听起来很美,但代价是什么?

下面这张表,列出了你为了“不用写 SQL”而需要学习的全部内容。

一、为了“把对象映射成表”,你学到的收益

ORM 概念 它的作用
@Entity 告诉框架这个类是实体
@Table 指定对应的数据库表名
@Column 指定字段映射(列名、长度、是否可空)
@Id 标记主键字段
@GeneratedValue 指定主键生成策略(AUTO、IDENTITY、SEQUENCE、TABLE)
@Transient 标记非持久化字段
@Enumerated 枚举类型映射(ORDINAL / STRING)
@Temporal 日期类型映射(DATE / TIME / TIMESTAMP)
@Lob 大字段映射(BLOB / CLOB)
@Version 乐观锁版本号字段
@Embeddable / @Embedded 嵌入对象映射
@EmbeddedId 复合主键
@MappedSuperclass 父类字段继承

二、为了“关联其他表”,你学到的收益

ORM 概念 它的作用
@OneToOne 一对一关联
@OneToMany 一对多关联
@ManyToOne 多对一关联
@ManyToMany 多对多关联
@JoinColumn 指定外键列名
@JoinTable 多对多中间表配置
@OrderBy 关联集合的排序方式
mappedBy 双向关联的对方字段
fetch = LAZY / EAGER 关联加载时机
cascade = ALL / PERSIST / MERGE / REMOVE / REFRESH / DETACH 级联操作类型
orphanRemoval = true / false 孤儿对象删除策略

三、为了“操作数据”,你学到的收益

ORM 概念 它的作用
EntityManager JPA 核心操作接口
EntityManagerFactory EntityManager 工厂
EntityTransaction 事务管理
persist() 保存实体
merge() 合并实体(更新)
remove() 删除实体
find() 按主键查询
getReference() 懒加载引用查询
flush() 立即同步到数据库
detach() 从持久上下文分离
clear() 清空持久上下文
contains() 判断是否托管

四、为了“查询数据”,你学到的收益

HQL / JPQL(一门全新的查询语言)
概念 它的作用
SELECT ... FROM 查询实体(注意:FROM 后面是实体名,不是表名)
JOIN FETCH 关联预加载(为了解决 N+1)
WHERE + 对象属性路径 条件查询(用 u.name 而不是 u.name 的 SQL 字段名)
GROUP BY / HAVING 分组聚合
ORDER BY 排序
NEW 构造器表达式 直接构造 VO 对象
子查询 支持但不完整
聚合函数 支持 COUNT、SUM、AVG、MAX、MIN,但有限制
窗口函数 不支持
递归 CTE 不支持
UNION 不支持
数据库特有函数(DATE_FORMATEXTRACTJSON_EXTRACT 等) 不支持,或需注册方言
Criteria API(一套更“类型安全”的查询构造器)
概念 它的作用
CriteriaBuilder 查询构建器入口
CriteriaQuery<T> 定义查询返回类型
Root<T> 查询根节点
Join<Z, X> 关联查询
Predicate 条件组合(AND / OR / NOT)
Path 属性路径访问
Order 排序条件
Expression 表达式(算术运算、函数调用)
Subquery 子查询构造
Metamodel 静态元模型(需要代码生成器)

你学完这套 Criteria API,写出来的代码长这样:

CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<User> query = cb.createQuery(User.class);
Root<User> user = query.from(User.class);
Join<User, Order> order = user.join("orders");
Predicate predicate = cb.and(
    cb.equal(user.get("name"), "张三"),
    cb.greaterThan(order.get("amount"), 100)
);
query.select(user).where(predicate).orderBy(cb.desc(user.get("createTime")));
List<User> result = entityManager.createQuery(query).getResultList();

五、为了“理解框架行为”,你学到的收益

ORM 概念 它的作用
一级缓存(Persistence Context) 同一个 EntityManager 内的对象缓存,自动管理
二级缓存 跨 EntityManager 的缓存,需额外配置
查询缓存 查询结果缓存,需配合二级缓存使用
脏检查机制 框架自动检测托管对象的变化并生成 UPDATE
延迟加载 访问关联对象时才触发查询,否则是代理对象
关联加载策略 LAZY vs EAGER,选错了就是 N+1
会话管理 什么时候打开 EntityManager,什么时候关闭
托管状态 / 游离状态 / 分离状态 / 删除状态 四种状态的区分,以及状态转换规则
乐观锁实现机制 @Version 注解 + 版本号检查
事务传播行为 需要配合 Spring 事务管理(@Transactional 的各种属性)
方言(Dialect) 不同数据库的 SQL 方言差异,需要选择正确的方言类

六、为了“处理复杂场景”,你学到的收益

ORM 概念 它的作用
@NamedQuery 预定义 JPQL 查询,启动时解析
@NamedNativeQuery 预定义原生 SQL 查询
@SqlResultSetMapping 原生 SQL 结果映射到实体
@ConstructorResult 原生 SQL 结果映射到 VO
@EntityGraph 动态控制关联加载图
@NamedEntityGraph 预定义关联加载图
@Query(Spring Data JPA) 在 Repository 接口上写 JPQL 或原生 SQL
@Modifying 标记 UPDATE / DELETE 操作
@Transactional 事务边界控制(其实跟 JPA 无关,是 Spring 的能力)
PersistenceUnitUtil 工具类,判断实体状态
EntityManager.createNativeQuery() 你终于退回了原生 SQL

这些“收益”的最终结果是什么?

你学完了:

  • 20+ 个注解
  • 10+ 个关联关系配置
  • 一套完整的查询语言(JPQL/HQL)
  • 一套复杂的 Criteria API
  • N+1 问题的识别与解决方案
  • 四种实体状态及转换规则
  • 一级缓存 / 二级缓存 / 查询缓存的区别与用法
  • 方言配置

你以为换来了“不用写 SQL”。

但事实是:

  • 复杂联表查询你写不出 JPQL → 退回到 createNativeQuery()
  • 窗口函数、递归 CTE、UNION → JPA 不支持 → 退回到 createNativeQuery()
  • 报表统计 > 3 张表 JOIN → JPQL 写起来比 SQL 还难懂 → 退回到 createNativeQuery()
  • 需要调优 SQL 时,框架生成的那坨 SQL 你自己都看不懂 → 退回到 createNativeQuery()

你学了一大套 ORM 体系,最终在复杂场景下,它只是你退回原生 SQL 路上的一个减速带。

破:MyBatis 至少还让你写 SQL,JPA 连让你写 SQL 都要绕一圈

MyBatis 的缺点是 XML 太啰嗦,但它至少让你直接写 SQL。你的 SQL 能力是完整的。

JPA 的问题是:你学了一套框架,但它主动阉割了你的 SQL 能力。你本来能写 SQL,但框架告诉你“用 JPQL”,你用了之后发现 JPQL 只有 SQL 1/3 的能力。剩下 2/3 的能力,你只能退回原生 SQL——但这时你发现,你学的那套 JPQL 语法在复杂场景下毫无用处。

你学了一套专有语法,结果它在你最需要的时候用不上。那学它的“收益”在哪?

立:SimpleDAO

SimpleDAO 不做对象关系映射,不管理对象状态,不制造缓存,不生成 SQL。

  • 你要查数据?写 SQL,调用 list()
  • 你要联表?写 SQL,调用 page()
  • 你要复杂报表?写 SQL,调用 list()
  • 你要调优?复制 SQL 到数据库直接跑

你不需要学 JPQL、Criteria API、EntityManager、缓存策略、懒加载配置、四种状态转换、N+1 解决方案、方言配置、EntityGraph、二级缓存配置……

你只需要会 SQL,就会用 SimpleDAO。

因为它的核心能力只有一条:执行你写的 SQL,把结果映射成 Java 对象。

就这么简单。没有任何隐藏的“收益”,也没有任何隐藏的成本。

开源地址

  1. 核心框架:https://gitee.com/gao_zhenzhong/simple-dao
  2. 系统底座:https://gitee.com/gao_zhenzhong/simple-dao-starter
  3. 代码生成器:https://gitee.com/gao_zhenzhong/simple-dao-coder
  4. 实战案例:https://gitee.com/gao_zhenzhong/simple-dao-demo

写在最后

MyBatis 让你学了几十个标签、上百个属性、十几个注解、31 个异常,最终换来的是“在 XML 里拼一个字符串”。

JPA 让你学了 20+ 个注解、10+ 个关联关系、一套 JPQL 语法、一套 Criteria API、四种状态转换、三种缓存策略、一套方言体系……最终换来的是“写了复杂查询时,还得退回到原生 SQL”。

但是,我们也要承认一个事实:单表对象化确实是一个真实的收益。

在单表 CRUD 的场景下,JPA 和 Hibernate 确实能帮你省掉写 INSERT、UPDATE、DELETE 和按主键 SELECT 的 SQL。你不用写 INSERT INTO user (name, age) VALUES (?, ?),直接 user.setName(“张三”) 然后 entityManager.persist(user) 就行。

这个收益是真实的,不是带引号的。

问题是:单表 CRUD 只是企业持久层开发的一小部分——远不到 10%。

剩下的 90%(多表联查、子查询、聚合报表、批量更新、跨表条件筛选……),才是你真正要面对的业务场景。而这些场景下,JPA 帮不了你,你只能退回到原生 SQL。

所以,单表对象化是一个真实的收益,但它覆盖的只是业务中很小的一部分。用一个覆盖 10% 场景的收益,换取 90% 场景下的复杂度和限制,这笔账怎么算,你自己权衡。

两个方向,殊途同归。

SimpleDAO 让你什么都不用多学,你只需要会 SQL,就会用它。

它把“你不用学什么”当作核心收益。

哪一个收益是真的,你自己算账。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐