为啥 Hibernate 不需要参数构造函数?

Posted

技术标签:

【中文标题】为啥 Hibernate 不需要参数构造函数?【英文标题】:Why does Hibernate require no argument constructor?为什么 Hibernate 不需要参数构造函数? 【发布时间】:2022-01-20 17:45:10 【问题描述】:

无参数构造函数是 要求(像 Hibernate 这样的工具使用 对此构造函数的反思 实例化对象)。

我得到了这个挥手的答案,但有人可以进一步解释吗?谢谢

【问题讨论】:

仅供参考:The no-argument constructor is a requirement 错误的断言,以及所有继续解释为什么会这样的答案,而无需质疑这是否是事实上,(包括已接受的答案,甚至获得了赏金)是错误的。看到这个答案:***.com/a/29433238/773113 如果你使用 hibernate 作为 JPA 的提供者,它是必需的。 @MikeNakis 你是不正确的迈克。如果您使用 hibernate 作为 JPA (Amalgovinus) 的提供者,Hibernate 需要默认构造函数来实例化对象,否则 Hibernate 将报告 Caused by: org.hibernate.InstantiationException: No default constructor for entity: : hibernate.tutorial.Student,就像我刚刚遇到的情况一样 @Mushy 问题被标记为“hibernate”和“orm”,它没有被标记为“jpa”。问题中没有提到 JPA。 @MikeNakis 我同意 Mike,但 Hibernate 被用作“JPA”的实现,并且在没有“JPA”或“ORM”的情况下不使用。因此,假设是hibernate正在实现“JPA”。 【参考方案1】:

在我的例子中,我不得不隐藏我的无参数构造函数,但是因为 Hibernate 我不能这样做。所以我用另一种方式解决了这个问题。

/**
 * @deprecated (Hibernate's exclusive constructor)
 */
public ObjectConstructor () 

【讨论】:

【参考方案2】:

Erm,对不起大家,但是 Hibernate 要求你的类必须有一个无参数的构造函数。 JPA 2.0 specification 需要它,这代表 JPA 非常蹩脚。像 JAXB 这样的其他框架也需要它,这对于那些框架来说也很蹩脚。

(实际上,JAXB 应该允许实体工厂,但它坚持自己实例化这些工厂,要求它们有一个 --guess what-- 无参数构造函数,这在我的书中是完全一样的还好不允许工厂;那是多么的蹩脚!)

但是Hibernate不需要这样的东西。

Hibernate 支持一种拦截机制(参见"Interceptor" in the documentation),它允许您使用所需的任何构造函数参数来实例化您的对象。

基本上,您所做的是,当您设置 hibernate 时,您会向它传递一个实现 org.hibernate.Interceptor 接口的对象,然后当它需要一个对象的新实例时,hibernate 将调用该接口的 instantiate() 方法你的,所以你对该方法的实现可以new你的对象以任何你喜欢的方式。

我在一个项目中完成了它,它就像一个魅力。在这个项目中,我尽可能通过 JPA 进行操作,并且只有在没有其他选择时才使用拦截器等 Hibernate 功能。

Hibernate 似乎对此有些不安全,因为在启动期间它会为我的每个实体类发出一条信息消息,告诉我 INFO: HHH000182: No default (no-argument) constructor for classclass must be instantiated by Interceptor,但后来我确实通过拦截器实例化它们,它对此很满意。

对于Hibernate 以外的工具,要回答问题的“为什么”部分,答案是“绝对没有充分的理由”,这可以通过 hibernate 拦截器的存在来证明。有许多工具可以支持一些类似的客户端对象实例化机制,但它们不支持,因此它们自己创建对象,因此它们必须需要无参数构造函数。我很想相信这种情况正在发生,因为这些工具的创建者认为自己是忍者系统程序员,他们创建了充满魔法的框架,供无知的应用程序程序员使用,他们(他们认为)在他们最疯狂的梦想中永远不会有一个需要像...Factory Pattern 这样的高级构造。 (好吧,我很想这么想。我实际上不这么想。我在开玩笑。)

【讨论】:

终于有人明白了!我花在处理这些框架上的时间比我喜欢的要多,这些框架掩盖了对象实例化过程(这对于正确的依赖注入和丰富的对象行为绝对至关重要)。此外,Java 反射允许您在不使用 newInstance() 的情况下创建对象。自 JDK 1.1 以来,getDeclaredConstructors 方法一直在反射 API 中。可怕的是 JPA 规范设计者忽略了这一点。 这是错误的。如果 Hibernate 用作 JPA 提供程序以实现持久性,它确实需要一个默认构造函数,否则会出现以下 Caused by: org.hibernate.InstantiationException: No default constructor for entity: : hibernate.tutorial.Student,这是最近发生的,因为使用了 javax.persistence.*;,并且在创建 Session, SessionFactory, and Configuration 时只有 org.hibernate @Mushy 这是完全正确的,因为a)问题是关于休眠的,没有提到JPA,并且b)我仍然在我的答案的第二句话中明确提到JPA确实需要即使 hibernate 没有默认构造函数。【参考方案3】:

查看解释静态和非静态内部类之间区别的 Java 语言规范的这一部分:http://java.sun.com/docs/books/jls/third_edition/html/classes.html#8.1.3

静态内部类在概念上与在 .java 文件中声明的常规通用类没有区别。

由于 Hibernate 需要独立于 Project 实例来实例化 ProjectPK,ProjectPK 要么需要是静态内部类,要么在它自己的 .java 文件中声明。

参考org.hibernate.InstantiationException: No default constructor

【讨论】:

【参考方案4】:

hibernate 是一个支持字段或属性访问策略的 ORM 框架。但是,它不支持基于构造函数的映射——也许你想要什么? - 因为一些问题,比如

如果你的类包含很多构造函数会发生什么

public class Person 

    private String name;
    private Integer age;

    public Person(String name, Integer age)  ... 
    public Person(String name)  ... 
    public Person(Integer age)  ... 


如您所见,您处理的是不一致问题,因为 Hibernate 无法假设应该调用哪个构造函数。例如,假设您需要检索存储的 Person 对象

Person person = (Person) session.get(Person.class, <IDENTIFIER>);

Hibernate 应该调用哪个构造函数来检索 Person 对象?你能看到吗?

最后,通过使用反射,Hibernate 可以通过其无参数构造函数实例化一个类。所以当你打电话时

Person person = (Person) session.get(Person.class, <IDENTIFIER>);

Hibernate 将按如下方式实例化您的 Person 对象

Person.class.newInstance();

根据 API 文档

该类被实例化为一个带有 empty 参数列表的 new 表达式

故事的寓意

Person.class.newInstance();

类似于

new Person();

没有别的

【讨论】:

这是迄今为止我找到的关于这个问题的最出色的描述。我发现的大多数答案都使用了书呆子的技术术语,没有人像你那样以一种灵活的方式解释它。向你致敬,谢谢! 这很可能是 Hibernate 团队的推理。但实际上,可以通过以下方式解决问题:(1) 需要一个注解,或者如果只有一个构造函数,则仅使用非默认构造函数;(2) 使用 class.getDeclaredConstructors。并使用 Constructor.newInstance() 而不是 Class.newInstance()。在 Java 8 之前,需要在 XML/注释中进行适当的映射,但这是完全可行的。 好的,所以 hibernate 从默认构造函数创建对象,然后使用 setter 设置字段 nameage?如果不是,那么它稍后会使用另一个构造函数? @tryingHard 是的,一旦实例化,Hibernate 就会使用设置器或字段——这取决于访问策略。默认情况下,Id 注释的放置给出了默认的访问策略。见docs.jboss.org/hibernate/orm/5.1/userguide/html_single/chapters/…【参考方案5】:

Hibernate 实例化您的对象。所以它需要能够实例化它们。如果没有无参数构造函数,Hibernate 将不知道 如何 实例化它,即传递什么参数。

hibernate documentation 说:

4.1.1。实现无参数构造函数

所有持久类都必须有一个默认构造函数(可以是非公共的),以便 Hibernate 可以使用Constructor.newInstance() 实例化它们。建议您有一个默认构造函数,至少具有包可见性,以便在 Hibernate 中生成运行时代理。

【讨论】:

至于构造函数的可见性,如果您使用的是 JPA v2.0,请注意 JSR-317 表示:无参数构造函数必须是公共的或受保护的。跨度> @Bozho 你好先生,我有一个疑问,如果内部休眠使用 Constructor.newInstance() 来实例化对象,那么休眠如何将值设置为没有定义任何设置器的字段? 我不明白为什么我会看到带有公共无参数构造函数的 @Embeddable 非私有子类的此警告... Constructor.newInstance() 接受参数,问题(实际上是非问题)是映射这些参数。不知道为什么hibernate没有解决这个问题。作为比较:Jackson 中的 @JsonCreator 注释做到了这一点,并且有很多不可变对象的好处。 @你能看看我的问题吗How to write HQL JOIN query for multiple table's selected Columns using Constructor In The Select Clause【参考方案6】:

Hibernate 使用代理进行延迟加载。如果您没有定义构造函数或将其设为私有,那么一些事情可能仍然有效 - 那些不依赖于代理机制的事情。例如,直接使用查询 API 加载对象(没有构造函数)。

但是,如果您使用 session.load 方法(),由于构造函数不可用,您将面临来自代理生成器库的 InstantiationException。

这个人报告了类似的情况:

http://kristian-domagala.blogspot.com/2008/10/proxy-instantiation-problem-from.html

【讨论】:

【参考方案7】:

休眠和通过反射创建对象的一般代码使用Class&lt;T&gt;.newInstance() 来创建类的新实例。此方法需要一个公共的无参数构造函数才能实例化对象。对于大多数用例,提供无参数构造函数不是问题。

有一些基于序列化的技巧可以解决没有无参数构造函数的问题,因为序列化使用 jvm 魔法来创建对象而不调用构造函数。但这并非适用于所有虚拟机。例如,XStream 可以创建没有公共无参数构造函数的对象实例,但只能在仅在某些 VM 上可用的所谓“增强”模式下运行。 (有关详细信息,请参阅链接。)Hibernate 的设计者肯定选择保持与所有 VM 的兼容性,因此避免了此类技巧,并使用了官方支持的反射方法 Class&lt;T&gt;.newInstance(),需要一个无参数的构造函数。

【讨论】:

仅供参考:构造函数不需要公开。它可以具有包可见性,Hibernate 应该在其上setAccessible(true) 我是否能够使用非默认构造函数创建自定义用户类型,以便为其操作设置所需的字段。 供参考 ObjectInputStream 做了一些类似于 sun.reflect.ReflectionFactory.getReflectionFactory().newConstructorForSerialization(classToGetInstanceOf, Object.class.getConstructor()).newInstance() 的事情,用于实例化没有默认构造函数的对象(JDK1.6 for Windows) 回复:It can have package visibility and Hibernate should setAccessible(true)it 是否意味着通过反射实例化类? Hibernate should setAccessible(true) 是什么意思? Objenesis 做到了这一点,并被许多框架广泛使用,如 spring-data 和 mockito github.com/easymock/objenesis【参考方案8】:

使用无参数构造函数通过反射创建对象,然后通过反射用数据填充其属性,比尝试将数据匹配到参数化构造函数的任意参数要容易得多,名称/命名冲突,未定义的逻辑在构造函数内部,参数集与对象的属性不匹配,等等。

许多 ORM 和序列化程序需要无参数构造函数,因为通过反射的参数化构造函数非常脆弱,而无参数构造函数既为应用程序提供稳定性,又为开发人员提供对对象行为的控制。

【讨论】:

我认为,对可能需要成为富域对象的内容强制完全可变仍然更加脆弱(如果您的实体需要成为无特征的数据包才能工作,那么它就不是 ORM - - 我在这里是因为我想要一个构造函数,但有一个未定义的丰富 setter 调用顺序)......但是 +1 因为你承认可以使用 args 对构造函数执行反射:)【参考方案9】:

实际上,您可以实例化没有 0-args 构造函数的类;您可以获取一个类的构造函数列表,选择一个并使用虚假参数调用它。

虽然这是可能的,而且我猜它会起作用并且不会有问题,但你必须同意这很奇怪。

以 Hibernate 的方式构造对象(我相信它调用 0-arg 构造函数,然后它可能直接通过反射修改实例的字段。也许它知道如何调用 setter)有点违背对象应该如何在 Java 中构造 - 使用适当的参数调用构造函数,以便新对象是您想要的对象。我相信实例化一个对象然后对其进行变异有点“反Java”(或者我会说,反纯理论Java)——当然,如果你通过直接字段操作来做到这一点,它就会进行封装和所有花哨的封装的东西.

我认为执行此操作的正确方法是在 Hibernate 映射中定义如何使用正确的构造函数从数据库行中的信息实例化对象......但这会更复杂 - 这意味着 Hibernate会更复杂,映射会更复杂......而且一切都会更“纯粹”;而且我认为这不会比当前的方法有优势(除了对“以正确的方式”做事感觉良好)。

话虽如此,并且看到 Hibernate 方法不是很“干净”,因此具有 0-arg 构造函数的义务并不是绝对必要的,但我可以理解一些要求,尽管我相信他们纯粹是这样做的“正当方式”的理由,在此之前他们偏离了“正当方式”(尽管出于合理的原因)。

【讨论】:

【参考方案10】:

Hibernate 需要根据您的查询(通过反射)创建实例,Hibernate 为此依赖于实体的无参数构造函数,因此您需要提供一个无参数构造函数。什么不清楚?

【讨论】:

在什么情况下private构造函数不正确?我看到 java.lang.InstantiationException 即使我的 JPA 实体有 private 构造函数。 reference. 我尝试了没有空构造函数的类(但使用 args 构造函数)并且它有效。我从休眠中得到了信息“INFO:HHH000182:没有类的默认(无参数)构造函数,并且类必须由拦截器实例化”,但没有异常,并且从数据库成功接收到对象。

以上是关于为啥 Hibernate 不需要参数构造函数?的主要内容,如果未能解决你的问题,请参考以下文章

为啥 C++ 构造函数在继承中需要默认参数?

为啥没有构造函数参数的类需要括号

golang函数中的参数为啥不支持默认值

为啥 C++ 映射类型参数在使用 [] 时需要一个空的构造函数?

如果类具有参数化构造函数,为啥Java不提供默认构造函数? [复制]

hibernate 为什么持久化类时必须提供一个不带参数的默认构造函数