为什么我不能把 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 等提供者会生成 代理 类:

  1. 继承原实体类。
  2. 重写 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 持久化特性的场景下使用。
Back to Blog

相关文章

阅读更多 »

Java 中的方法重写

什么是 method overriding?当子类为已经在 parent class 中定义的 method 提供特定实现时,这称为 method overriding。