Dlaczego Hibernate nie wymaga konstruktora argumentów?

105

Konstruktor bez argumentów jest wymagany (narzędzia takie jak Hibernate używają refleksji na tym konstruktorze do tworzenia instancji obiektów).

Otrzymałem tę falującą odpowiedź, ale czy ktoś mógłby wyjaśnić dalej? Dzięki

unj2
źródło
7
FYI: twierdzenie, które The no-argument constructor is a requirement jest błędne , i wszystkie odpowiedzi, które prowadzą do wyjaśnienia, dlaczego tak jest, bez kwestionowania, czy tak jest w rzeczywistości (w tym zaakceptowana odpowiedź, która nawet otrzymała nagrodę) są błędne . Zobacz tę odpowiedź: stackoverflow.com/a/29433238/773113
Mike Nakis
2
Jest to wymagane, jeśli używasz hibernacji jako dostawcy JPA.
Amalgovinus
1
@MikeNakis Nie masz racji Mike. Hibernacja wymaga domyślnego konstruktora do tworzenia instancji obiektów, jeśli używasz hibernacji jako dostawcy dla JPA (Amalgovinus), w przeciwnym razie Hibernate zgłosi, Caused by: org.hibernate.InstantiationException: No default constructor for entity: : hibernate.tutorial.Studentjak w przypadku, w którym właśnie się zetknąłem
Mushy
@Mushy pytanie jest oznaczone tagiem „hibernacja” i „orm”, a nie „jpa”. W pytaniu nie ma wzmianki o WZP.
Mike Nakis
1
@MikeNakis Zgadzam się Mike, ale Hibernate jest używany jako implementacja „JPA” i nie jest używany w przypadku braku „JPA” lub „ORM”. Dlatego zakłada się, że hibernacja polega na implementacji "JPA".
Mushy

Odpowiedzi:

138

Hibernate i ogólnie kod, który tworzy obiekty za pomocą odbicia, Class<T>.newInstance()aby utworzyć nowe wystąpienie klas. Ta metoda wymaga publicznego konstruktora no-arg, aby móc utworzyć wystąpienie obiektu. W większości przypadków użycie konstruktora bez argumentów nie stanowi problemu.

Istnieją hacki oparte na serializacji, które mogą obejść brak konstruktora bez arg, ponieważ serializacja używa magii jvm do tworzenia obiektów bez wywoływania konstruktora. Ale nie jest to dostępne we wszystkich maszynach wirtualnych. Na przykład XStream może tworzyć instancje obiektów, które nie mają publicznego konstruktora bez argonu, ale tylko działając w tak zwanym trybie „rozszerzonym”, który jest dostępny tylko na niektórych maszynach wirtualnych. (Zobacz łącze, aby uzyskać szczegółowe informacje). Projektanci Hibernate z pewnością zdecydowali się zachować zgodność ze wszystkimi maszynami wirtualnymi, unikając w ten sposób takich sztuczek i używają oficjalnie obsługiwanej metody odbicia Class<T>.newInstance()wymagającej konstruktora bez argonu.

mdma
źródło
31
FYI: Konstruktor nie musi być publiczny. Może mieć widoczność pakietu, a Hibernacja powinna setAccessible(true)na nim być.
Gray
Czy mogę utworzyć niestandardowy typ użytkownika z innym niż domyślny konstruktor w celu ustawienia potrzebnego pola dla jego operacji.
L-Samuels
1
Dla odniesienia ObjectInputStreamrobi coś podobnego sun.reflect.ReflectionFactory.getReflectionFactory().newConstructorForSerialization(classToGetInstanceOf, Object.class.getConstructor()).newInstance()do tworzenia instancji obiektów bez domyślnego konstruktora (JDK1.6 dla Windows)
SamYonnou
re: It can have package visibility and Hibernate should setAccessible(true). Czy itoznacza, że ​​klasa jest tworzona przez odbicie? A co to Hibernate should setAccessible(true)znaczy?
Kevin Meredith
Objenesis to robi i jest szeroko stosowany w wielu frameworkach, takich jak spring-data i mockito github.com/easymock/objenesis
ltfishie.
47

Hibernate tworzy instancje obiektów. Dlatego musi mieć możliwość ich utworzenia. Jeśli nie ma konstruktora bez argumentów, Hibernate nie będzie wiedział, jak go utworzyć, tj. Jaki argument przekazać.

Dokumentacja dotycząca hibernacji mówi:

4.1.1. Zaimplementuj konstruktora bez argumentów

Wszystkie trwałe klasy muszą mieć domyślny konstruktor (który może być niepubliczny), aby Hibernate mógł utworzyć ich instancję przy użyciu Constructor.newInstance(). Zaleca się posiadanie domyślnego konstruktora z widocznością przynajmniej pakietu do generowania proxy w czasie wykonywania w Hibernate.

Bozho
źródło
6
Jeśli chodzi o widoczność konstruktora, jeśli używasz JPA v2.0, zauważ, że JSR-317 mówi: Konstruktor no-arg musi być publiczny lub chroniony .
José Andias
@Bozho witaj panie, mam jedną wątpliwość, że jeśli wewnętrznie hibernujesz, użyj Constructor.newInstance () do utworzenia instancji obiektu, to jak hibernować ustawiać wartości w pola bez zdefiniowanych ustawników?
Vikas Verma
Nie rozumiem, dlaczego widzę to ostrzeżenie dla nieprywatnej podklasy @Embeddable z publicznym konstruktorem bez argumentów ...
argonu
Constructor.newInstance () przyjmuje argumenty, problem (właściwie nie jest problemem) mapuje te argumenty. Nie mam pojęcia, dlaczego hibernacja nie rozwiązała tego problemu. Dla porównania: adnotacja @JsonCreator w Jackson robi to, i miała wiele korzyści z niezmiennych obiektów.
drrob
44

Przepraszam wszystkich, ale Hibernate nie wymaga, aby twoje klasy miały konstruktora bez parametrów. Specyfikacji JPA 2.0 wymaga, a to jest bardzo kulawy w imieniu WZP. Inne frameworki, takie jak JAXB, również tego wymagają, co jest również bardzo kiepskie w imieniu tych frameworków.

(Właściwie JAXB przypuszczalnie zezwala na fabryki jednostek, ale nalega na samodzielne tworzenie instancji tych fabryk, wymagając od nich - zgadnijcie - konstruktora bez parametrów , co w mojej książce jest dokładnie tak samo dobre, jak nie zezwalanie na fabryki; jakie to kiepskie !)

Ale Hibernate nie wymaga tego.

Hibernate obsługuje mechanizm przechwytywania (patrz „Interceptor” w dokumentacji ), który umożliwia tworzenie instancji obiektów z dowolnymi parametrami konstruktora, których potrzebują.

Zasadniczo to, co robisz, to to, że po skonfigurowaniu hibernacji przekazujesz mu obiekt implementujący org.hibernate.Interceptorinterfejs, a hibernacja będzie wtedy wywoływać instantiate()metodę tego interfejsu, gdy będzie potrzebować nowej instancji twojego obiektu, więc twoja implementacja tej metody może newswoje przedmioty w dowolny sposób.

Zrobiłem to w projekcie i działa jak urok. W tym projekcie robię rzeczy przez JPA, kiedy tylko jest to możliwe, i używam funkcji Hibernacji, takich jak przechwytywacz, tylko wtedy, gdy nie mam innej opcji.

Wydaje się, że Hibernate jest nieco niepewny, ponieważ podczas uruchamiania wyświetla komunikat informacyjny dla każdej z moich klas encji, informując mnie INFO: HHH000182: No default (no-argument) constructor for classiclass must be instantiated by Interceptor , ale później mam instancję je kolektora i jest zadowolony z tego.

Aby odpowiedzieć na pytanie „dlaczego”, część pytania o narzędzia faktycznie tak myślę. Żartuję.) innych niż Hibernate , odpowiedź brzmi „bez absolutnie żadnego powodu”, a potwierdza to istnienie przechwytywacza hibernacji. Istnieje wiele narzędzi, które mogłyby obsługiwać podobny mechanizm tworzenia instancji obiektu klienta, ale tak nie jest, więc tworzą one obiekty samodzielnie, więc muszą wymagać konstruktorów bez parametrów. Kusi mnie, by uwierzyć, że dzieje się tak, ponieważ twórcy tych narzędzi myślą o sobie jako o programistach systemów ninja, którzy tworzą frameworki pełne magii, do wykorzystania przez nieświadomych programistów aplikacji, którzy (tak im się wydaje) nigdy w swoich najśmielszych marzeniach nigdy nie mieliby potrzeba tak zaawansowanych konstrukcji, jak ... Wzorzec fabryczny, . (Okej, jestemtak myśleć. Nie

Mike Nakis
źródło
1
Wreszcie ktoś, kto to rozumie! Spędziłem więcej czasu niż lubię zajmować się tymi frameworkami, zasłaniając proces tworzenia instancji obiektów (co jest absolutnie kluczowe dla prawidłowego wstrzykiwania zależności i dla bogatego zachowania obiektów). Ponadto odbicie w Javie pozwala na tworzenie obiektów bez użycia newInstance (). Metoda getDeclaredConstructors znajduje się w interfejsie API odbicia od wersji JDK 1.1. To przerażające, że projektanci specyfikacji JPA zaniedbali to.
drrob
To jest źle. Jeśli Hibernate jest używany jako dostawca JPA dla trwałości, wymaga on domyślnego konstruktora, w przeciwnym razie następuje następujące, Caused by: org.hibernate.InstantiationException: No default constructor for entity: : hibernate.tutorial.Studentktóre ostatnio wystąpiło, ponieważ javax.persistence.*;jest używane i tylko org.hibernatepodczas tworzeniaSession, SessionFactory, and Configuration
Mushy
2
@Mushy Jest to całkowicie poprawne, ponieważ a) pytanie dotyczy hibernacji, bez ani jednej wzmianki o JPA, oraz b) w drugim zdaniu mojej odpowiedzi wyraźnie wspominam, że JPA wymaga domyślnych konstruktorów, mimo że hibernacja nie wymaga.
Mike Nakis
36

Hibernacja to struktura ORM, która obsługuje strategię dostępu do pól lub właściwości. Jednak nie obsługuje mapowania opartego na konstruktorze - może co byś chciał? - z powodu pewnych problemów, takich jak

Co się stanie, jeśli Twoja klasa zawiera dużo konstruktorów

public class Person {

    private String name;
    private Integer age;

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

}

Jak widać, mamy do czynienia z problemem niespójności, ponieważ Hibernate nie może przewidzieć, który konstruktor powinien zostać wywołany. Na przykład załóżmy, że musisz pobrać przechowywany obiekt Person

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

Który konstruktor powinien wywołać Hibernate, aby pobrać obiekt Person? Czy widzisz ?

I wreszcie, używając refleksji, Hibernate może utworzyć instancję klasy poprzez jej konstruktor bezargumentowy. Więc kiedy dzwonisz

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

Hibernate utworzy wystąpienie obiektu Person w następujący sposób

Person.class.newInstance();

Które zgodnie z dokumentacją API

Instancja klasy jest tworzona tak, jakby za pomocą nowego wyrażenia z pustą listą argumentów

Morał historii

Person.class.newInstance();

jest podobne do

new Person();

Nic więcej

Arthur Ronald
źródło
1
Jest to zdecydowanie najdoskonalszy opis, jaki znalazłem w odniesieniu do tego pytania. Większość odpowiedzi, które znalazłem, zawierało książkowe terminy techniczne i żaden organ nie wyjaśnił tego tak elastycznie, jak ty. Uznanie i dzięki!
The Dark Knight
1
Może to być rozumowanie zespołu Hibernate. Ale w rzeczywistości problemy można rozwiązać przez (1) wymaganie albo adnotacji, albo tylko użycie konstruktora innego niż domyślny, jeśli jest tylko jeden konstruktor i (2) użycie class.getDeclaredConstructors. I używając Constructor.newInstance () zamiast Class.newInstance (). Potrzebne byłoby odpowiednie mapowanie w XML / adnotacjach przed Java 8, ale jest to całkowicie wykonalne.
drrob
Ok, więc hibernacja tworzy obiekt z domyślnego konstruktora, a następnie używa seterów dla pól namei age? Jeśli nie, to później użyje innego konstruktora?
próbowanie Hard
2
@tryingHard Tak, po utworzeniu instancji Hibernate używa seterów lub pól - zależy to od strategii dostępu. Domyślnie umieszczenie adnotacji Id zapewnia domyślną strategię dostępu. Zobacz docs.jboss.org/hibernate/orm/5.1/userguide/html_single/chapters/…
Arthur Ronald
6

Właściwie można tworzyć instancje klas, które nie mają konstruktora 0-args; możesz uzyskać listę konstruktorów klasy, wybrać jeden i wywołać go z fałszywymi parametrami.

Chociaż jest to możliwe i myślę, że zadziałałoby i nie byłoby problematyczne, musisz się zgodzić, że jest to dość dziwne.

Konstruowanie obiektów w sposób, w jaki robi to Hibernate (wydaje mi się, że wywołuje konstruktor 0-arg, a następnie prawdopodobnie modyfikuje pola instancji bezpośrednio przez Reflection. Być może wie, jak wywoływać metody ustawiające) jest trochę sprzeczne z tym, jak obiekt ma być zbudowany w Java - wywołaj konstruktora z odpowiednimi parametrami, aby nowy obiekt był tym, którego chcesz. Uważam, że utworzenie instancji obiektu, a następnie jego mutacja jest w pewnym sensie „anty-Java” (lub powiedziałbym, anty-czysto teoretyczna Java) - i zdecydowanie, jeśli zrobisz to przez bezpośrednią manipulację polem, nastąpi hermetyzacja i wszystkie te fantazyjne elementy enkapsulacji .

Myślę, że właściwym sposobem na zrobienie tego byłoby zdefiniowanie w mapowaniu Hibernate, w jaki sposób obiekt powinien być utworzony na podstawie informacji w wierszu bazy danych przy użyciu odpowiedniego konstruktora ... ale byłoby to bardziej złożone - co oznacza, że ​​oba Hibernate byłyby równe bardziej złożone, mapowanie byłoby bardziej złożone ... a wszystko po to, by być bardziej „czystym”; i nie sądzę, żeby to miało przewagę nad obecnym podejściem (poza poczuciem zadowolenia z robienia rzeczy „we właściwy sposób”).

Powiedziawszy to i widząc, że podejście Hibernate nie jest zbyt „czyste”, obowiązek posiadania konstruktora 0-argowego nie jest bezwzględnie konieczny, ale mogę nieco zrozumieć ten wymóg, chociaż uważam, że zrobili to w czysto „właściwy sposób” „powodów, kiedy zboczyli z„ właściwej drogi ”(aczkolwiek z uzasadnionych powodów) dużo wcześniej.

Alex
źródło
5

Hibernate musi tworzyć instancje w wyniku twoich zapytań (przez odbicie), Hibernate opiera się na konstruktorze encji bez argonu, więc musisz zapewnić konstruktor bez argonu. Co nie jest jasne?

Pascal Thivent
źródło
W jakich warunkach privatekonstruktor jest nieprawidłowy? Widzę java.lang.InstantiationExceptionnawet z privatekonstruktorem dla mojej jednostki JPA. odniesienie .
Kevin Meredith
Próbowałem klasy bez pustego konstruktora (ale z konstruktorem args) i zadziałało. Otrzymałem INFO z hibernacji „INFO: HHH000182: Żaden domyślny (bezargumentowy) konstruktor dla klasy i klasy nie musi być utworzony przez Interceptor”, ale nie wystąpił wyjątek i obiekt został pomyślnie odebrany z bazy danych.
zapora lijep
2

Znacznie łatwiej jest stworzyć obiekt z konstruktorem bez parametrów poprzez odbicie, a następnie wypełnić jego właściwości danymi poprzez odbicie, niż próbować dopasować dane do dowolnych parametrów konstruktora sparametryzowanego, ze zmieniającymi się nazwami / konfliktami nazw, niezdefiniowaną logiką wewnątrz konstruktora, zestawy parametrów nie pasują do właściwości obiektu i tak dalej.

Wiele ORM i serializatorów wymaga konstruktorów bez parametrów, ponieważ konstruktory sparametryzowane przez odbicie są bardzo delikatne, a konstruktory bez parametrów zapewniają zarówno stabilność aplikacji, jak i kontrolę nad zachowaniem obiektu dla dewelopera.

Kaerber
źródło
Twierdziłbym, że wymuszanie całkowitej zmienności na tym, co może potrzebować być bogatym obiektem domeny, jest jeszcze bardziej kruche (nie jest to dużo ORM, jeśli twoje byty muszą być pozbawione cech, aby działać konstruktor, ale zamiast tego mają niezdefiniowaną kolejność wywołań bogatych seterów) ... Ale +1, ponieważ
uznajesz,
2

Hibernate wykorzystuje serwery proxy do leniwego ładowania. Jeśli nie zdefiniujesz konstruktora lub nie ustawisz go jako prywatnego, kilka rzeczy może nadal działać - te, które nie są zależne od mechanizmu proxy. Na przykład ładowanie obiektu (bez konstruktora) bezpośrednio przy użyciu interfejsu API zapytań.

Ale jeśli użyjesz metody session.load (), napotkasz wyjątek InstantiationException z biblioteki generatora proxy z powodu niedostępności konstruktora.

Ten facet zgłosił podobną sytuację:

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

haps10
źródło
0

Zapoznaj się z tą sekcją specyfikacji języka Java, która wyjaśnia różnicę między statycznymi i niestatycznymi klasami wewnętrznymi: http://java.sun.com/docs/books/jls/third_edition/html/classes.html#8.1.3

Statyczna klasa wewnętrzna koncepcyjnie nie różni się od zwykłej klasy ogólnej zadeklarowanej w pliku .java.

Ponieważ Hibernate musi utworzyć wystąpienie ProjectPK niezależnie od instancji Project, ProjectPK musi być statyczną klasą wewnętrzną lub zadeklarowaną we własnym pliku .java.

odwołanie org.hibernate.InstantiationException: Brak domyślnego konstruktora

Amitābha
źródło