Perché Hibernate non richiede alcun costruttore di argomenti?


103

Il costruttore senza argomenti è un requisito (strumenti come Hibernate usano la riflessione su questo costruttore per istanziare gli oggetti).

Ho ricevuto questa risposta agitata, ma qualcuno potrebbe spiegare ulteriormente? Grazie


7
Cordiali saluti: l'affermazione che The no-argument constructor is a requirement è sbagliata e tutte le risposte che procedono a spiegare perché è così senza chiedersi se sia effettivamente così (inclusa la risposta accettata, che ha persino ricevuto una taglia) sono sbagliate . Vedi questa risposta: stackoverflow.com/a/29433238/773113
Mike Nakis

2
È necessario se stai utilizzando ibernazione come provider per JPA.
Amalgovinus

1
@MikeNakis Hai sbagliato Mike. Hibernate richiede un costruttore predefinito per istanziare gli oggetti se stai usando Hibernate come provider per JPA (Amalgovinus) altrimenti Hibernate riporterà Caused by: org.hibernate.InstantiationException: No default constructor for entity: : hibernate.tutorial.Studentcome nel caso in cui ho appena incontrato
Mushy

@ Mushy la domanda è contrassegnata con "hibernate" e "orm", non è contrassegnata con "jpa". Non si fa menzione dell'APP nella domanda.
Mike Nakis

1
@ MikeNakis Sono d'accordo Mike ma Hibernate è usato come implementazione di "JPA" e non usato in assenza di "JPA" o "ORM". Pertanto, il presupposto è ibernato sta implementando "JPA".
Mushy

Risposte:


138

Hibernate e il codice in generale che crea oggetti tramite la riflessione vengono utilizzati Class<T>.newInstance()per creare una nuova istanza delle classi. Questo metodo richiede un costruttore pubblico no-arg per essere in grado di istanziare l'oggetto. Per la maggior parte dei casi d'uso, fornire un costruttore senza argomenti non è un problema.

Esistono hack basati sulla serializzazione che possono aggirare il problema senza un costruttore senza argomenti, poiché la serializzazione utilizza la magia jvm per creare oggetti senza richiamare il costruttore. Ma questo non è disponibile su tutte le VM. Ad esempio, XStream può creare istanze di oggetti che non dispongono di un costruttore pubblico no-arg, ma solo eseguendolo in una cosiddetta modalità "avanzata", disponibile solo su determinate VM. (Vedere il collegamento per i dettagli.) I progettisti di Hibernate hanno sicuramente scelto di mantenere la compatibilità con tutte le VM e quindi evitano questi trucchi, e usano il metodo di riflessione ufficialmente supportato che Class<T>.newInstance()richiede un costruttore senza argomenti.


31
FYI: il costruttore non ha bisogno di essere pubblico. Può avere visibilità del pacchetto e Hibernate dovrebbe setAccessible(true)su di esso.
Gray

Sono in grado di creare un Custom UserType con un costruttore non predefinito per impostare un campo necessario per le sue operazioni.
L-Samuels

1
Per riferimento ObjectInputStreamfa qualcosa sulla sun.reflect.ReflectionFactory.getReflectionFactory().newConstructorForSerialization(classToGetInstanceOf, Object.class.getConstructor()).newInstance()falsariga di per istanziare oggetti senza un costruttore predefinito (JDK1.6 per Windows)
SamYonnou

re: It can have package visibility and Hibernate should setAccessible(true). Non itsignifica che l'essere di classe un'istanza attraverso la riflessione? E cosa Hibernate should setAccessible(true)significa?
Kevin Meredith

Objenesis fa questo ed è ampiamente utilizzato da molti framework come spring-data e mockito github.com/easymock/objenesis
ltfishie

46

Hibernate istanzia i tuoi oggetti. Quindi deve essere in grado di istanziarli. Se non c'è un costruttore senza argomenti , Hibernate non saprà come istanziarlo, cioè quale argomento passare.

La documentazione di ibernazione dice:

4.1.1. Implementa un costruttore senza argomenti

Tutte le classi persistenti devono avere un costruttore predefinito (che può essere non pubblico) in modo che Hibernate possa istanziarle usando Constructor.newInstance(). Si consiglia di disporre di un costruttore predefinito con almeno visibilità del pacchetto per la generazione di proxy di runtime in Hibernate.


6
Per quanto riguarda la visibilità del costruttore, se si utilizza JPA v2.0, notare che JSR-317 dice: Il costruttore no-arg deve essere pubblico o protetto .
José Andias

@ Bozho ciao signore, ho un dubbio che se l'ibernazione interna usa Constructor.newInstance () per istanziare l'oggetto, allora come ibernare i valori impostati nei campi senza alcun setter definito?
Vikas Verma

Non capisco perché vedo questo avviso per una sottoclasse non privata @Embeddable con un costruttore pubblico no-arg ...
Amalgovinus

Constructor.newInstance () accetta argomenti, il problema (in realtà un non-problema) sta mappando quegli argomenti. Non ho idea del motivo per cui l'ibernazione non ha risolto questo problema. Per fare un confronto: l'annotazione @JsonCreator in Jackson fa questo e si sono ottenuti molti vantaggi con gli oggetti immutabili.
drrob


43

Ehm, scusate tutti, ma Hibernate non richiede che le vostre classi debbano avere un costruttore senza parametri. La specifica JPA 2.0 lo richiede, e questo è molto noioso per conto di JPA. Anche altri framework come JAXB lo richiedono, il che è anche molto debole per conto di quei framework.

(In realtà, JAXB presumibilmente consente fabbriche di entità, ma insiste nell'istanziare queste fabbriche da solo, richiedendo loro di avere un costruttore senza parametri - indovina cosa - , che nel mio libro è esattamente buono come non consentire fabbriche; quanto è zoppo !)

Ma Hibernate non richiede una cosa del genere.

Hibernate supporta un meccanismo di intercettazione, (vedere "Interceptor" nella documentazione ,) che consente di istanziare i propri oggetti con qualsiasi parametro del costruttore di cui hanno bisogno.

Fondamentalmente, quello che fai è che quando configuri l'ibernazione gli passi un oggetto che implementa l' org.hibernate.Interceptorinterfaccia, e l'ibernazione invocerà il instantiate()metodo di quell'interfaccia ogni volta che ha bisogno di una nuova istanza di un tuo oggetto, quindi la tua implementazione di quel metodo può newi tuoi oggetti come preferisci.

L'ho fatto in un progetto e funziona come un fascino. In questo progetto faccio le cose tramite JPA quando possibile e utilizzo le funzionalità di Hibernate come l'interceptor solo quando non ho altre opzioni.

Hibernate sembra essere alquanto insicuro al riguardo, poiché durante l'avvio emette un messaggio informativo per ciascuna delle mie classi di entità, dicendomi INFO: HHH000182: No default (no-argument) constructor for classe class must be instantiated by Interceptor, ma poi in seguito le installo tramite l'interceptor, ed è felice di questo.

Per rispondere alla parte "perché" della domanda per strumenti diversi da Hibernate , la risposta è "assolutamente senza una buona ragione", e questo è dimostrato dall'esistenza dell'intercettore di ibernazione. Ci sono molti strumenti là fuori che avrebbero potuto supportare un meccanismo simile per l'istanziazione di oggetti client, ma non lo fanno, quindi creano gli oggetti da soli, quindi devono richiedere costruttori senza parametri. Sono tentato di credere che ciò stia accadendo perché i creatori di questi strumenti si considerano programmatori di sistemi ninja che creano framework pieni di magia per essere utilizzati da programmatori di applicazioni ignoranti, che (così pensano) non avrebbero mai nei loro sogni più sfrenati bisogno di costrutti così avanzati come ... Factory Pattern . (Va bene,pensare così. In realtà non la penso così. Sto scherzando.)


1
Finalmente qualcuno che lo capisce! Ho passato più tempo di quanto mi piaccia occuparmi di questi framework oscurando il processo di istanziazione degli oggetti (che è assolutamente cruciale per una corretta iniezione delle dipendenze e per un comportamento ricco degli oggetti). Inoltre, la riflessione Java consente di creare oggetti senza utilizzare newInstance (). Il metodo getDeclaredConstructors è presente nell'API di riflessione sin da JDK 1.1. È terrificante che i progettisti delle specifiche JPA lo abbiano trascurato.
drrob

Questo è sbagliato. Se Hibernate viene utilizzato come provider JPA per la persistenza, richiede un costruttore predefinito altrimenti segue quanto segue Caused by: org.hibernate.InstantiationException: No default constructor for entity: : hibernate.tutorial.Studentche si javax.persistence.*;è verificato di recente perché viene utilizzato e solo org.hibernatequando si crea ilSession, SessionFactory, and Configuration
Mushy

2
@ Mushy Questo è perfettamente corretto, perché a) la domanda riguarda l'ibernazione, senza una sola menzione di JPA, eb) nondimeno menziono esplicitamente nella seconda frase della mia risposta che JPA richiede costruttori predefiniti anche se l'ibernazione non lo fa.
Mike Nakis

36

L'ibernazione è un framework ORM che supporta la strategia di accesso al campo o alla proprietà. Tuttavia, non supporta la mappatura basata sul costruttore, forse quello che vorresti? - a causa di alcuni problemi come

Cosa succede se la tua classe contiene molti costruttori

public class Person {

    private String name;
    private Integer age;

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

}

Come puoi vedere, hai a che fare con un problema di inconsistenza perché Hibernate non può supporre quale costruttore debba essere chiamato. Ad esempio, supponiamo di dover recuperare un oggetto Person memorizzato

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

Quale costruttore dovrebbe chiamare Hibernate per recuperare un oggetto Person? Riesci a vedere ?

Infine, usando la riflessione, Hibernate può istanziare una classe tramite il suo costruttore senza argomenti. Quindi quando chiami

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

Hibernate istanzerà il tuo oggetto Person come segue

Person.class.newInstance();

Che secondo la documentazione API

La classe viene istanziata come da una nuova espressione con un elenco di argomenti vuoto

Morale della storia

Person.class.newInstance();

è simile a

new Person();

Nient'altro


1
Questa è di gran lunga la descrizione più eccellente che ho trovato riguardo a questa domanda. La maggior parte delle risposte che avevo trovato utilizzavano termini tecnici libreschi e nessun ente lo spiegava in modo flessibile come hai fatto tu. Complimenti a te e grazie!
Il cavaliere oscuro

1
Questo potrebbe essere il ragionamento del team di Hibernate. Ma in realtà, i problemi potrebbero essere risolti (1) richiedendo un'annotazione, oppure utilizzando solo un costruttore non predefinito se esiste un solo costruttore e (2) utilizzando class.getDeclaredConstructors. E usando Constructor.newInstance () invece di Class.newInstance (). Sarebbe necessaria una mappatura appropriata in XML / annotazioni, prima di Java 8, ma è del tutto fattibile.
drrob

Ok, quindi hibernate crea un oggetto dal costruttore predefinito, quindi utilizza i setter per i campi namee age? In caso contrario, successivamente utilizza un altro costruttore?
tryHard

2
@tryingHard Sì, una volta istanziato, Hibernate utilizza i setter oi campi - Dipende dalla strategia di accesso. Per impostazione predefinita, il posizionamento dell'annotazione Id fornisce la strategia di accesso predefinita. Vedi docs.jboss.org/hibernate/orm/5.1/userguide/html_single/chapters/…
Arthur Ronald

6

In realtà, puoi istanziare classi che non hanno un costruttore 0-args; puoi ottenere un elenco dei costruttori di una classe, sceglierne uno e invocarlo con parametri fasulli.

Anche se questo è possibile, e immagino che funzionerebbe e non sarebbe problematico, dovrai ammettere che è piuttosto strano.

Costruire oggetti come fa Hibernate (credo che invoca il costruttore 0-arg e quindi probabilmente modifica i campi dell'istanza direttamente tramite Reflection. Forse sa come chiamare setter) va un po 'contro come dovrebbe essere costruito un oggetto in Java: invoca il costruttore con i parametri appropriati in modo che il nuovo oggetto sia l'oggetto desiderato. Credo che istanziare un oggetto e poi mutarlo sia in qualche modo "anti-Java" (o, direi, anti Java teorico puro) - e sicuramente, se lo fai tramite la manipolazione diretta del campo, si passa all'incapsulamento e tutte quelle fantasiose cose di incapsulamento .

Penso che il modo corretto per farlo sarebbe definire nella mappatura Hibernate come un oggetto dovrebbe essere istanziato dalle informazioni nella riga del database usando il costruttore appropriato ... ma questo sarebbe più complesso, il che significa che entrambi Hibernate sarebbero pari più complessa, la mappatura sarebbe più complessa ... e tutto per essere più "puro"; e non penso che questo avrebbe un vantaggio rispetto all'approccio attuale (oltre a sentirsi bene nel fare le cose "nel modo giusto").

Detto questo, e visto che l'approccio Hibernate non è molto "pulito", l'obbligo di avere un costruttore a 0 arg non è strettamente necessario, ma posso capire in qualche modo il requisito, anche se credo che lo abbiano fatto in modo puramente "corretto "motivi, quando si sono allontanati dal" modo corretto "(anche se per ragioni ragionevoli) molto prima.


5

Hibernate ha bisogno di creare istanze come risultato delle tue query (tramite la riflessione), Hibernate si affida al costruttore no-arg di entità per questo, quindi devi fornire un costruttore no-arg. Cosa non è chiaro?


In quali condizioni un privatecostruttore non è corretto? Sto vedendo java.lang.InstantiationExceptionanche con un privatecostruttore per la mia entità JPA. riferimento .
Kevin Meredith

Ho provato la classe senza un costruttore vuoto (ma con il costruttore args) e ha funzionato. Ho ricevuto INFO da hibernate "INFO: HHH000182: Nessun costruttore predefinito (senza argomenti) per classe e classe deve essere istanziato da Interceptor", ma non ci sono state eccezioni e l'oggetto è stato ricevuto con successo dal DB.
lijep dam

2

È molto più facile creare oggetti con un costruttore senza parametri attraverso la riflessione, e quindi riempire le sue proprietà con i dati attraverso la riflessione, piuttosto che provare ad abbinare i dati ai parametri arbitrari di un costruttore parametrizzato, con nomi che cambiano / conflitti di denominazione, logica indefinita all'interno del costruttore, set di parametri che non corrispondono alle proprietà di un oggetto, eccetera.

Molti ORM e serializzatori richiedono costruttori senza parametri, perché i costruttori paramterizzati tramite la reflection sono molto fragili ei costruttori senza parametri forniscono sia stabilità all'applicazione che controllo sul comportamento dell'oggetto allo sviluppatore.


Direi che forzare la completa mutabilità su quello che potrebbe dover essere un ricco oggetto di dominio è ancora più fragile (non è un grande ORM se le tue entità devono essere sacchi di dati senza caratteristiche per funzionare - Sono qui perché voglio un costruttore ma invece ha un ordine indefinito di rich setter call) ... Ma +1 perché riconosci che la riflessione può essere eseguita sui costruttori con args :)
drrob

2

Hibernate utilizza proxy per il caricamento lento. Se non si definisce un costruttore o lo si rende privato, alcune cose potrebbero ancora funzionare - quelle che non dipendono dal meccanismo proxy. Ad esempio, caricare l'oggetto (senza costruttore) direttamente utilizzando l'API di query.

Tuttavia, se utilizzi il metodo session.load () dovrai affrontare InstantiationException dal generatore di proxy lib a causa della non disponibilità del costruttore.

Questo ragazzo ha riportato una situazione simile:

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


0

Dai un'occhiata a questa sezione delle specifiche del linguaggio Java che spiega la differenza tra classi interne statiche e non statiche: http://java.sun.com/docs/books/jls/third_edition/html/classes.html#8.1.3

Una classe interna statica non è concettualmente diversa da una normale classe generale dichiarata in un file .java.

Poiché Hibernate deve creare un'istanza di ProjectPK indipendentemente dall'istanza di Project, ProjectPK deve essere una classe interna statica o dichiarata nel proprio file .java.

riferimento org.hibernate.InstantiationException: nessun costruttore predefinito

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.