Warum benötigt Hibernate keinen Argumentkonstruktor?

102

Der Konstruktor ohne Argumente ist eine Anforderung (Tools wie Hibernate verwenden die Reflexion dieses Konstruktors, um Objekte zu instanziieren).

Ich habe diese handgewellte Antwort erhalten, aber könnte jemand weiter erklären? Vielen Dank

unj2
quelle
7
Zu Ihrer Information: Die Behauptung, The no-argument constructor is a requirement die falsch ist , und alle Antworten, die erklären, warum dies so ist, ohne zu hinterfragen, ob dies tatsächlich so ist (einschließlich der akzeptierten Antwort, die sogar Kopfgeld erhalten hat), sind falsch . Siehe diese Antwort: stackoverflow.com/a/29433238/773113
Mike Nakis
2
Dies ist erforderlich, wenn Sie den Ruhezustand als Anbieter für JPA verwenden.
Amalgovinus
1
@ MikeNakis Du bist falsch Mike. Für den Ruhezustand ist ein Standardkonstruktor erforderlich, um Objekte zu instanziieren, wenn Sie den Ruhezustand als Anbieter für JPA (Amalgovinus) verwenden. Andernfalls meldet sich der Ruhezustand Caused by: org.hibernate.InstantiationException: No default constructor for entity: : hibernate.tutorial.Studentwie in dem Fall, in dem ich gerade aufgetreten bin
Mushy
@Mushy die Frage ist mit "Ruhezustand" und "Orm" markiert, sie ist nicht mit "JPA" markiert. JPA wird in der Frage nicht erwähnt.
Mike Nakis
1
@ MikeNakis Ich stimme Mike zu, aber Hibernate wird als Implementierung von "JPA" verwendet und nicht ohne "JPA" oder "ORM". Daher wird im Ruhezustand "JPA" implementiert.
Mushy

Antworten:

136

Ruhezustand und Code im Allgemeinen, der Objekte über Reflektion erstellt, werden verwendet Class<T>.newInstance(), um eine neue Instanz Ihrer Klassen zu erstellen. Diese Methode erfordert einen öffentlichen Konstruktor ohne Argumente, um das Objekt instanziieren zu können. In den meisten Anwendungsfällen ist die Bereitstellung eines Konstruktors ohne Argumente kein Problem.

Es gibt auf Serialisierung basierende Hacks, die umgehen können, wenn kein Konstruktor ohne Argumente vorhanden ist, da die Serialisierung mithilfe von JVM-Magie Objekte erstellt, ohne den Konstruktor aufzurufen. Dies ist jedoch nicht für alle VMs verfügbar. Beispielsweise kann XStream Instanzen von Objekten erstellen, die keinen öffentlichen Konstruktor ohne Argumente haben, sondern nur in einem sogenannten "erweiterten" Modus, der nur auf bestimmten VMs verfügbar ist. (Einzelheiten finden Sie unter dem Link.) Die Designer von Hibernate haben sich sicherlich dafür entschieden, die Kompatibilität mit allen VMs aufrechtzuerhalten, um solche Tricks zu vermeiden, und verwenden die offiziell unterstützte Reflektionsmethode, für Class<T>.newInstance()die ein Konstruktor ohne Argumente erforderlich ist.

mdma
quelle
31
Zu Ihrer Information: Der Konstruktor muss nicht öffentlich sein. Es kann Paketsichtbarkeit haben und Hibernate sollte setAccessible(true)darauf sein.
Grau
Kann ich einen benutzerdefinierten Benutzertyp mit einem nicht standardmäßigen Konstruktor erstellen, um ein erforderliches Feld für seine Operationen festzulegen?
L-Samuels
1
Als Referenz ObjectInputStreamdient etwas in der Art der sun.reflect.ReflectionFactory.getReflectionFactory().newConstructorForSerialization(classToGetInstanceOf, Object.class.getConstructor()).newInstance()Instanziierung von Objekten ohne Standardkonstruktor (JDK1.6 für Windows)
SamYonnou
re : It can have package visibility and Hibernate should setAccessible(true). Bedeutet das, itdass die Klasse durch Reflexion instanziiert wird? Und was heißt Hibernate should setAccessible(true)das?
Kevin Meredith
Objenesis tut dies und wird von vielen Frameworks wie Spring-Data und Mockito github.com/easymock/objenesis
ltfishie
46

Der Ruhezustand instanziiert Ihre Objekte. Es muss also in der Lage sein, sie zu instanziieren. Wenn es keinen Konstruktor ohne Argumente gibt, weiß Hibernate nicht, wie er instanziiert werden soll, dh welches Argument übergeben werden soll.

In der Dokumentation zum Ruhezustand heißt es:

4.1.1. Implementieren Sie einen Konstruktor ohne Argumente

Alle persistenten Klassen müssen über einen Standardkonstruktor verfügen (der nicht öffentlich sein kann), damit Hibernate sie mithilfe von instanziieren kann Constructor.newInstance(). Es wird empfohlen, einen Standardkonstruktor mit mindestens Paketsichtbarkeit für die Laufzeit-Proxy-Generierung in Hibernate zu haben.

Bozho
quelle
6
Wenn Sie JPA v2.0 verwenden, beachten Sie bei der Sichtbarkeit des Konstruktors, dass im JSR-317 Folgendes angegeben ist: Der Konstruktor ohne Argumente muss öffentlich oder geschützt sein .
José Andias
@Bozho Hallo Sir, ich habe einen Zweifel, dass wenn im internen Ruhezustand Constructor.newInstance () verwendet wird, um ein Objekt zu instanziieren, wie dann die eingestellten Werte in Felder ohne definierte Setter in den Ruhezustand versetzt werden?
Vikas Verma
Ich verstehe nicht, warum ich diese Warnung für eine nicht private @ Embeddable-Unterklasse mit einem öffentlichen Konstruktor ohne
Argumente sehe
Constructor.newInstance () akzeptiert Argumente, das Problem (eigentlich kein Problem) ordnet diese Argumente zu. Keine Ahnung, warum der Ruhezustand dieses Problem nicht gelöst hat. Zum Vergleich: Die Annotation @JsonCreator in Jackson macht dies, und es wurde viel von unveränderlichen Objekten profitiert.
Drrob
@ Könnten Sie sich bitte mein Problem ansehen ? Wie schreibe
?
43

Ähm, entschuldigen Sie alle, aber Hibernate erfordert nicht , dass Ihre Klassen einen parameterlosen Konstruktor haben. Die JPA 2.0-Spezifikation erfordert dies, und dies ist im Namen von JPA sehr lahm. Andere Frameworks wie JAXB erfordern dies ebenfalls, was für diese Frameworks ebenfalls sehr lahm ist.

(Eigentlich erlaubt JAXB angeblich Entitätsfabriken, aber es besteht darauf, diese Fabriken selbst zu instanziieren, und erfordert, dass sie einen - erraten, was - parameterlosen Konstruktor haben , was in meinem Buch genauso gut ist, als Fabriken nicht zuzulassen; wie lahm ist das ? !)

Aber Hibernate erfordert so etwas nicht.

Der Ruhezustand unterstützt einen Abfangmechanismus (siehe "Abfangjäger" in der Dokumentation ), mit dem Sie Ihre Objekte mit den erforderlichen Konstruktorparametern instanziieren können.

Grundsätzlich übergeben Sie beim Einrichten des Ruhezustands ein Objekt, das die org.hibernate.InterceptorSchnittstelle implementiert , und der Ruhezustand ruft dann die instantiate()Methode dieser Schnittstelle auf, wenn eine neue Instanz eines Objekts von Ihnen benötigt wird, damit Sie diese Methode implementieren können newIhre Objekte, wie Sie möchten.

Ich habe es in einem Projekt gemacht und es funktioniert wie ein Zauber. In diesem Projekt mache ich Dinge über JPA, wann immer dies möglich ist, und ich verwende Hibernate-Funktionen wie den Interceptor nur, wenn ich keine andere Option habe.

Der Ruhezustand scheint etwas unsicher zu sein, da beim Start eine Infomeldung für jede meiner Entitätsklassen ausgegeben wird, die mir INFO: HHH000182: No default (no-argument) constructor for classundclass must be instantiated by Interceptor , aber später instanziiere ich sie per Interceptor, und das freut mich.

Um den "Warum" -Teil der Frage für andere Tools als "Ruhezustand" zu beantworten, lautet die Antwort "aus absolut keinem guten Grund", und dies wird durch die Existenz des Abfangjägers für den Ruhezustand bewiesen. Es gibt viele Tools, die einen ähnlichen Mechanismus für die Instanziierung von Clientobjekten hätten unterstützen können, aber dies ist nicht der Fall. Daher erstellen sie die Objekte selbst und benötigen daher parameterlose Konstruktoren. Ich bin versucht zu glauben, dass dies geschieht, weil die Entwickler dieser Tools sich selbst als Ninja-Systemprogrammierer betrachten, die Frameworks voller Magie erstellen, die von ignoranten Anwendungsprogrammierern verwendet werden, die (so denken sie) in ihren wildesten Träumen niemals eine haben würden Notwendigkeit für so fortschrittliche Konstrukte wie das ... Factory Pattern . (In Ordnung,so zu denken. Das glaube ich eigentlich nicht . Ich mache Witze.)

Mike Nakis
quelle
1
Endlich jemand, der es bekommt! Ich habe mehr Zeit damit verbracht, als ich gerne mit diesen Frameworks zu tun habe, die den Objektinstanziierungsprozess verdecken (was für eine ordnungsgemäße Abhängigkeitsinjektion und für ein reichhaltiges Objektverhalten absolut entscheidend ist). Darüber hinaus können Sie mit Java Reflection Objekte erstellen, ohne newInstance () zu verwenden. Die Methode getDeclaredConstructors befindet sich seit JDK 1.1 in der Reflection-API. Es ist erschreckend, dass die JPA-Spezifikationsdesigner dies vernachlässigt haben.
Drrob
Das ist falsch. Wenn Hibernate als JPA-Anbieter für die Persistenz verwendet wird, ist ein Standardkonstruktor erforderlich, andernfalls folgt Caused by: org.hibernate.InstantiationException: No default constructor for entity: : hibernate.tutorial.Studentdas Folgende, das kürzlich aufgetreten ist, weil javax.persistence.*;es verwendet wird und nur org.hibernatebeim Erstellen desSession, SessionFactory, and Configuration
Mushy
2
@Mushy Dies ist vollkommen richtig, da a) die Frage den Ruhezustand ohne eine einzige Erwähnung von JPA betrifft und b) ich im zweiten Satz meiner Antwort dennoch ausdrücklich erwähne, dass für JPA Standardkonstruktoren erforderlich sind, obwohl der Ruhezustand dies nicht tut.
Mike Nakis
36

Der Ruhezustand ist ein ORM-Framework, das die Feld- oder Eigenschaftszugriffsstrategie unterstützt. Konstruktorbasiertes Mapping wird jedoch nicht unterstützt - was möchten Sie vielleicht? - wegen einiger Probleme wie

Was passiert, wenn Ihre Klasse viele Konstruktoren enthält?

public class Person {

    private String name;
    private Integer age;

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

}

Wie Sie sehen, haben Sie es mit einem Problem der Inkonsistenz zu tun, da Hibernate nicht annehmen kann, welcher Konstruktor aufgerufen werden soll. Angenommen, Sie müssen ein gespeichertes Personenobjekt abrufen

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

Welchen Konstruktor sollte Hibernate aufrufen, um ein Personenobjekt abzurufen? Kannst du sehen ?

Und schließlich kann Hibernate durch Reflektion eine Klasse über seinen Konstruktor ohne Argumente instanziieren. Also wenn du anrufst

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

Durch den Ruhezustand wird Ihr Personenobjekt wie folgt instanziiert

Person.class.newInstance();

Welche laut API-Dokumentation

Die Klasse wird wie durch einen neuen Ausdruck mit einer leeren Argumentliste instanziiert

Moral der Geschichte

Person.class.newInstance();

ist ähnlich wie

new Person();

Nichts anderes

Arthur Ronald
quelle
1
Dies ist bei weitem die beste Beschreibung, die ich in Bezug auf diese Frage gefunden habe. Die meisten Antworten, die ich gefunden hatte, verwendeten buchstäbliche Fachbegriffe, und niemand erklärte es so biegsam wie Sie. Ein großes Lob an Sie und danke!
Der dunkle Ritter
1
Dies könnte die Argumentation des Hibernate-Teams sein. Tatsächlich könnten die Probleme jedoch gelöst werden, indem (1) entweder eine Anmerkung erforderlich ist oder nur ein nicht standardmäßiger Konstruktor verwendet wird, wenn nur ein Konstruktor vorhanden ist, und (2) class.getDeclaredConstructors verwendet wird. Und mit Constructor.newInstance () anstelle von Class.newInstance (). Vor Java 8 wäre eine entsprechende Zuordnung in XML / Anmerkungen erforderlich, die jedoch vollständig möglich ist.
Drrob
Ok, der Ruhezustand erstellt also ein Objekt aus dem Standardkonstruktor und verwendet dann Setter für Felder nameund age? Wenn nicht, wird später ein anderer Konstruktor verwendet?
Hart versuchen
2
@tryingHard Ja, nach der Instanziierung verwendet Hibernate die Setter oder Felder. Dies hängt von der Zugriffsstrategie ab. Standardmäßig gibt die Platzierung der ID-Annotation die Standardzugriffsstrategie an. Siehe docs.jboss.org/hibernate/orm/5.1/userguide/html_single/chapters/…
Arthur Ronald
6

Tatsächlich können Sie Klassen instanziieren, die keinen 0-args-Konstruktor haben. Sie können eine Liste der Konstruktoren einer Klasse abrufen, einen auswählen und ihn mit falschen Parametern aufrufen.

Dies ist zwar möglich und ich denke, es würde funktionieren und wäre nicht problematisch, aber Sie müssen zustimmen, dass das ziemlich seltsam ist.

Das Konstruieren von Objekten wie im Ruhezustand (ich glaube, es ruft den 0-arg-Konstruktor auf und ändert dann wahrscheinlich die Felder der Instanz direkt über Reflection. Vielleicht weiß es, wie man Setter aufruft) widerspricht ein wenig der Art und Weise, wie ein Objekt konstruiert werden soll Java - Rufen Sie den Konstruktor mit den entsprechenden Parametern auf, damit das neue Objekt das gewünschte Objekt ist. Ich glaube, dass das Instanziieren eines Objekts und das anschließende Mutieren etwas "Anti-Java" ist (oder ich würde sagen, anti-reines theoretisches Java) - und definitiv, wenn Sie dies über direkte Feldmanipulation tun, geht es um Kapselung und all das ausgefallene Kapselungsmaterial .

Ich denke, dass der richtige Weg, dies zu tun, darin besteht, im Hibernate-Mapping zu definieren, wie ein Objekt aus den Informationen in der Datenbankzeile mit dem richtigen Konstruktor instanziiert werden soll ... aber dies wäre komplexer - was bedeutet, dass beide Hibernate gerade wären komplexer wäre das Mapping komplexer ... und alles "reiner"; und ich denke nicht, dass dies einen Vorteil gegenüber dem gegenwärtigen Ansatz hätte (abgesehen davon, dass man sich gut fühlt, wenn man Dinge "richtig" macht).

Angesichts der Tatsache, dass der Hibernate-Ansatz nicht sehr "sauber" ist, ist die Verpflichtung, einen 0-arg-Konstruktor zu haben, nicht unbedingt erforderlich, aber ich kann die Anforderung etwas verstehen, obwohl ich glaube, dass sie dies auf rein "richtige Weise" getan haben "Gründe, als sie viel früher vom" richtigen Weg "abgewichen sind (wenn auch aus vernünftigen Gründen).

Alex
quelle
5

Hibernate muss Instanzen als Ergebnis Ihrer Abfragen erstellen (über Reflection). Hibernate verwendet dafür den Konstruktor no-arg von Entitäten, sodass Sie einen Konstruktor no-arg bereitstellen müssen. Was ist nicht klar?

Pascal Thivent
quelle
Unter welchen Bedingungen ist ein privateKonstruktor falsch? Ich sehe java.lang.InstantiationExceptionsogar mit einem privateKonstruktor für meine JPA-Entität. Referenz .
Kevin Meredith
Ich habe versucht, Klasse ohne leeren Konstruktor (aber mit args Konstruktor) und es hat funktioniert. Ich habe INFO aus dem Ruhezustand "INFO: HHH000182: Kein Standardkonstruktor (ohne Argument) für Klasse und Klasse muss von Interceptor instanziiert werden", aber es gab keine Ausnahme und das Objekt wurde erfolgreich von DB empfangen.
Lijep Mutter
2

Es ist viel einfacher, ein Objekt mit einem parameterlosen Konstruktor durch Reflexion zu erstellen und dann seine Eigenschaften durch Reflexion mit Daten zu füllen, als zu versuchen, Daten mit beliebigen Parametern eines parametrisierten Konstruktors abzugleichen, mit sich ändernden Namen / Namenskonflikten, undefinierter Logik innerhalb des Konstruktors. Parametersätze, die nicht mit den Eigenschaften eines Objekts übereinstimmen, usw.

Viele ORMs und Serializer erfordern parameterlose Konstruktoren, da parametrisierte Konstruktoren durch Reflexion sehr fragil sind und parameterlose Konstruktoren sowohl Stabilität für die Anwendung als auch Kontrolle über das Objektverhalten für den Entwickler bieten.

Kaerber
quelle
Ich würde argumentieren, dass das Erzwingen einer vollständigen Veränderlichkeit für ein möglicherweise reichhaltiges Domänenobjekt noch fragiler ist (es ist kein großer ORM, wenn Ihre Entitäten nichtssagende Datenmengen benötigen, um zu funktionieren - ich bin hier, weil ich möchte ein Konstruktor, aber stattdessen eine undefinierte Reihenfolge von Rich-Setter-Aufrufen) ... Aber +1, weil Sie anerkennen, dass Reflexion auf Konstruktoren mit
Argumenten
2

Hibernate verwendet Proxys zum verzögerten Laden. Wenn Sie einen Konstruktor nicht definieren oder privat machen, funktionieren möglicherweise noch einige Dinge - diejenigen, die nicht vom Proxy-Mechanismus abhängen. Laden Sie beispielsweise das Objekt (ohne Konstruktor) direkt über die Abfrage-API.

Wenn Sie jedoch die session.load-Methode () verwenden, tritt aufgrund der Nichtverfügbarkeit des Konstruktors eine InstantiationException aus der Proxy-Generator-Bibliothek auf.

Dieser Typ berichtete über eine ähnliche Situation:

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

haps10
quelle
0

Lesen Sie diesen Abschnitt der Java-Sprachspezifikation, in dem der Unterschied zwischen statischen und nicht statischen inneren Klassen erläutert wird: http://java.sun.com/docs/books/jls/third_edition/html/classes.html#8.1.3

Eine statische innere Klasse unterscheidet sich konzeptionell nicht von einer regulären allgemeinen Klasse, die in einer Java-Datei deklariert ist.

Da Hibernate ProjectPK unabhängig von der Project-Instanz instanziieren muss, muss ProjectPK entweder eine statische innere Klasse sein oder in einer eigenen Java-Datei deklariert sein.

Referenz org.hibernate.InstantiationException: Kein Standardkonstruktor

Amitābha
quelle