¿Por qué Hibernate no requiere un constructor de argumentos?

103

El constructor sin argumentos es un requisito (herramientas como Hibernate usan la reflexión sobre este constructor para instanciar objetos).

Recibí esta respuesta de mano ondulada, pero ¿alguien podría explicar más? Gracias

unj2
fuente
7
Para su información: la afirmación que The no-argument constructor is a requirement es incorrecta , y todas las respuestas que proceden a explicar por qué esto es así, sin cuestionar si esto es así, (incluida la respuesta aceptada, que incluso ha recibido recompensa) son incorrectas . Vea esta respuesta: stackoverflow.com/a/29433238/773113
Mike Nakis
2
ES obligatorio si está utilizando hibernate como proveedor de JPA.
Amalgovinus
1
@MikeNakis Estás incorrecto, Mike. Hibernate requiere un constructor predeterminado para instanciar objetos si está usando hibernate como proveedor de JPA (Amalgovinus); de lo contrario, Hibernate informará Caused by: org.hibernate.InstantiationException: No default constructor for entity: : hibernate.tutorial.Studentcomo en el caso en el que acabo de encontrarlo
Mushy
@Mushy la pregunta está etiquetada con "hibernate" y "orm", no está etiquetada con "jpa". No se menciona JPA en la pregunta.
Mike Nakis
1
@MikeNakis Estoy de acuerdo con Mike, pero Hibernate se usa como una implementación de "JPA" y no se usa en ausencia de "JPA" u "ORM". Por lo tanto, se supone que hibernate está implementando "JPA".
Mushy

Respuestas:

138

Hibernate, y el código en general que crea objetos a través de la reflexión se usa Class<T>.newInstance()para crear una nueva instancia de tus clases. Este método requiere un constructor público sin argumentos para poder crear una instancia del objeto. Para la mayoría de los casos de uso, proporcionar un constructor sin argumentos no es un problema.

Hay trucos basados ​​en la serialización que pueden evitar no tener un constructor sin argumentos, ya que la serialización usa jvm magic para crear objetos sin invocar al constructor. Pero esto no está disponible en todas las máquinas virtuales. Por ejemplo, XStream puede crear instancias de objetos que no tienen un constructor público sin argumentos, pero solo ejecutándose en un modo llamado "mejorado" que está disponible solo en ciertas VM. (Consulte el enlace para obtener más detalles). Los diseñadores de Hibernate seguramente eligieron mantener la compatibilidad con todas las máquinas virtuales y, por lo tanto, evitan tales trucos y utilizan el método de reflexión oficialmente admitido que Class<T>.newInstance()requiere un constructor sin argumentos.

mdma
fuente
31
FYI: el constructor no necesita ser público. Puede tener visibilidad de paquete e Hibernate debería setAccessible(true)tenerlo.
Grey
¿Puedo crear un UserType personalizado con un constructor no predeterminado para establecer un campo necesario para sus operaciones?
L-Samuels
1
Como referencia, ObjectInputStreamhace algo parecido sun.reflect.ReflectionFactory.getReflectionFactory().newConstructorForSerialization(classToGetInstanceOf, Object.class.getConstructor()).newInstance()a crear instancias de objetos sin un constructor predeterminado (JDK1.6 para Windows)
SamYonnou
re: It can have package visibility and Hibernate should setAccessible(true). ¿ itSignifica que la clase se instancia a través de la reflexión? Y que Hibernate should setAccessible(true)significa
Kevin Meredith
Objenesis hace esto y es ampliamente utilizado por muchos marcos como spring-data y mockito github.com/easymock/objenesis
ltfishie
46

Hibernate crea una instancia de sus objetos. Por lo tanto, debe poder instanciarlos. Si no hay un constructor sin argumentos , Hibernate no sabrá cómo instanciarlo, es decir, qué argumento pasar.

La documentación de hibernación dice:

4.1.1. Implementar un constructor sin argumentos

Todas las clases persistentes deben tener un constructor predeterminado (que puede ser no público) para que Hibernate pueda instanciarlas usando Constructor.newInstance(). Se recomienda que tenga un constructor predeterminado con al menos visibilidad de paquete para la generación de proxy en tiempo de ejecución en Hibernate.

Bozho
fuente
6
En cuanto a la visibilidad del constructor, si está utilizando JPA v2.0, observe que JSR-317 dice: El constructor no-arg debe ser público o estar protegido .
José Andias
@Bozho hola señor, tengo una duda de que si hibernar internamente use Constructor.newInstance () para instanciar el objeto, ¿cómo hibernar establecer valores en campos sin ningún setter definido?
Vikas Verma
No entiendo por qué veo esta advertencia para una subclase no privada @Embeddable con un constructor público sin
argumentos
Constructor.newInstance () toma argumentos, el problema (en realidad no es un problema) es mapear esos argumentos. No tengo idea de por qué Hibernate no resolvió este problema. A modo de comparación: la anotación @JsonCreator en Jackson hace esto, y se obtuvieron muchos beneficios de los objetos inmutables.
drrob
43

Erm, lo siento a todos, pero Hibernate no requiere que sus clases deban tener un constructor sin parámetros. La especificación JPA 2.0 requiere, y esto es muy poco convincente en nombre de JPA. Otros marcos como JAXB también lo requieren, lo que también es muy poco convincente en nombre de esos marcos.

(En realidad, JAXB supuestamente permite las fábricas de entidades, pero insiste en instanciar estas fábricas por sí misma, requiriendo que tengan un constructor sin parámetros , adivina qué , que en mi libro es exactamente tan bueno como no permitir fábricas; qué tonto es eso !)

Pero Hibernate no requiere tal cosa.

Hibernate soporta un mecanismo de interceptación (ver "Interceptor" en la documentación ) que le permite crear una instancia de sus objetos con cualquier parámetro de constructor que necesiten.

Básicamente, lo que haces es que cuando configuras hibernate le pasas un objeto que implementa la org.hibernate.Interceptorinterfaz, e hibernate invocará el instantiate()método de esa interfaz cada vez que necesite una nueva instancia de un objeto tuyo, por lo que tu implementación de ese método puede newsus objetos de la forma que desee.

Lo he hecho en un proyecto y funciona de maravilla. En este proyecto hago las cosas a través de JPA siempre que sea posible, y solo uso funciones de Hibernate como el interceptor cuando no tengo otra opción.

Hibernate parece estar algo inseguro al respecto, ya que durante el inicio emite un mensaje de información para cada una de mis clases de entidad, diciéndome INFO: HHH000182: No default (no-argument) constructor for classyclass must be instantiated by Interceptor , pero luego, más adelante, las instancia por interceptor, y está contento con eso.

Para responder a la parte del "por qué" de la pregunta para herramientas distintas de Hibernate , la respuesta es "sin ninguna buena razón", y esto está probado por la existencia del interceptor de hibernación. Hay muchas herramientas que podrían haber admitido algún mecanismo similar para la creación de instancias de objetos del cliente, pero no lo hacen, por lo que crean los objetos por sí mismos, por lo que deben requerir constructores sin parámetros. Me siento tentado a creer que esto está sucediendo porque los creadores de estas herramientas piensan en sí mismos como programadores de sistemas ninja que crean marcos llenos de magia para ser utilizados por programadores de aplicaciones ignorantes, quienes (eso creen) nunca en sus sueños más locos tendrían un necesidad de construcciones tan avanzadas como el ... Patrón de fábrica . (Bueno,pensar eso. En realidad, no lo creo. Estoy bromeando.)

Mike Nakis
fuente
1
¡Por fin alguien que lo entiende! He pasado más tiempo del que me gusta lidiando con estos marcos oscureciendo el proceso de creación de instancias de objetos (que es absolutamente crucial para la inyección de dependencia adecuada y para el comportamiento de objetos enriquecidos). Además, la reflexión de Java le permite crear objetos sin usar newInstance (). El método getDeclaredConstructors ha estado en la API de reflexión desde JDK 1.1. Es aterrador que los diseñadores de especificaciones de JPA hayan descuidado esto.
drrob
Esto está mal. Si Hibernate se usa como proveedor de JPA para la persistencia, requiere un constructor predeterminado; de lo contrario, se produce Caused by: org.hibernate.InstantiationException: No default constructor for entity: : hibernate.tutorial.Studentlo siguiente, que ocurrió recientemente porque javax.persistence.*;se utiliza y solo org.hibernateal crear elSession, SessionFactory, and Configuration
Mushy
2
@Mushy Esto es perfectamente correcto, porque a) la pregunta es sobre hibernación, sin una sola mención de JPA, yb) No obstante, menciono explícitamente en la segunda oración de mi respuesta que JPA requiere constructores predeterminados aunque hibernate no.
Mike Nakis
36

La hibernación es un marco ORM que admite la estrategia de acceso al campo o la propiedad. Sin embargo, no admite el mapeo basado en constructores, ¿tal vez lo que le gustaría? - debido a algunos problemas como

¿Qué pasa si tu clase contiene muchos constructores?

public class Person {

    private String name;
    private Integer age;

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

}

Como puede ver, se enfrenta a un problema de inconsistencia porque Hibernate no puede suponer qué constructor debería llamarse. Por ejemplo, suponga que necesita recuperar un objeto Person almacenado

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

¿Qué constructor debería llamar a Hibernate para recuperar un objeto Person? Puedes ver ?

Y finalmente, usando la reflexión, Hibernate puede instanciar una clase a través de su constructor no-arg. Así que cuando llamas

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

Hibernate instanciará su objeto Person de la siguiente manera

Person.class.newInstance();

Que según la documentación de la API

La clase se instancia como si fuera una nueva expresión con una lista de argumentos vacía

Moraleja de la historia

Person.class.newInstance();

es parecido a

new Person();

Nada más

Arthur Ronald
fuente
1
Esta es, con mucho, la descripción más excelente que he encontrado con respecto a esta cuestión. La mayoría de las respuestas que encontré utilizaban términos técnicos librescos y ningún organismo lo explicó de una manera flexible como tú. ¡Felicitaciones y gracias!
The Dark Knight
1
Este bien podría ser el razonamiento del equipo de Hibernate. Pero en realidad, los problemas podrían resolverse (1) requiriendo una anotación o solo usando un constructor no predeterminado si solo hay un constructor y (2) usando class.getDeclaredConstructors. Y usando Constructor.newInstance () en lugar de Class.newInstance (). Se necesitaría un mapeo apropiado en XML / anotaciones, antes de Java 8, pero es completamente factible.
drrob
Ok, entonces hibernate crea un objeto desde el constructor predeterminado, y luego usa setters para campos namey age? Si no, ¿usa otro constructor más tarde?
tryHard
2
@tryingHard Sí, una vez instanciado, Hibernate usa los establecedores o campos - Depende de la estrategia de acceso. De forma predeterminada, la ubicación de la anotación Id proporciona la estrategia de acceso predeterminada. Ver docs.jboss.org/hibernate/orm/5.1/userguide/html_single/chapters/…
Arthur Ronald
6

En realidad, puede crear instancias de clases que no tienen constructor 0-args; puede obtener una lista de los constructores de una clase, elegir uno e invocarlo con parámetros falsos.

Si bien esto es posible, y supongo que funcionaría y no sería problemático, tendrás que aceptar que es bastante extraño.

Construir objetos de la forma en que lo hace Hibernate (creo que invoca el constructor de 0-arg y luego probablemente modifica los campos de la instancia directamente a través de Reflection. Quizás sepa cómo llamar a los setters) va un poco en contra de cómo se supone que se construye un objeto en Java: invoca al constructor con los parámetros adecuados para que el nuevo objeto sea el objeto que deseas. Creo que instanciar un objeto y luego mutarlo es algo "anti-Java" (o yo diría, anti Java teórico puro) - y definitivamente, si lo haces a través de la manipulación de campo directo, se encapsula y todo ese material de encapsulación elegante .

Creo que la forma correcta de hacer esto sería definir en el mapeo de Hibernate cómo se debe crear una instancia de un objeto a partir de la información en la fila de la base de datos usando el constructor adecuado ... pero esto sería más complejo, lo que significa que ambos Hibernate serían pares más complejo, el mapeo sería más complejo ... y todo más "puro"; y no creo que esto tenga una ventaja sobre el enfoque actual (aparte de sentirse bien haciendo las cosas "de la manera correcta").

Dicho esto, y viendo que el enfoque de Hibernate no es muy "limpio", la obligación de tener un constructor de 0-arg no es estrictamente necesaria, pero puedo entender un poco el requisito, aunque creo que lo hicieron de manera puramente "adecuada "motivos, cuando se desviaron del" camino correcto "(aunque por razones razonables) mucho antes de eso.

alex
fuente
5

Hibernate necesita crear instancias como resultado de sus consultas (a través de la reflexión), Hibernate se basa en el constructor de entidades sin argumentos para eso, por lo que debe proporcionar un constructor sin argumentos. ¿Qué no está claro?

Pascal Thivent
fuente
¿En qué condiciones es privateincorrecto un constructor? Estoy viendo java.lang.InstantiationExceptionincluso con un privateconstructor para mi entidad JPA. referencia .
Kevin Meredith
Probé la clase sin el constructor vacío (pero con el constructor args) y funcionó. Obtuve INFO de hibernate "INFO: HHH000182: Ningún constructor predeterminado (sin argumentos) para la clase y la clase debe ser instanciado por Interceptor", pero no hubo excepción y el objeto se recibió con éxito desde DB.
Presa lijep
2

Es mucho más fácil crear un objeto con un constructor sin parámetros a través de la reflexión y luego llenar sus propiedades con datos a través de la reflexión, que intentar hacer coincidir los datos con los parámetros arbitrarios de un constructor parametrizado, con cambios de nombres / conflictos de nombres, lógica no definida dentro del constructor, conjuntos de parámetros que no coinciden con las propiedades de un objeto, etcétera.

Muchos ORM y serializadores requieren constructores sin parámetros, porque los constructores paramterizados mediante reflexión son muy frágiles, y los constructores sin parámetros brindan estabilidad a la aplicación y control sobre el comportamiento del objeto al desarrollador.

Kaerber
fuente
Yo diría que forzar la mutabilidad completa en lo que puede necesitar ser un objeto de dominio rico es aún más frágil (no es mucho de un ORM si sus entidades necesitan ser bolsas de datos sin características para funcionar; estoy aquí porque quiero un constructor pero en su lugar tienen un orden indefinido de llamadas de establecedores ricos) ... Pero +1 porque reconoce que la reflexión se puede realizar en constructores con
argumentos
2

Hibernate usa proxies para la carga diferida. Si no define un constructor o lo hace privado, es posible que algunas cosas sigan funcionando, las que no dependen del mecanismo de proxy. Por ejemplo, cargar el objeto (sin constructor) directamente usando la API de consulta.

Pero, si usa el método session.load (), se enfrentará a InstantiationException de la biblioteca del generador de proxy debido a la no disponibilidad del constructor.

Este chico informó una situación similar:

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

haps10
fuente
0

Consulte esta sección de la especificación del lenguaje Java que explica la diferencia entre clases internas estáticas y no estáticas: http://java.sun.com/docs/books/jls/third_edition/html/classes.html#8.1.3

Una clase interna estática no es conceptualmente diferente a una clase general normal declarada en un archivo .java.

Dado que Hibernate necesita crear una instancia de ProjectPK independientemente de la instancia de Project, ProjectPK debe ser una clase interna estática o declararse en su propio archivo .java.

referencia org.hibernate.InstantiationException: No hay constructor predeterminado

Amitābha
fuente