Pourquoi Hibernate ne nécessite aucun constructeur d'argument?

105

Le constructeur sans argument est une exigence (des outils comme Hibernate utilisent la réflexion sur ce constructeur pour instancier des objets).

J'ai eu cette réponse ondulée mais quelqu'un pourrait-il expliquer davantage? Merci

unj2
la source
7
FYI: l'affirmation qui The no-argument constructor is a requirement est fausse , et toutes les réponses qui continuent à expliquer pourquoi il en est ainsi sans se demander s'il en est effectivement ainsi (y compris la réponse acceptée, qui a même reçu une prime,) sont fausses . Voir cette réponse: stackoverflow.com/a/29433238/773113
Mike Nakis
2
Il est obligatoire si vous utilisez hibernate en tant que fournisseur pour JPA.
Amalgovinus
1
@MikeNakis Vous vous trompez Mike. Hibernate nécessite un constructeur par défaut pour instancier les objets si vous utilisez hibernate comme fournisseur pour JPA (Amalgovinus), sinon Hibernate rapportera Caused by: org.hibernate.InstantiationException: No default constructor for entity: : hibernate.tutorial.Studentcomme dans le cas où je viens de rencontrer
Mushy
@Mushy la question est taguée avec "hibernate" et "orm", elle n'est pas taguée avec "jpa". Il n'y a aucune mention de JPA dans la question.
Mike Nakis
1
@MikeNakis Je suis d'accord Mike mais Hibernate est utilisé comme une implémentation de "JPA" et n'est pas utilisé en l'absence de "JPA" ou "ORM". Par conséquent, l'hypothèse est que la mise en veille prolongée met en œuvre "JPA".
Mushy

Réponses:

138

Hibernate, et le code en général qui crée des objets via la réflexion utilise Class<T>.newInstance()pour créer une nouvelle instance de vos classes. Cette méthode nécessite un constructeur public sans argument pour pouvoir instancier l'objet. Pour la plupart des cas d'utilisation, fournir un constructeur sans argument n'est pas un problème.

Il existe des hacks basés sur la sérialisation qui peuvent contourner le fait de ne pas avoir de constructeur sans argument, car la sérialisation utilise la magie jvm pour créer des objets sans appeler le constructeur. Mais ce n'est pas disponible sur toutes les VM. Par exemple, XStream peut créer des instances d'objets qui n'ont pas de constructeur public no-arg, mais uniquement en s'exécutant dans un mode dit "amélioré" qui n'est disponible que sur certaines VM. (Voir le lien pour plus de détails.) Les concepteurs d'Hibernate ont sûrement choisi de maintenir la compatibilité avec toutes les machines virtuelles et évite ainsi de telles astuces, et utilise la méthode de réflexion officiellement prise en charge Class<T>.newInstance()nécessitant un constructeur sans argument.

mdma
la source
31
FYI: Le constructeur n'a pas besoin d'être public. Il peut avoir une visibilité sur les paquets et Hibernate devrait y figurer setAccessible(true).
Gris du
Suis-je capable de créer un UserType personnalisé avec un constructeur non par défaut afin de définir un champ nécessaire pour ses opérations.
L-Samuels
1
Pour référence, ObjectInputStreamfait quelque chose comme sun.reflect.ReflectionFactory.getReflectionFactory().newConstructorForSerialization(classToGetInstanceOf, Object.class.getConstructor()).newInstance()pour instancier des objets sans constructeur par défaut (JDK1.6 pour Windows)
SamYonnou
re: It can have package visibility and Hibernate should setAccessible(true). Cela itsignifie- t - il que la classe est instanciée par réflexion? Et qu'est-ce que ça Hibernate should setAccessible(true)veut dire?
Kevin Meredith
Objenesis le fait et est largement utilisé par de nombreux frameworks tels que spring-data et mockito github.com/easymock/objenesis
ltfishie
47

Hibernate instancie vos objets. Il doit donc être en mesure de les instancier. S'il n'y a pas de constructeur no-arg, Hibernate ne saura pas comment l' instancier, c'est-à-dire quel argument passer.

La documentation de mise en veille prolongée dit:

4.1.1. Implémenter un constructeur sans argument

Toutes les classes persistantes doivent avoir un constructeur par défaut (qui peut être non public) pour qu'Hibernate puisse les instancier en utilisant Constructor.newInstance(). Il est recommandé d'avoir un constructeur par défaut avec au moins une visibilité sur les packages pour la génération de proxy d'exécution dans Hibernate.

Bozho
la source
6
En ce qui concerne la visibilité du constructeur, si vous utilisez JPA v2.0, notez que le JSR-317 dit: Le constructeur no-arg doit être public ou protégé .
José Andias
@Bozho bonjour monsieur, j'ai un doute que si la mise en veille prolongée utilise en interne Constructor.newInstance () pour instancier un objet, comment mettre en veille prolongée les valeurs définies dans des champs sans aucun paramètre défini?
Vikas Verma
Je ne comprends pas pourquoi je vois cet avertissement pour une sous-classe non privée @Embeddable avec un constructeur public no-arg ...
Amalgovinus
Constructor.newInstance () prend des arguments, le problème (en fait un non-problème) est de mapper ces arguments. Je ne sais pas pourquoi la mise en veille prolongée n'a pas résolu ce problème. À titre de comparaison: l'annotation @JsonCreator dans Jackson fait cela, et beaucoup d'avantages des objets immuables ont été obtenus.
drrob
44

Euh, désolé tout le monde, mais Hibernate n'exige pas que vos classes aient un constructeur sans paramètre. La spécification JPA 2.0 exige, et c'est très boiteux de la part de JPA. D'autres frameworks comme JAXB l'exigent également, ce qui est également très boiteux de la part de ces frameworks.

(En fait, JAXB autorise soi-disant les usines d'entités, mais il insiste pour instancier ces usines par lui-même, en leur demandant d'avoir un - devinez quoi-- constructeur sans paramètre , ce qui dans mon livre est exactement aussi bon que de ne pas autoriser les usines; à quel point est-ce que !)

Mais Hibernate n'exige pas une telle chose.

Hibernate prend en charge un mécanisme d'interception, (voir «Intercepteur» dans la documentation ,) qui vous permet d'instancier vos objets avec les paramètres de constructeur dont ils ont besoin.

Fondamentalement, ce que vous faites est que lorsque vous configurez la mise en veille prolongée, vous lui transmettez un objet implémentant l' org.hibernate.Interceptorinterface, et la mise en veille prolongée appellera alors la instantiate()méthode de cette interface chaque fois qu'elle a besoin d'une nouvelle instance d'un de vos objets, de sorte que votre implémentation de cette méthode peut newvos objets comme vous le souhaitez.

Je l'ai fait dans un projet et cela fonctionne comme un charme. Dans ce projet, je fais les choses via JPA chaque fois que c'est possible, et je n'utilise les fonctionnalités Hibernate comme l'intercepteur que lorsque je n'ai pas d'autre option.

Hibernate semble être quelque peu incertain à ce sujet, car au démarrage, il émet un message d'information pour chacune de mes classes d'entité, me disant INFO: HHH000182: No default (no-argument) constructor for classetclass must be instantiated by Interceptor , mais plus tard, je les instancie par intercepteur, et il en est satisfait.

Pour répondre à la partie «pourquoi» de la question pour les outils autres qu'Hibernate , la réponse est «sans aucune raison valable», et cela est prouvé par l'existence de l'intercepteur d'hibernation. Il existe de nombreux outils qui auraient pu prendre en charge un mécanisme similaire pour l'instanciation d'objet client, mais ils ne le font pas, ils créent donc les objets eux-mêmes, ils doivent donc nécessiter des constructeurs sans paramètre. Je suis tenté de croire que cela se produit parce que les créateurs de ces outils se considèrent comme des programmeurs de systèmes ninja qui créent des cadres pleins de magie à utiliser par des programmeurs d'applications ignorants, qui (selon eux) n'auraient jamais dans leurs rêves les plus fous besoin de constructions avancées comme le ... Factory Pattern . (D'accord,penser ainsi. Je ne pense pas vraiment . Je plaisante.)

Mike Nakis
la source
1
Enfin quelqu'un qui comprend! J'ai passé plus de temps que j'aime à traiter ces frameworks pour obscurcir le processus d'instanciation d'objet (ce qui est absolument crucial pour une injection de dépendances appropriée et pour un comportement d'objet riche). De plus, la réflexion Java vous permet de créer des objets sans utiliser newInstance (). La méthode getDeclaredConstructors fait partie de l'API de réflexion depuis le JDK 1.1. Il est terrifiant que les concepteurs de spécifications JPA aient négligé cela.
drrob
C'est faux. Si Hibernate est utilisé comme fournisseur JPA pour la persistance, il nécessite un constructeur par défaut sinon ce qui suit s'ensuit Caused by: org.hibernate.InstantiationException: No default constructor for entity: : hibernate.tutorial.Studentqui s'est produit récemment car il javax.persistence.*;est utilisé et uniquement org.hibernatelors de la création duSession, SessionFactory, and Configuration
Mushy
2
@Mushy C'est parfaitement correct, car a) la question porte sur la mise en veille prolongée, sans une seule mention de JPA, et b) je mentionne néanmoins explicitement dans la deuxième phrase de ma réponse que JPA nécessite des constructeurs par défaut même si hibernate ne le fait pas.
Mike Nakis
36

La mise en veille prolongée est un framework ORM qui prend en charge la stratégie d'accès aux champs ou aux propriétés. Cependant, il ne prend pas en charge le mappage basé sur le constructeur - peut-être que vous aimeriez? - à cause de certains problèmes comme

Que se passe-t-il si votre classe contient beaucoup de constructeurs

public class Person {

    private String name;
    private Integer age;

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

}

Comme vous pouvez le voir, vous gérez un problème d'incohérence car Hibernate ne peut pas supposer quel constructeur doit être appelé. Par exemple, supposons que vous ayez besoin de récupérer un objet Person stocké

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

Quel constructeur doit appeler Hibernate pour récupérer un objet Person? Peux tu voir ?

Et enfin, en utilisant la réflexion, Hibernate peut instancier une classe via son constructeur sans argument. Alors quand tu appelles

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

Hibernate instanciera votre objet Person comme suit

Person.class.newInstance();

Qui selon la documentation de l'API

La classe est instanciée comme par une nouvelle expression avec une liste d'arguments vide

Morale de l'histoire

Person.class.newInstance();

est similaire à

new Person();

Rien d'autre

Arthur Ronald
la source
1
C'est de loin la description la plus excellente que j'aie trouvée à propos de cette question. La plupart des réponses que j'avais trouvées utilisaient des termes techniques livresques et aucun organisme ne l'expliquait d'une manière souple comme vous l'avez fait. Bravo à vous et merci!
The Dark Knight
1
Cela pourrait bien être le raisonnement de l'équipe Hibernate. Mais en réalité, les problèmes pourraient être résolus en (1) nécessitant soit une annotation, soit en utilisant uniquement un constructeur non par défaut s'il n'y a qu'un seul constructeur et (2) en utilisant class.getDeclaredConstructors. Et en utilisant Constructor.newInstance () au lieu de Class.newInstance (). Un mappage approprié en XML / annotations serait nécessaire, avant Java 8, mais c'est tout à fait faisable.
drrob
Ok, donc hibernate crée un objet à partir du constructeur par défaut, puis il utilise des setters pour les champs nameet age? Sinon, il utilise un autre constructeur plus tard?
essayantHard
2
@tryingHard Oui, une fois instancié, Hibernate utilise les setters ou les champs - Cela dépend de la stratégie d'accès. Par défaut, le placement de l'annotation Id donne la stratégie d'accès par défaut. Voir docs.jboss.org/hibernate/orm/5.1/userguide/html_single/chapters/…
Arthur Ronald
6

En fait, vous pouvez instancier des classes qui n'ont pas de constructeur 0-args; vous pouvez obtenir une liste des constructeurs d'une classe, en choisir un et l'invoquer avec de faux paramètres.

Bien que cela soit possible, et je suppose que cela fonctionnerait et ne serait pas problématique, vous devrez convenir que c'est assez étrange.

Construire des objets comme le fait Hibernate (je crois qu'il invoque le constructeur 0-arg, puis il modifie probablement les champs de l'instance directement via Reflection. Peut-être qu'il sait comment appeler des setters) va un peu à l'encontre de la façon dont un objet est censé être construit dans Java - invoquez le constructeur avec les paramètres appropriés pour que le nouvel objet soit l'objet souhaité. Je crois qu'instancier un objet puis le muter est un peu "anti-Java" (ou je dirais, anti pur Java théorique) - et certainement, si vous faites cela via une manipulation directe de champ, cela passe par l'encapsulation et tout ce truc d'encapsulation sophistiqué .

Je pense que la bonne façon de faire cela serait de définir dans le mappage Hibernate comment un objet devrait être instancié à partir des informations de la ligne de base de données en utilisant le constructeur approprié ... mais ce serait plus complexe - ce qui signifie que les deux Hibernate seraient même plus complexe, la cartographie serait plus complexe ... et tout cela pour être plus "pur"; et je ne pense pas que cela aurait un avantage sur l'approche actuelle (autre que se sentir bien de faire les choses "de la bonne manière").

Cela dit, et vu que l'approche Hibernate n'est pas très "propre", l'obligation d'avoir un constructeur 0-arg n'est pas strictement nécessaire, mais je peux comprendre quelque peu l'exigence, même si je pense qu'ils l'ont fait de manière purement "correcte "motifs, lorsqu'ils se sont écartés de la" bonne voie "(quoique pour des raisons raisonnables) bien avant cela.

Alex
la source
5

Hibernate doit créer des instances à la suite de vos requêtes (via la réflexion), Hibernate s'appuie sur le constructeur d'entités no-arg pour cela, vous devez donc fournir un constructeur no-arg. Qu'est-ce qui n'est pas clair?

Pascal Thivent
la source
Dans quelles conditions un privateconstructeur est-il incorrect? Je vois java.lang.InstantiationExceptionmême avec un privateconstructeur pour mon entité JPA. référence .
Kevin Meredith
J'ai essayé la classe sans constructeur vide (mais avec le constructeur args) et cela a fonctionné. J'ai obtenu des informations de hibernate "INFO: HHH000182: Aucun constructeur par défaut (sans argument) pour la classe et la classe ne doit être instancié par Interceptor", mais il n'y a pas eu d'exception et l'objet a été reçu avec succès de DB.
lijep dam
2

Il est beaucoup plus facile de créer un objet avec un constructeur sans paramètre par réflexion, puis de remplir ses propriétés de données par réflexion, que d'essayer de faire correspondre des données à des paramètres arbitraires d'un constructeur paramétré, avec des changements de noms / conflits de dénomination, une logique non définie à l'intérieur du constructeur, ensembles de paramètres ne correspondant pas aux propriétés d'un objet, et cetera.

De nombreux ORM et sérialiseurs nécessitent des constructeurs sans paramètre, car les constructeurs paramétrés par réflexion sont très fragiles et les constructeurs sans paramètre fournissent à la fois la stabilité de l'application et le contrôle du comportement de l'objet pour le développeur.

Kaerber
la source
Je dirais que forcer une mutabilité complète sur ce qui peut avoir besoin d'être un objet de domaine riche est encore plus fragile (ce n'est pas vraiment un ORM si vos entités doivent être des sacs de données sans caractéristiques pour fonctionner - je suis ici parce que je veux un constructeur mais à la place ont un ordre non défini d'appels de setter riches) ... Mais +1 parce que vous reconnaissez que la réflexion peut être effectuée sur les constructeurs avec args :)
drrob
2

Hibernate utilise des proxies pour le chargement différé. Si vous ne définissez pas de constructeur ou ne le rendez pas privé, certaines choses peuvent toujours fonctionner - celles qui ne dépendent pas du mécanisme de proxy. Par exemple, charger l'objet (sans constructeur) directement à l'aide de l'API de requête.

Mais, si vous utilisez la méthode session.load (), vous serez confronté à InstantiationException de la bibliothèque du générateur de proxy en raison de la non-disponibilité du constructeur.

Ce type a signalé une situation similaire:

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

haps10
la source
0

Consultez cette section de la spécification du langage Java qui explique la différence entre les classes internes statiques et non statiques: http://java.sun.com/docs/books/jls/third_edition/html/classes.html#8.1.3

Une classe interne statique n'est pas différente du point de vue conceptuel d'une classe générale ordinaire déclarée dans un fichier .java.

Étant donné qu'Hibernate doit instancier ProjectPK indépendamment de l'instance Project, ProjectPK doit soit être une classe interne statique, soit être déclaré dans son propre fichier .java.

reference org.hibernate.InstantiationException: aucun constructeur par défaut

Amitābha
la source