为什么 JPA Entity 类需要有默认的无参构造函数?

1、概览

在本教程中,我们将学习在 JPA 中,为什么实体类必须要有默认无参数构造函数?

为了理解无参数构造函数的意义,我们将使用一个简单的 Employee 实体示例。我们将观察缺少默认构造函数是如何导致编译时错误的。我们将深入探讨 JPA 在实体实例化中对 Reflection 的使用。此外,我们还将简要介绍这些类中可能需要该构造函数的其他原因。

2、示例

让我们举一个简单的例子,建立一个名为 Employee 的实体类,其中包含 namedepartment 和自动生成的 id。让我们定义一个包含所有三个字段的构造函数:

@Entity
public class Employee {

    @Id
    private Long id;
    private String name;
    private int age;

    public Employee(Long id, String name, int age) {
        this.id = id;
        this.name = name;
        this.age = age;
    }

    // get 和 set
}

不过,此时我们会发现 Employee 类无法编译:Class'Employee' should have [public, protected] no-arg constructor

一个 JPA 实体,其构造函数有参数

显然,在这里我们可以看到,我们定义了一个带有全参构造函数的 Entity 类,但却没有无参数构造函数。在这种情况下,会出现一个编译时错误,提示我们除了现有的构造函数外,还需要一个无参数构造函数。

在接下来的两节中,我们将围绕 Entity 类中的构造函数了解 JPA 规范。我们将了解如何修复错误,以及 JPA 施加这些约束的根本原因。

3、JPA 规范、构造函数和反射

JPA 规范要求所有实体类都有一个默认的无参数构造函数。该构造函数可以是 public 的,也可以是 protected 的。

如果没有定义其他构造函数,编译时将自动提供一个默认的无参数构造函数。但是,如果我们定义了一个参数化的构造函数,就必须显式地提供一个默认构造函数。

JPA 使用该默认构造函数来使用 Reflection (反射) 创建实体类的实例。它提供了一种动态创建类实例、调用方法和访问字段的方法。

要使用 Reflection 创建类的实例,我们可以使用 Class 类及其 newInstance() 方法。当 JPA 创建一个持久化的实体类的实例时,它首先使用实体的完全限定类名获取 Class 对象。一旦获得 Class 对象,JPA 就会使用 Reflection 通过调用无参数构造函数来创建该类的新实例。因此,在 JPA 实体类中提供无参数构造函数始终是一种好的做法,即使我们没有显式地使用它。

简单地说,为了解决这个问题,我们要在 Employee 实体类中明确定义一个无参数构造函数:

@Entity
public class Employee implements Serializable {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    private String name;
    private String dept;
    private int salary;

    public Employee(String name, String dept, int salary) {
        this.name = name;
        this.dept = dept;
        this.salary = salary;
    }

    public Employee() {
        
    }
}

现在,编译时错误已得到修复:

无参数构造函数示例

显然,通过引入无参数构造函数,之前的编译器错误已经得到解决。

4、使用无参构造函数的原因

首先,正如我们在上一节中简要讨论过的,JPA 实现(如 Hibernate)使用 Reflection 来创建实体类的实例。这允许在运行时进行动态类检查和实例化。要创建实体对象,JPA 提供者需要调用一个不带任何参数的构造函数。如果没有无参数构造函数,在对象实例化过程中就会出现异常。

此外,JPA Provider 经常使用代理对象来优化性能和懒加载关系。代理对象是动态生成的实体类子类。这些子类需要一个无参数构造函数来创建实例并实现其目的。没有这个构造函数,代理机制就会失效,导致运行时出错。

最后,我们知道 JPA 提供实体和数据库表之间的双向映射。在将实体对象映射到其对应的数据库记录的过程中,JPA Provider 必须创建实体类的实例。如果没有无参数构造函数,Provider 就无法实例化实体对象,从而导致映射失败和数据检索问题。

5、总结

在本文中,我们讨论了在实体类中使用默认无参数构造函数的必要性。

我们了解到,它使 JPA Provider 能够实例化对象、利用代理机制以及在实体和数据库表之间执行无缝映射。

缺少无参数构造函数会导致编译时错误,从而阻碍持久化操作的成功执行。通过了解无参数构造函数的必要性以及反射在处理实体实例化时的作用,我们可以确保基于 JPA 的应用程序顺利运行。


参考:https://www.baeldung.com/jpa-no-argument-constructor-entity-class