Mengapa Hibernate tidak memerlukan konstruktor argumen?

103

Konstruktor tanpa argumen adalah persyaratan (alat seperti Hibernasi menggunakan refleksi pada konstruktor ini untuk membuat contoh objek).

Saya mendapat jawaban bergelombang tangan ini tetapi dapatkah seseorang menjelaskan lebih lanjut? Terima kasih

unj2
sumber
7
FYI: pernyataan yang The no-argument constructor is a requirement salah , dan semua jawaban yang melanjutkan untuk menjelaskan mengapa demikian tanpa mempertanyakan apakah memang demikian, (termasuk jawaban yang diterima, yang bahkan telah menerima karunia,) salah . Lihat jawaban ini: stackoverflow.com/a/29433238/773113
Mike Nakis
2
Itu IS diperlukan jika Anda menggunakan hibernate sebagai penyedia JPA.
Amalgovinus
1
@MikeNakis Anda salah Mike. Hibernate memerlukan konstruktor default untuk membuat instance objek jika Anda menggunakan hibernate sebagai penyedia JPA (Amalgovinus), jika tidak Hibernate akan melaporkan Caused by: org.hibernate.InstantiationException: No default constructor for entity: : hibernate.tutorial.Studentseperti yang baru saja saya temui
Mushy
@Mushy pertanyaannya ditandai dengan "hibernate" dan "orm", tidak di tag dengan "jpa". Tidak disebutkan JPA dalam pertanyaan tersebut.
Mike Nakis
1
@ MikeNakis Saya setuju Mike tetapi Hibernate digunakan sebagai implementasi "JPA" dan tidak digunakan jika tidak ada "JPA" atau "ORM". Oleh karena itu, asumsinya adalah hibernate menerapkan "JPA".
Mushy

Jawaban:

138

Hibernate, dan kode secara umum yang membuat objek melalui penggunaan refleksi Class<T>.newInstance()untuk membuat instance baru dari kelas Anda. Metode ini membutuhkan konstruktor no-arg publik untuk dapat membuat instance objek. Untuk sebagian besar kasus penggunaan, menyediakan konstruktor no-arg bukanlah masalah.

Ada peretasan berdasarkan serialisasi yang dapat diatasi tanpa memiliki konstruktor no-arg, karena serialisasi menggunakan sihir jvm untuk membuat objek tanpa memanggil konstruktor. Namun ini tidak tersedia di semua VM. Misalnya, XStream dapat membuat instance objek yang tidak memiliki konstruktor no-arg publik, tetapi hanya dengan menjalankan apa yang disebut mode "ditingkatkan" yang hanya tersedia di VM tertentu. (Lihat tautan untuk detailnya.) Desainer Hibernate pasti memilih untuk menjaga kompatibilitas dengan semua VM dan menghindari trik seperti itu, dan menggunakan metode refleksi yang didukung secara resmi yang Class<T>.newInstance()membutuhkan konstruktor tanpa argumen.

mdma
sumber
31
FYI: Konstruktor tidak perlu untuk publik. Ini dapat memiliki visibilitas paket dan Hibernate harus setAccessible(true)di atasnya.
Gray
Apakah saya dapat membuat Custom UserType dengan konstruktor non default untuk menyetel bidang yang diperlukan untuk operasinya.
L-Samuels
1
Untuk referensi ObjectInputStreammelakukan sesuatu di sepanjang baris sun.reflect.ReflectionFactory.getReflectionFactory().newConstructorForSerialization(classToGetInstanceOf, Object.class.getConstructor()).newInstance()untuk membuat instance objek tanpa konstruktor default (JDK1.6 untuk Windows)
SamYonnou
re: It can have package visibility and Hibernate should setAccessible(true). Apakah itberarti kelas dibuat melalui refleksi? Dan apa Hibernate should setAccessible(true)artinya?
Kevin Meredith
Objenesis melakukan ini dan secara luas digunakan oleh banyak kerangka kerja seperti spring-data dan mockito github.com/easymock/objenesis
ltfishie
46

Hibernasi membuat instance objek Anda. Jadi harus bisa memberi contoh. Jika tidak ada konstruktor no-arg, Hibernate tidak akan tahu bagaimana cara membuatnya, yaitu argumen apa yang harus diteruskan.

The dokumentasi hibernasi mengatakan:

4.1.1. Menerapkan konstruktor tanpa argumen

Semua kelas persisten harus memiliki konstruktor default (yang bisa non-publik) sehingga Hibernate dapat membuat instance menggunakan Constructor.newInstance(). Direkomendasikan agar Anda memiliki konstruktor default dengan setidaknya visibilitas paket untuk pembuatan proxy waktu proses dalam Hibernate.

Bozho
sumber
6
Untuk visibilitas konstruktor, jika Anda menggunakan JPA v2.0, perhatikan bahwa JSR-317 mengatakan: Konstruktor no-arg harus publik atau dilindungi .
José Andias
@ Bozho halo Pak, saya punya satu keraguan bahwa jika secara internal menggunakan hibernasi Constructor.newInstance () untuk membuat instance objek lalu bagaimana mengatur hibernasi nilai ke dalam bidang tanpa penyetel yang ditentukan?
Vikas Verma
Saya tidak mengerti mengapa saya melihat peringatan ini untuk subkelas non-privat @Embeddable dengan konstruktor no-arg publik ...
Amalgovinus
Constructor.newInstance () mengambil argumen, masalah (sebenarnya bukan masalah) adalah memetakan argumen tersebut. Tidak tahu mengapa hibernasi tidak menyelesaikan masalah ini. Sebagai perbandingan: anotasi @JsonCreator di Jackson melakukan hal ini, dan banyak manfaat yang didapat dari objek yang tidak dapat diubah.
drrob
43

Erm, maaf semuanya, tetapi Hibernate tidak mengharuskan kelas Anda harus memiliki konstruktor tanpa parameter. Spesifikasi JPA 2.0 membutuhkannya, dan ini sangat timpang atas nama JPA. Kerangka lain seperti JAXB juga membutuhkannya, yang juga sangat timpang atas nama kerangka tersebut.

(Sebenarnya, JAXB seharusnya mengizinkan pabrik entitas, tetapi bersikeras untuk membuat contoh pabrik-pabrik ini dengan sendirinya, mengharuskan mereka untuk memiliki --tebak apa-- konstruktor tanpa parameter , yang dalam buku saya sama baiknya dengan tidak mengizinkan pabrik; betapa payahnya itu !)

Tapi Hibernate tidak membutuhkan hal seperti itu.

Hibernate mendukung mekanisme intersepsi, (lihat "Interceptor" dalam dokumentasi ,) yang memungkinkan Anda untuk membuat instance objek dengan parameter konstruktor apa pun yang mereka butuhkan.

Pada dasarnya, apa yang Anda lakukan adalah ketika Anda mengatur hibernate, Anda meneruskannya objek yang mengimplementasikan org.hibernate.Interceptorantarmuka, dan hibernate kemudian akan memanggil instantiate()metode antarmuka itu kapan pun ia membutuhkan instance baru dari objek Anda, sehingga implementasi Anda dari metode itu dapat newobjek Anda dengan cara apa pun yang Anda suka.

Saya telah melakukannya dalam sebuah proyek dan itu bekerja seperti pesona. Dalam proyek ini saya melakukan banyak hal melalui JPA bila memungkinkan, dan saya hanya menggunakan fitur Hibernate seperti interceptor ketika saya tidak punya pilihan lain.

Hibernate tampaknya agak tidak aman tentang hal itu, karena selama startup mengeluarkan pesan info untuk setiap kelas entitas saya, memberi tahu saya INFO: HHH000182: No default (no-argument) constructor for classdan class must be instantiated by Interceptor, tetapi kemudian saya membuat instantiate mereka dengan interseptor, dan itu senang dengan itu.

Untuk menjawab bagian "mengapa" dari pertanyaan untuk alat selain Hibernate , jawabannya adalah "sama sekali tidak ada alasan yang baik", dan ini dibuktikan dengan adanya penyergap hibernate. Ada banyak alat di luar sana yang mungkin telah mendukung beberapa mekanisme serupa untuk instansiasi objek klien, tetapi sebenarnya tidak, sehingga mereka membuat objek sendiri, sehingga mereka harus memerlukan konstruktor tanpa parameter. Saya tergoda untuk percaya bahwa ini terjadi karena pencipta alat ini menganggap diri mereka sebagai pemrogram sistem ninja yang membuat kerangka kerja penuh keajaiban untuk digunakan oleh pemrogram aplikasi yang bodoh, yang (menurut mereka) tidak akan pernah dalam mimpi terliar mereka memiliki kebutuhan untuk konstruksi lanjutan seperti ... Pola Pabrik . (Baik,berpikir begitu. Saya tidak benar-benar berpikir begitu. Saya bercanda.)

Mike Nakis
sumber
1
Akhirnya, seseorang yang mengerti! Saya telah menghabiskan lebih banyak waktu saya daripada yang saya suka berurusan dengan kerangka kerja ini yang mengaburkan proses pembuatan instance objek (yang sangat penting untuk injeksi ketergantungan yang tepat dan untuk perilaku objek yang kaya). Selanjutnya, refleksi Java memungkinkan Anda membuat objek tanpa menggunakan newInstance (). Metode getDeclaredConstructors telah ada di API refleksi sejak JDK 1.1. Mengerikan bahwa perancang spesifikasi JPA mengabaikan ini.
drrob
Ini salah. Jika Hibernate digunakan sebagai penyedia JPA untuk ketekunan, itu memang memerlukan konstruktor default jika tidak, berikut ini terjadi Caused by: org.hibernate.InstantiationException: No default constructor for entity: : hibernate.tutorial.Studentyang baru-baru ini terjadi karena javax.persistence.*;digunakan dan hanya org.hibernatesaat membuatSession, SessionFactory, and Configuration
Mushy
2
@Mushy Ini benar sekali, karena a) pertanyaannya adalah tentang hibernate, tanpa satu pun menyebutkan JPA, dan b) Saya tetap secara eksplisit menyebutkan dalam kalimat kedua jawaban saya bahwa JPA memang memerlukan konstruktor default meskipun hibernate tidak.
Mike Nakis
36

Hibernate adalah kerangka kerja ORM yang mendukung strategi akses lapangan atau properti. Namun, itu tidak mendukung pemetaan berbasis konstruktor - mungkin apa yang Anda inginkan? - karena beberapa masalah seperti

Apa yang terjadi jika kelas Anda berisi banyak konstruktor

public class Person {

    private String name;
    private Integer age;

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

}

Seperti yang Anda lihat, Anda berurusan dengan masalah ketidakkonsistenan karena Hibernate tidak dapat menganggap konstruktor mana yang harus dipanggil. Misalnya, Anda perlu mengambil objek Person yang disimpan

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

Konstruktor mana yang harus dipanggil Hibernate untuk mengambil objek Person? Bisakah kamu melihat ?

Dan terakhir, dengan menggunakan refleksi, Hibernate dapat membuat instance kelas melalui konstruktor tanpa argumen. Jadi saat Anda menelepon

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

Hibernate akan membuat instance objek Person Anda sebagai berikut

Person.class.newInstance();

Yang menurut dokumentasi API

Kelas dibuat seolah-olah oleh ekspresi baru dengan daftar argumen kosong

Pesan moral dalam cerita

Person.class.newInstance();

mirip dengan

new Person();

Tidak ada lagi

Arthur Ronald
sumber
1
Sejauh ini, ini adalah uraian paling bagus yang saya temukan berkenaan dengan pertanyaan ini. Sebagian besar jawaban yang saya temukan menggunakan istilah teknis kutu buku dan tidak ada orang yang menjelaskannya dengan cara yang lentur seperti yang Anda lakukan. Kudos to you and thanks!
The Dark Knight
1
Ini mungkin alasan tim Hibernate. Namun pada kenyataannya, masalah dapat diselesaikan dengan (1) memerlukan anotasi, atau hanya menggunakan konstruktor non-default jika hanya ada satu konstruktor dan (2) menggunakan class.getDeclaredConstructors. Dan menggunakan Constructor.newInstance () sebagai ganti Class.newInstance (). Pemetaan yang sesuai dalam XML / anotasi akan dibutuhkan, sebelum Java 8, tetapi itu sepenuhnya bisa dilakukan.
drrob
Ok, jadi hibernate membuat objek dari konstruktor default, lalu menggunakan setter untuk field namedan age? Jika tidak, maka ia menggunakan konstruktor lain nanti?
TryHard
2
@tryingHard Ya, setelah dibuat, Hibernasi menggunakan penyetel atau bidang - Tergantung pada strategi akses. Secara default, penempatan anotasi Id memberikan strategi akses default. Lihat docs.jboss.org/hibernate/orm/5.1/userguide/html_single/chapters/…
Arthur Ronald
6

Sebenarnya, Anda bisa membuat instance kelas yang tidak memiliki konstruktor 0-args; Anda bisa mendapatkan daftar konstruktor kelas, memilih satu dan memanggilnya dengan parameter palsu.

Meskipun ini mungkin, dan saya rasa ini akan berhasil dan tidak akan menjadi masalah, Anda harus setuju bahwa itu cukup aneh.

Membangun objek seperti yang dilakukan Hibernate (saya percaya ini memanggil konstruktor 0-arg dan kemudian mungkin memodifikasi bidang instance secara langsung melalui Refleksi. Mungkin ia tahu cara memanggil setter) sedikit bertentangan dengan bagaimana sebuah objek seharusnya dibangun di Java- panggil konstruktor dengan parameter yang sesuai sehingga objek baru adalah objek yang Anda inginkan. Saya percaya bahwa membuat instance objek dan kemudian memutasinya agak "anti-Java" (atau menurut saya, anti Java teoretis murni) - dan tentunya, jika Anda melakukan ini melalui manipulasi bidang langsung, ia akan menjadi enkapsulasi dan semua hal enkapsulasi yang mewah .

Saya pikir cara yang tepat untuk melakukan ini adalah dengan menentukan dalam pemetaan Hibernate bagaimana sebuah objek harus dibuat dari info di baris database menggunakan konstruktor yang tepat ... tetapi ini akan lebih kompleks - yang berarti keduanya Hibernate akan genap lebih kompleks, pemetaan akan menjadi lebih kompleks ... dan semuanya menjadi lebih "murni"; dan menurut saya ini tidak akan memiliki keuntungan dibandingkan pendekatan saat ini (selain merasa nyaman melakukan sesuatu dengan "cara yang tepat").

Karena itu, dan melihat bahwa pendekatan Hibernate tidak terlalu "bersih", kewajiban untuk memiliki konstruktor 0-arg tidak sepenuhnya diperlukan, tetapi saya dapat memahami sedikit persyaratannya, meskipun saya yakin mereka melakukannya hanya dengan "cara yang benar" "alasan, ketika mereka menyimpang dari" cara yang tepat "(meskipun untuk alasan yang masuk akal) jauh sebelum itu.

alex
sumber
5

Hibernate perlu membuat instance sebagai hasil dari kueri Anda (melalui refleksi), Hibernate bergantung pada konstruktor entitas no-arg untuk itu, jadi Anda perlu menyediakan konstruktor tanpa argumen. Apa yang tidak jelas?

Pascal Thivent
sumber
Dalam kondisi apa privatekonstruktor salah? Saya melihat java.lang.InstantiationExceptionbahkan dengan privatekonstruktor untuk entitas JPA saya. referensi .
Kevin Meredith
Saya mencoba kelas tanpa konstruktor kosong (tetapi dengan konstruktor args) dan berhasil. Saya mendapat INFO dari hibernate "INFO: HHH000182: Tidak ada konstruktor default (tanpa argumen) untuk kelas dan kelas harus dibuat oleh Interceptor", tetapi tidak ada pengecualian dan objek berhasil diterima dari DB.
bendungan lijep
2

Jauh lebih mudah untuk membuat objek dengan konstruktor tanpa parameter melalui refleksi, dan kemudian mengisi propertinya dengan data melalui refleksi, daripada mencoba dan mencocokkan data dengan parameter arbitrer dari konstruktor berparameter, dengan perubahan nama / konflik penamaan, logika tak terdefinisi di dalam konstruktor, set parameter yang tidak cocok dengan properti suatu objek, dan sebagainya.

Banyak ORM dan pembuat serial memerlukan konstruktor tanpa parameter, karena konstruktor berparameter melalui refleksi sangat rapuh, dan konstruktor tanpa parameter memberikan stabilitas pada aplikasi dan kontrol atas perilaku objek kepada pengembang.

Kaerber
sumber
Saya berpendapat bahwa memaksa mutabilitas total pada apa yang mungkin perlu menjadi objek domain kaya masih lebih rapuh (ini bukan ORM jika entitas Anda perlu menjadi kantong data tanpa fitur untuk bekerja - Saya di sini karena saya ingin konstruktor tetapi memiliki urutan panggilan penyetel kaya yang tidak ditentukan) ... Tetapi +1 karena Anda mengetahui bahwa refleksi dapat dilakukan pada konstruktor dengan args :)
drrob
2

Hibernate menggunakan proxy untuk pemuatan lambat. Jika Anda tidak menentukan konstruktor atau menjadikannya pribadi, beberapa hal mungkin masih berfungsi - hal-hal yang tidak bergantung pada mekanisme proxy. Misalnya, memuat objek (tanpa konstruktor) secara langsung menggunakan API kueri.

Tapi, jika Anda menggunakan metode session.load () Anda akan menghadapi InstantiationException dari lib generator proxy karena tidak tersedia konstruktor.

Orang ini melaporkan situasi serupa:

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

haps10
sumber
0

Lihat bagian spesifikasi bahasa Java ini yang menjelaskan perbedaan antara kelas dalam statis dan non-statis: http://java.sun.com/docs/books/jls/third_edition/html/classes.html#8.1.3

Kelas dalam statis secara konseptual tidak berbeda dengan kelas umum biasa yang dideklarasikan dalam file .java.

Karena Hibernate perlu membuat instance ProjectPK secara independen dari instance Project, ProjectPK harus berupa kelas dalam statis, atau dideklarasikan dalam file .java miliknya sendiri.

referensi org.hibernate.InstantiationException: Tidak ada konstruktor default

Amitābha
sumber