为什么我不能把 Java Records 用作 JPA 实体
发布: (2025年12月14日 GMT+8 06:40)
3 min read
原文: Dev.to
Source: Dev.to
Introduction
随着 Java Records(正式在 Java 16 中引入)的出现,开发者终于拥有了一种简洁的方式来创建不可变的数据载体类,而无需编写 equals()、hashCode()、toString() 等样板代码。
自然地,很多人看到自己冗长的 JPA 实体后会产生这样的疑问:
“我能用 Record 来替代它们,使代码更简洁吗?”
简短的答案是 不行。
Why Records Are Incompatible with JPA
Immutability vs. Mutability
- Record 的理念 – 所有字段默认是
final;实例创建后其状态不可更改。 - JPA 的要求 – 实体必须是可变的。JPA 通过从数据库加载实体、允许字段被修改,然后再持久化这些更改来管理实体的状态。
依赖可变性的关键 JPA 机制:
- 脏检查 – Hibernate 将对象的当前状态与其原始快照进行比较。
- Setter 方法 – JPA 在从数据库读取时使用 setter(或字段访问)来填充实体字段。
由于 Record 没有 setter 且字段为 final,JPA 无法管理其生命周期或更新其状态。
Lazy Loading and Proxies
JPA 最强大的特性之一是 懒加载,即仅在访问时才获取关联数据。为实现此功能,Hibernate 等提供者会生成 代理 类:
- 继承原实体类。
- 重写 getter 方法,在需要时触发数据库调用。
Record 是 final 类,这意味着它们不能被子类化。因此,JPA 无法为 Record 创建代理,懒加载(以及其他基于代理的功能)会彻底失效。
Alternatives: Keep Entities Concise with Lombok
虽然 Record 不适合作为 JPA 实体,但仍可以使用 Lombok 与普通类相结合来减少样板代码。
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.GeneratedValue;
import lombok.Getter;
import lombok.Setter;
import lombok.NoArgsConstructor;
import lombok.AccessLevel;
@Entity
@Getter
@Setter
@NoArgsConstructor(access = AccessLevel.PROTECTED) // limit access to the default constructor
public class User {
@Id
@GeneratedValue
private Long id;
private String name;
private String email;
}
Note: 以下 Record 定义会导致 JPA 出现问题,因为 Record 是不可变且为 final 的。
// This will cause issues with JPA!
public record User(Long id, String name, String email) {}
Conclusion
- 使用普通类(可选配合 Lombok) 作为 JPA 实体,以满足可变性、默认构造函数以及非 final 类的要求。
- 使用 Record 作为不可变的数据载体(如 DTO),在不需要 JPA 持久化特性的场景下使用。