为什么Hibernate不需要参数构造函数?


103

无参数构造函数是必需的(像Hibernate这样的工具在此构造函数上使用反射来实例化对象)。

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


7
仅供参考:The no-argument constructor is a requirement 错误的断言,以及所有继续解释其原因的答案,而不必怀疑事实是否如此(包括被接受的答案,甚至得到了赏金)都是错误的。看到这个答案:stackoverflow.com/a/29433238/773113
Mike Nakis 2015年

2
如果您将休眠用作JPA的提供程序,则需要它。
Amalgovinus

1
@MikeNakis您不正确的迈克。如果您使用hibernate作为JPA(Amalgovinus)的提供程序,则Hibernate需要一个默认的构造函数来实例化对象,否则Hibernate将Caused by: org.hibernate.InstantiationException: No default constructor for entity: : hibernate.tutorial.Student像我刚才遇到的情况一样进行报告
Mushy,2015年

@Mushy,该问题被标记为“休眠”和“ orm”,而不被标记为“ jpa”。问题中没有提到JPA。
Mike Nakis

1
@MikeNakis我同意Mike,但是将Hibernate用作“ JPA”的实现,而不在没有“ JPA”或“ ORM”的情况下使用。因此,假设休眠是在实现“ JPA”。
糊糊的

Answers:


138

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

有一些基于序列化的技巧可以解决没有no-arg构造函数的问题,因为序列化使用jvm magic创建对象而无需调用构造函数。但这并非在所有VM上都可用。例如,XStream可以创建没有公共no-arg构造函数的对象实例,但只能通过在所谓的“增强”模式下运行,该模式仅在某些VM上可用。(有关详细信息,请参见链接。)Hibernate的设计人员肯定会选择保持与所有VM的兼容性,因此避免了此类技巧,并使用了正式支持的反射方法Class<T>.newInstance(),该方法需要无参数的构造函数。


31
仅供参考:构造函数不需要是公共的。它可以具有程序包可见性,并且Hibernate应该setAccessible(true)在其上。
灰色

我可以使用非默认构造函数创建Custom UserType,以便为其操作设置所需的字段。
L-Samuels,2014年

1
作为参考ObjectInputStream,在sun.reflect.ReflectionFactory.getReflectionFactory().newConstructorForSerialization(classToGetInstanceOf, Object.class.getConstructor()).newInstance()没有默认构造函数的情况下实例化对象的操作(对于Windows为
JDK1.6

回复:It can have package visibility and Hibernate should setAccessible(true)。是否it意味着该类通过反射实例化?那是什么Hibernate should setAccessible(true)意思呢?
凯文·梅雷迪斯

Objenesis做到了这一点,并被许多框架(例如spring-data和mockito)广泛使用github.com/easymock/objenesis
ltfishie 2015年

46

休眠实例化您的对象。因此,它需要能够实例化它们。如果没有no-arg构造函数,则Hibernate将不知道如何实例化它,即传递什么参数。

Hibernate文档说:

4.1.1。实现无参数构造函数

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


6
关于构造函数的可见性,如果您使用的是JPA v2.0,请注意JSR-317说:no-arg构造函数必须是public或protected
何塞Andias

@Bozho您好先生,我有一个疑问,如果在内部休眠时使用Constructor.newInstance()实例化对象,那么如何将设置值休眠到没有定义任何设置器的字段中?
Vikas Verma

我不明白为什么我看到带有公共无参数构造函数的@Embeddable非私有子类的警告...
Amalgovinus 16'Aug10

Constructor.newInstance()接受参数,问题(实际上是非问题)正在映射这些参数。不知道为什么冬眠没有解决这个问题。为了进行比较:Jackson中的@JsonCreator注释执行了此操作,并且拥有不可变对象的很多好处。
drrob's


43

呃,对不起大家,但是Hibernate并没有要求你的类必须有一个参数的构造函数。JPA 2.0规范对此有要求,对于JPA而言,这非常la脚。其他框架(例如JAXB)也需要它,对于那些框架而言,这也非常la脚。

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

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

Hibernate支持一种拦截机制(请参阅文档中的“ Interceptor”),该机制允许您使用所需的构造函数参数来实例化您的对象。

基本上,您要做的是在设置休眠状态时将其传递给实现org.hibernate.Interceptor接口的对象,然后休眠状态将instantiate()在需要它的新实例时调用该接口的方法,因此您可以实现该方法new以您喜欢的任何方式来处理您的对象。

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

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

对于Hibernate以外的工具,要回答问题的“为什么”部分,答案是“绝对没有充分的理由”,这已通过休眠拦截器的存在得到了证明。那里有许多工具本来可以支持某种类似的机制来实现客户端对象实例化,但是它们却没有,因此它们是自己创建对象的,因此它们必须要求无参数的构造函数。我很容易相信这种情况的发生,因为这些工具的创建者将自己视为忍者系统程序员,他们创建了充满魔力的框架,供无知的应用程序程序员使用,他们(所以他们认为)绝不会在他们最疯狂的梦想中拥有需要诸如工厂模式这样的高级构造。(好的,这样想。我实际上并不这么认为。开玩笑。)


1
最后,有人得到了!我花了更多的时间而不是喜欢使用这些框架来掩盖对象实例化过程(这对于正确的依赖注入和丰富的对象行为绝对至关重要)。此外,Java反射使您无需使用newInstance()即可创建对象。从JDK 1.1开始,方法getDeclaredConstructors就已经在反射API中了。令人震惊的是,JPA规范设计师忽略了这一点。
drrob's

错了 如果将Hibernate用作持久性的JPA提供程序,则它确实需要默认的构造函数,否则,随后发生的以下Caused by: org.hibernate.InstantiationException: No default constructor for entity: : hibernate.tutorial.Student事件javax.persistence.*;是最近发生的,因为已被使用,并且仅org.hibernate当创建Session, SessionFactory, and Configuration
Mushy

2
@Mushy这是完全正确的,因为a)问题是关于休眠的,没有提到JPA,并且b)尽管如此,我在回答的第二句话中明确提到即使Hibernate不需要,JPA仍需要默认构造函数。
Mike Nakis

36

休眠是支持字段或属性访问策略的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文档

就像通过带有参数列表的表达式实例化该类一样

故事的道德启示

Person.class.newInstance();

类似于

new Person();

没有其他的


1
这是迄今为止我对这个问题最出色的描述。我发现的大多数答案都是用书呆子的技术术语,没有人像您那样以柔韧的方式来解释它。感谢您,谢谢!
黑暗骑士

1
这很可能是Hibernate团队的理由。但是实际上,可以通过以下方法解决问题:(1)需要注释,或者如果只有一个构造函数,则仅使用非默认构造函数;以及(2)使用class.getDeclaredConstructors。并使用Constructor.newInstance()代替Class.newInstance()。在Java 8之前,需要使用XML /注释进行适当的映射,但这是完全可行的。
drrob's

好的,所以hibernate从默认构造函数创建对象,然后将设置器用于字段nameage?如果不是,那么稍后使用另一个构造函数?
试试

2
@tryingHard是的,一旦实例化,Hibernate将使用设置器或字段-这取决于访问策略。默认情况下,Id注释的位置提供默认的访问策略。见docs.jboss.org/hibernate/orm/5.1/userguide/html_single/chapters/...
亚瑟·罗纳德·

6

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

尽管这是可能的,而且我想它会起作用并且不会出现问题,但您必须同意这很奇怪。

以Hibernate的方式构造对象(我相信它会调用0-arg构造函数,然后它可能直接通过Reflection修改实例的字段。也许它知道如何调用setter)与应该如何构造对象有关。 Java-使用适当的参数调用构造函数,以便新对象是您想要的对象。我相信实例化一个对象然后对其进行变异有点“反Java”(或者我会说反纯理论Java),并且肯定的是,如果您通过直接的字段操作来做到这一点,它将进行封装以及所有奇特的封装工作。

我认为执行此操作的正确方法是在Hibernate映射中定义如何使用适当的构造函数从数据库行中的信息实例化对象...但这将更加复杂-意味着两个Hibernate都将甚至更复杂的是,映射也将更加复杂……而且都将变得更加“纯粹”。而且我认为这不会比目前的方法有优势(除了对“正确的方式”做事感到满意之外)。

话虽如此,并且看到Hibernate的方法不是很“干净”,但严格来说没有必要拥有0-arg构造函数,但我可以从某种程度上理解这一要求,尽管我相信他们纯粹是通过“正确的方式”完成的”,因为他们早于“正确的方式”(尽管出于合理的原因)偏离了。


5

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


在什么情况下private构造函数不正确?我java.lang.InstantiationException什至看到一个privateJPA实体的构造函数。参考
凯文·梅雷迪斯

我尝试了没有空构造函数的类(但是使用了args构造函数),并且它起作用了。我从休眠中获得了“ INFO:HHH000182:无需通过Interceptor实例化类和类的默认(无参数)构造函数”的INFO,但是没有异常,并且成功从DB接收了对象。
lijep大坝

2

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

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


我认为,强制将完全可变性强制为富域对象还是比较脆弱的(如果您的实体需要无特征的数据包才能工作,那么ORM就不多了-我在这里是因为我想要一个构造函数,但是具有富setter调用的未定义顺序)...但是+1,因为您承认可以使用args在构造函数上执行反射:)
drrob

2

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

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

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

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


0

查看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:无默认构造函数

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.