Por que o Hibernate não requer nenhum construtor de argumento?

103

O construtor sem argumento é um requisito (ferramentas como o Hibernate usam reflexão neste construtor para instanciar objetos).

Recebi uma resposta ondulada, mas alguém poderia explicar melhor? obrigado

unj2
fonte
7
Para sua informação: a afirmação de que The no-argument constructor is a requirement está errada e todas as respostas que explicam por que isso é assim, sem questionar se é de fato assim, (incluindo a resposta aceita, que recebeu até recompensa) estão erradas . Veja esta resposta: stackoverflow.com/a/29433238/773113
Mike Nakis
2
É necessário se você estiver usando o hibernate como um provedor de JPA.
Amalgovinus
1
@MikeNakis Você está incorreto Mike. O Hibernate requer um construtor padrão para instanciar objetos se você estiver usando o hibernate como um provedor para JPA (Amalgovinus), caso contrário, o Hibernate irá relatar Caused by: org.hibernate.InstantiationException: No default constructor for entity: : hibernate.tutorial.Studentcomo no caso que eu acabei de encontrar
Mushy
@Mushy, a questão está marcada com "hibernate" e "orm", não com "jpa". Não há menção de JPA na pergunta.
Mike Nakis
1
@MikeNakis Concordo Mike, mas o Hibernate é usado como uma implementação de "JPA" e não é usado na ausência de "JPA" ou "ORM". Portanto, supõe-se que o hibernate está implementando "JPA".
Mushy

Respostas:

138

O Hibernate e o código em geral que cria objetos via reflexão usam Class<T>.newInstance()para criar uma nova instância de suas classes. Este método requer um construtor público sem argumentos para poder instanciar o objeto. Para a maioria dos casos de uso, fornecer um construtor sem argumentos não é um problema.

Existem hacks baseados na serialização que podem contornar a falta de um construtor no-arg, já que a serialização usa a magia jvm para criar objetos sem invocar o construtor. Mas isso não está disponível em todas as VMs. Por exemplo, o XStream pode criar instâncias de objetos que não têm um construtor não-arg público, mas apenas executando em um modo chamado "aprimorado", que está disponível apenas em certas VMs. (Veja o link para detalhes.) Os designers do Hibernate certamente escolheram manter a compatibilidade com todas as VMs e, portanto, evitam tais truques e usam o método de reflexão oficialmente suportado que Class<T>.newInstance()requer um construtor sem arg.

mdma
fonte
31
FYI: O construtor não precisa ser público. Ele pode ter visibilidade de pacote e o Hibernate deve estar setAccessible(true)nele.
Gray
Posso criar um UserType personalizado com um construtor não padrão para definir um campo necessário para suas operações.
L-Samuels
1
Para referência, ObjectInputStreamfaz algo sun.reflect.ReflectionFactory.getReflectionFactory().newConstructorForSerialization(classToGetInstanceOf, Object.class.getConstructor()).newInstance()semelhante a instanciar objetos sem construtor padrão (JDK1.6 para Windows)
SamYonnou
re: It can have package visibility and Hibernate should setAccessible(true). Será que itsignifica o ser classe instanciado via reflexão? E o que isso Hibernate should setAccessible(true)significa?
Kevin Meredith
Objenesis faz isso e é amplamente usado por muitos frameworks como spring-data e mockito github.com/easymock/objenesis
ltfishie
46

O Hibernate instancia seus objetos. Portanto, ele precisa ser capaz de instanciá-los. Se não houver um construtor sem arg, o Hibernate não saberá como instanciá-lo, isto é, qual argumento passar.

A documentação de hibernação diz:

4.1.1. Implementar um construtor sem argumento

Todas as classes persistentes devem ter um construtor padrão (que pode ser não público) para que o Hibernate possa instanciá-las usando Constructor.newInstance(). É recomendado que você tenha um construtor padrão com pelo menos visibilidade de pacote para geração de proxy em tempo de execução no Hibernate.

Bozho
fonte
6
Quanto à visibilidade do construtor, se você estiver usando JPA v2.0, observe que o JSR-317 diz: O construtor no-arg deve ser público ou protegido .
José Andias
@Bozho olá senhor, tenho uma dúvida que se hibernar internamente use Constructor.newInstance () para instanciar o objeto, então como hibernar define valores em campos sem nenhum setter definido?
Vikas Verma
Não entendo por que estou vendo este aviso para uma subclasse não privada @Embeddable com um construtor não-arg público ...
Amalgovinus
Constructor.newInstance () aceita argumentos, o problema (na verdade um não-problema) é mapear esses argumentos. Não faço ideia por que a hibernação não resolveu esse problema. Para comparação: a anotação @JsonCreator em Jackson faz isso, e muitos benefícios dos objetos imutáveis ​​foram obtidos.
drrob de
43

Erm, desculpe a todos, mas o Hibernate não requer que suas classes tenham um construtor sem parâmetros. A especificação JPA 2.0 exige isso, e isso é muito ruim em nome do JPA. Outras estruturas, como JAXB, também exigem isso, o que também é muito ruim em nome dessas estruturas.

(Na verdade, JAXB supostamente permite fábricas de entidades, mas insiste em instanciar essas fábricas por si só, exigindo que elas tenham um construtor - adivinhe - sem parâmetros , que em meu livro é exatamente tão bom quanto não permitir fábricas; quão ruim é isso !)

Mas o Hibernate não exige tal coisa.

O Hibernate suporta um mecanismo de interceptação, (veja "Interceptor" na documentação ), que permite a você instanciar seus objetos com quaisquer parâmetros de construtor que eles precisem.

Basicamente, o que você faz é que quando você configura o hibernate você passa um objeto que implementa a org.hibernate.Interceptorinterface, e o hibernate irá então invocar o instantiate()método daquela interface sempre que precisar de uma nova instância de um objeto seu, então a implementação desse método pode newseus objetos da maneira que quiser.

Eu fiz isso em um projeto e funciona como um encanto. Neste projeto, faço as coisas via JPA sempre que possível e só uso recursos do Hibernate, como o interceptor, quando não tenho outra opção.

O Hibernate parece um tanto inseguro quanto a isso, já que durante a inicialização ele emite uma mensagem de informação para cada uma das minhas classes de entidade, dizendo-me INFO: HHH000182: No default (no-argument) constructor for classe class must be instantiated by Interceptor, mas depois eu as instancio por interceptor, e ele está feliz com isso.

Para responder ao "por que" parte da pergunta para outras ferramentas além do Hibernate , a resposta é "sem nenhuma boa razão", e isso é comprovado pela existência do interceptor do Hibernate. Existem muitas ferramentas por aí que poderiam oferecer suporte a algum mecanismo semelhante para a instanciação de objetos do cliente, mas não suportam, portanto, eles criam os objetos por si próprios, portanto, precisam exigir construtores sem parâmetros. Estou tentado a acreditar que isso está acontecendo porque os criadores dessas ferramentas pensam em si mesmos como programadores de sistemas ninja que criam frameworks cheios de magia para serem usados ​​por programadores de aplicativos ignorantes, que (assim eles acham) nunca teriam em seus sonhos mais selvagens um necessidade de construções avançadas como o ... Padrão de fábrica . (OK,pensar assim. Eu realmente não penso assim. Estou brincando.)

Mike Nakis
fonte
1
Finalmente, alguém que entende! Passei mais tempo do que gostaria lidando com essas estruturas que obscurecem o processo de instanciação de objetos (o que é absolutamente crucial para a injeção de dependência adequada e para um comportamento rico de objetos). Além disso, a reflexão Java permite criar objetos sem usar newInstance (). O método getDeclaredConstructors está na API de reflexão desde o JDK 1.1. É assustador que os designers de especificações JPA tenham negligenciado isso.
drrob de
Isto está errado. Se o Hibernate for usado como o provedor JPA para persistência, ele requer um construtor padrão, caso contrário, o seguinte ocorre Caused by: org.hibernate.InstantiationException: No default constructor for entity: : hibernate.tutorial.Studentrecentemente porque javax.persistence.*;é utilizado e somente org.hibernateao criar oSession, SessionFactory, and Configuration
Mushy
2
@Mushy Isso é perfeitamente correto, porque a) a pergunta é sobre hibernação, sem uma única menção de JPA, eb) eu menciono explicitamente na segunda frase de minha resposta que JPA requer construtores padrão, embora o hibernação não exija.
Mike Nakis
36

O hibernate é uma estrutura ORM que suporta estratégia de acesso a campo ou propriedade. No entanto, ele não suporta mapeamento baseado em construtor - talvez o que você gostaria? - por causa de alguns problemas como

O que acontece se sua classe contiver muitos construtores

public class Person {

    private String name;
    private Integer age;

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

}

Como você pode ver, você lida com um problema de inconsistência porque o Hibernate não pode supor que construtor deve ser chamado. Por exemplo, suponha que você precise recuperar um objeto Person armazenado

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

Qual construtor o Hibernate deve chamar para recuperar um objeto Person? Você pode ver ?

E finalmente, usando reflexão, o Hibernate pode instanciar uma classe através de seu construtor sem arg. Então, quando você liga

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

O Hibernate irá instanciar seu objeto Person como segue

Person.class.newInstance();

Que de acordo com a documentação da API

A classe é instanciada como se por uma nova expressão com uma lista de argumentos vazia

Moral da história

Person.class.newInstance();

é similar a

new Person();

Nada mais

Arthur Ronald
fonte
1
Esta é de longe a descrição mais excelente que encontrei a respeito dessa questão. A maioria das respostas que encontrei usava termos técnicos livrescos e nenhum corpo explicou de maneira flexível como você. Parabéns para você e obrigado!
The Dark Knight
1
Este pode muito bem ser o raciocínio da equipe do Hibernate. Mas, na verdade, os problemas poderiam ser resolvidos (1) exigindo uma anotação ou então usando apenas um construtor não padrão se houver apenas um construtor e (2) usando class.getDeclaredConstructors. E usando Constructor.newInstance () em vez de Class.newInstance (). O mapeamento apropriado em XML / anotações seria necessário, antes do Java 8, mas é totalmente factível.
drrob de
Ok, então o hibernate cria o objeto do construtor padrão e então usa setters para os campos namee age? Se não, ele usa outro construtor depois?
tentando difícil,
2
@tryingHard Sim, uma vez instanciado, o Hibernate usa os setters ou campos - Depende da estratégia de acesso. Por padrão, o posicionamento da anotação Id fornece a estratégia de acesso padrão. Consulte docs.jboss.org/hibernate/orm/5.1/userguide/html_single/chapters/…
Arthur Ronald
6

Na verdade, você pode instanciar classes que não têm construtor 0-args; você pode obter uma lista dos construtores de uma classe, escolher um e invocá-lo com parâmetros falsos.

Embora isso seja possível, e eu acho que funcionaria e não seria problemático, você terá que concordar que é muito estranho.

Construir objetos da maneira que o Hibernate faz (eu acredito que ele invoca o construtor 0-arg e então provavelmente modifica os campos da instância diretamente via Reflection. Talvez ele saiba como chamar setters) vai um pouco contra como um objeto deve ser construído em Java- invoque o construtor com os parâmetros apropriados para que o novo objeto seja o objeto que você deseja. Eu acredito que instanciar um objeto e então transformá-lo é um pouco "anti-Java" (ou eu diria, anti-Java teórico puro) - e definitivamente, se você fizer isso por meio de manipulação direta de campo, haverá encapsulamento e todo aquele encapsulamento extravagante .

Eu acho que a maneira correta de fazer isso seria definir no mapeamento do Hibernate como um objeto deve ser instanciado a partir das informações na linha do banco de dados usando o construtor adequado ... mas isso seria mais complexo - o que significa que ambos os Hibernate seriam iguais mais complexo, o mapeamento ficaria mais complexo ... e tudo para ser mais "puro"; e não acho que isso teria uma vantagem sobre a abordagem atual (além de se sentir bem em fazer as coisas "da maneira certa").

Dito isso, e vendo que a abordagem do Hibernate não é muito "limpa", a obrigação de ter um construtor 0-arg não é estritamente necessária, mas posso entender um pouco o requisito, embora acredite que eles fizeram isso de maneira puramente "adequada "motivos, quando eles se desviaram do" caminho adequado "(embora por razões razoáveis) muito antes disso.

alex
fonte
5

O Hibernate precisa criar instâncias como resultado de suas consultas (via reflexão), o Hibernate depende do construtor no-arg de entidades para isso, então você precisa fornecer um construtor no-arg. O que não está claro?

Pascal Thivent
fonte
Em que condições um privateconstrutor está incorreto? Estou vendo java.lang.InstantiationExceptionaté com um privateconstrutor para minha entidade JPA. referência .
Kevin Meredith
Eu tentei classe sem construtor vazio (mas com construtor args) e funcionou. Recebi INFO do hibernate "INFO: HHH000182: Nenhum construtor padrão (sem argumento) para a classe e a classe deve ser instanciada pelo Interceptor", mas não houve exceção e o objeto foi recebido com sucesso do DB.
Barragem de lijep
2

É muito mais fácil criar um objeto com um construtor sem parâmetros por meio de reflexão e, em seguida, preencher suas propriedades com dados por meio de reflexão, do que tentar combinar os dados com parâmetros arbitrários de um construtor parametrizado, com mudanças de nomes / conflitos de nomenclatura, lógica indefinida dentro do construtor, parâmetro define propriedades não correspondentes de um objeto, etc.

Muitos ORMs e serializadores exigem construtores sem parâmetros, porque os construtores paramterizados por meio de reflexão são muito frágeis e os construtores sem parâmetros fornecem estabilidade ao aplicativo e controle sobre o comportamento do objeto para o desenvolvedor.

Kaerber
fonte
Eu diria que forçar a mutabilidade completa no que pode precisar ser um objeto de domínio rico é ainda mais frágil (não é muito de um ORM se suas entidades precisam ser pacotes de dados sem características para funcionar - estou aqui porque quero um construtor, mas em vez disso tem uma ordem indefinida de chamadas de setter rico) ... Mas +1 porque você reconhece que a reflexão pode ser realizada em construtores com args :)
drrob
2

O Hibernate usa proxies para carregamento lento. Se você não definir um construtor ou torná-lo privado, algumas coisas ainda podem funcionar - aquelas que não dependem do mecanismo de proxy. Por exemplo, carregar o objeto (sem construtor) diretamente usando a API de consulta.

Mas, se você usar o método session.load (), você enfrentará InstantiationException do proxy generator lib devido à indisponibilidade do construtor.

Esse cara relatou uma situação semelhante:

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

haps10
fonte
0

Confira esta seção das especificações da linguagem Java que explica a diferença entre classes internas estáticas e não estáticas: http://java.sun.com/docs/books/jls/third_edition/html/classes.html#8.1.3

Uma classe interna estática não é conceitualmente diferente de uma classe geral regular declarada em um arquivo .java.

Uma vez que o Hibernate precisa instanciar o ProjectPK independentemente da instância do Project, o ProjectPK precisa ser uma classe interna estática ou declarado em seu próprio arquivo .java.

reference org.hibernate.InstantiationException: Nenhum construtor padrão

Amitābha
fonte