Tại sao Hibernate không yêu cầu hàm tạo đối số?


103

Phương thức khởi tạo không đối số là một yêu cầu (các công cụ như Hibernate sử dụng phản ánh trên phương thức khởi tạo này để khởi tạo các đối tượng).

Tôi đã nhận được câu trả lời bằng tay này nhưng ai đó có thể giải thích thêm không? Cảm ơn


7
FYI: khẳng định The no-argument constructor is a requirement là sai và tất cả các câu trả lời giải thích lý do tại sao lại như vậy mà không đặt câu hỏi liệu điều này có thực sự như vậy hay không, (bao gồm cả câu trả lời được chấp nhận, thậm chí đã nhận được tiền thưởng) đều sai . Xem câu trả lời này: stackoverflow.com/a/29433238/773113
Mike Nakis

2
Nó là bắt buộc nếu bạn đang sử dụng chế độ ngủ đông làm nhà cung cấp cho JPA.
Amalgovinus

1
@MikeNakis Bạn không chính xác Mike. Hibernate đòi hỏi một constructor mặc định để nhanh chóng đối tượng nếu bạn đang sử dụng chế độ ngủ đông là nhà cung cấp cho JPA (Amalgovinus) nếu không Hibernate sẽ báo cáo Caused by: org.hibernate.InstantiationException: No default constructor for entity: : hibernate.tutorial.Studentnhư trong trường hợp trong đó tôi chỉ gặp
phiêu

@Mushy câu hỏi được gắn thẻ "hibernate" và "orm", nó không được gắn thẻ bằng "jpa". Không có đề cập đến JPA trong câu hỏi.
Mike Nakis

1
@MikeNakis Tôi đồng ý với Mike nhưng Hibernate được sử dụng để triển khai "JPA" và không được sử dụng trong trường hợp không có "JPA" hoặc "ORM". Do đó, giả định là ngủ đông đang thực hiện "JPA".
Mushy

Câu trả lời:


138

Hibernate và mã nói chung tạo ra các đối tượng thông qua phản chiếu, sử dụng Class<T>.newInstance()để tạo một phiên bản mới của các lớp của bạn. Phương thức này yêu cầu một phương thức khởi tạo no-arg công khai để có thể khởi tạo đối tượng. Đối với hầu hết các trường hợp sử dụng, việc cung cấp một hàm tạo no-arg không phải là một vấn đề.

Có những cách tấn công dựa trên tuần tự hóa có thể hoạt động xung quanh việc không có hàm tạo no-arg, vì tuần tự hóa sử dụng phép thuật jvm để tạo các đối tượng mà không cần gọi hàm tạo. Nhưng điều này không khả dụng trên tất cả các máy ảo. Ví dụ: XStream có thể tạo các phiên bản của các đối tượng không có hàm tạo no-arg công khai, nhưng chỉ bằng cách chạy trong một chế độ được gọi là "nâng cao" chỉ khả dụng trên một số máy ảo. (Xem liên kết để biết chi tiết.) Các nhà thiết kế của Hibernate chắc chắn đã chọn duy trì khả năng tương thích với tất cả các máy ảo và do đó tránh các thủ thuật như vậy và sử dụng phương pháp phản chiếu được hỗ trợ chính thức Class<T>.newInstance()yêu cầu một hàm tạo không tranh luận.


31
FYI: Hàm tạo không cần công khai. Nó có thể có khả năng hiển thị gói và Hibernate nên có setAccessible(true)trên đó.
Grey

Tôi có thể tạo Kiểu người dùng tùy chỉnh với hàm tạo không mặc định để đặt trường cần thiết cho các hoạt động của nó không.
L-Samuels

1
Để tham khảo ObjectInputStreamlàm điều gì đó dọc theo dòng sun.reflect.ReflectionFactory.getReflectionFactory().newConstructorForSerialization(classToGetInstanceOf, Object.class.getConstructor()).newInstance()cho instantiating đối tượng không có constructor mặc định (JDK1.6 dành cho Windows)
SamYonnou

lại: It can have package visibility and Hibernate should setAccessible(true). Có itnghĩa là lớp được khởi tạo thông qua phản chiếu? Và Hibernate should setAccessible(true)nghĩa là gì?
Kevin Meredith

Obenesis thực hiện điều này và được sử dụng rộng rãi bởi nhiều khuôn khổ như spring-data và mockito github.com/easymock/objenesis
ltfishie

46

Hibernate khởi tạo các đối tượng của bạn. Vì vậy, nó cần phải có khả năng khởi tạo chúng. Nếu không có hàm tạo no-arg, Hibernate sẽ không biết cách khởi tạo nó, tức là truyền đối số nào.

Các tài liệu hướng dẫn chế độ ngủ đông cho biết:

4.1.1. Triển khai một hàm tạo không đối số

Tất cả các lớp liên tục phải có một hàm tạo mặc định (có thể không công khai) để Hibernate có thể khởi tạo chúng bằng cách sử dụng Constructor.newInstance(). Bạn nên có một phương thức khởi tạo mặc định với ít nhất khả năng hiển thị gói để tạo proxy thời gian chạy trong Hibernate.


6
Đối với khả năng hiển thị của hàm tạo, nếu bạn đang sử dụng JPA v2.0, hãy lưu ý rằng JSR-317 cho biết: Hàm tạo no-arg phải được công khai hoặc được bảo vệ .
José Andias

@Bozho xin chào ông, tôi có một nghi ngờ rằng nếu nội bộ chế độ ngủ đông sử dụng Constructor.newInstance () để khởi tạo đối tượng thì làm thế nào để chế độ ngủ đông đặt các giá trị vào các trường mà không có bộ định nghĩa nào được xác định?
Vikas Verma

Tôi không hiểu lý do tại sao tôi nhìn thấy cảnh báo này cho một lớp con không tin @Embeddable với một công constructor không-arg ...
Amalgovinus

Constructor.newInstance () nhận các đối số, vấn đề (thực sự là một vấn đề không phải là vấn đề) đang ánh xạ các đối số đó. Không hiểu tại sao ngủ đông không giải quyết được vấn đề này. Để so sánh: chú thích @JsonCreator trong Jackson thực hiện điều này và có nhiều lợi ích của các đối tượng bất biến.
drrob


43

Erm, xin lỗi mọi người, nhưng Hibernate không yêu cầu các lớp của bạn phải có một hàm tạo không tham số. Đặc tả JPA 2.0 yêu cầu nó, và điều này rất khập khiễng thay mặt cho JPA. Các khung công tác khác như JAXB cũng yêu cầu nó, điều này cũng rất khập khiễng thay cho các khung công tác đó.

(Trên thực tế, JAXB được cho là cho phép các nhà máy thực thể, nhưng nó khăng khăng đòi tự khởi tạo các nhà máy này, yêu cầu chúng phải có một phương thức khởi tạo không tham số - đoán xem - cái gì - trong cuốn sách của tôi chính xác là không cho phép các nhà máy; điều đó thật khập khiễng !)

Nhưng Hibernate không yêu cầu một điều như vậy.

Hibernate hỗ trợ cơ chế đánh chặn, (xem "Bộ đánh chặn" trong tài liệu ,) cho phép bạn khởi tạo đối tượng của mình với bất kỳ tham số phương thức khởi tạo nào mà chúng cần.

Về cơ bản, những gì bạn làm là khi bạn thiết lập chế độ ngủ đông, bạn chuyển cho nó một đối tượng triển khai org.hibernate.Interceptorgiao diện và ngủ đông sau đó sẽ gọi instantiate()phương thức của giao diện đó bất cứ khi nào nó cần một phiên bản mới của một đối tượng của bạn, vì vậy việc triển khai phương thức đó của bạn có thể newđồ vật của bạn theo bất kỳ cách nào bạn thích.

Tôi đã thực hiện nó trong một dự án và nó hoạt động như một cái duyên. Trong dự án này, tôi thực hiện mọi việc thông qua JPA bất cứ khi nào có thể và tôi chỉ sử dụng các tính năng Hibernate như bộ đánh chặn khi tôi không còn lựa chọn nào khác.

Hibernate dường như hơi không an toàn về nó, vì trong quá trình khởi động, nó đưa ra một thông báo thông tin cho từng lớp thực thể của tôi, cho tôi biết INFO: HHH000182: No default (no-argument) constructor for classclass must be instantiated by Interceptor, nhưng sau đó tôi thực hiện việc khởi tạo chúng bằng interceptor và tôi rất vui với điều đó.

Để trả lời phần "tại sao" của câu hỏi cho các công cụ không phải Hibernate , câu trả lời là "hoàn toàn không có lý do chính đáng", và điều này được chứng minh bằng sự tồn tại của công cụ đánh chặn ngủ đông. Có rất nhiều công cụ có thể đã hỗ trợ một số cơ chế tương tự cho việc khởi tạo đối tượng khách, nhưng chúng không hỗ trợ, vì vậy chúng tự tạo các đối tượng, vì vậy chúng phải yêu cầu các hàm tạo không tham số. Tôi bị cám dỗ để tin rằng điều này đang xảy ra bởi vì những người tạo ra những công cụ này nghĩ mình là những lập trình viên hệ thống ninja, những người tạo ra các khuôn khổ đầy ma thuật để được sử dụng bởi những lập trình viên ứng dụng ngu dốt, những người (vì vậy họ nghĩ) sẽ không bao giờ có cần các cấu trúc nâng cao như ... Mô hình Nhà máy . (Được chứ,nghĩ vậy. Tôi không thực sự nghĩ như vậy. Tôi đang đua ma.)


1
Cuối cùng, ai đó đã nhận được nó! Tôi đã dành nhiều thời gian hơn thời gian của mình để xử lý các khuôn khổ này che khuất quá trình khởi tạo đối tượng (điều này hoàn toàn quan trọng để chèn phụ thuộc thích hợp và cho hành vi đối tượng phong phú). Hơn nữa, phản xạ Java cho phép bạn tạo các đối tượng mà không cần sử dụng newInstance (). Phương thức getDeclaredConstructors đã có trong API phản chiếu kể từ JDK 1.1. Thật đáng sợ khi các nhà thiết kế đặc tả JPA đã bỏ qua điều này.
drrob

Cái này sai. Nếu Hibernate được sử dụng như các nhà cung cấp JPA cho persistence, nó đòi hỏi một constructor mặc định nếu không xảy ra sau đó sau Caused by: org.hibernate.InstantiationException: No default constructor for entity: : hibernate.tutorial.Studentđó thời gian gần đây xảy ra vì javax.persistence.*;được sử dụng và chỉ org.hibernatekhi tạoSession, SessionFactory, and Configuration
phiêu

2
@Mushy Điều này hoàn toàn chính xác, bởi vì a) câu hỏi là về ngủ đông, không đề cập đến JPA, và b) Tôi vẫn đề cập rõ ràng trong câu thứ hai của câu trả lời của tôi rằng JPA yêu cầu các hàm tạo mặc định mặc dù ngủ đông thì không.
Mike Nakis

36

Hibernate là một khung ORM hỗ trợ chiến lược truy cập trường hoặc thuộc tính. Tuy nhiên, nó không hỗ trợ ánh xạ dựa trên phương thức khởi tạo - có thể bạn muốn gì? - vì một số vấn đề như

Điều gì sẽ xảy ra cho dù lớp của bạn chứa nhiều hàm tạo

public class Person {

    private String name;
    private Integer age;

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

}

Như bạn có thể thấy, bạn gặp phải vấn đề không nhất quán vì Hibernate không thể cho rằng phương thức khởi tạo nào nên được gọi. Ví dụ: giả sử bạn cần truy xuất đối tượng Người được lưu trữ

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

Hàm dựng nào nên gọi Hibernate để truy xuất đối tượng Person? Bạn có thấy không ?

Và cuối cùng, bằng cách sử dụng phản xạ, Hibernate có thể khởi tạo một lớp thông qua hàm tạo không đối số của nó. Vì vậy, khi bạn gọi

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

Hibernate sẽ khởi tạo đối tượng Person của bạn như sau

Person.class.newInstance();

Theo tài liệu API

Lớp được khởi tạo như thể bởi một biểu thức mới với danh sách đối số trống

Đạo đức của câu chuyện

Person.class.newInstance();

tương tự như

new Person();

Không có gì khác


1
Đây là mô tả tuyệt vời nhất mà tôi đã tìm thấy về câu hỏi này. Hầu hết các câu trả lời tôi đã tìm thấy các thuật ngữ kỹ thuật sách đã được sử dụng và không có cơ quan nào giải thích nó một cách dễ hiểu như bạn đã làm. Kudos cho bạn và cảm ơn!
Hiệp sĩ bóng đêm

1
Đây cũng có thể là lý do của nhóm Hibernate. Nhưng trên thực tế, các vấn đề có thể được giải quyết bằng cách (1) yêu cầu chú thích hoặc chỉ sử dụng hàm tạo không mặc định nếu chỉ có một hàm tạo và (2) sử dụng class.getDeclaredConstructors. Và sử dụng Constructor.newInstance () thay vì Class.newInstance (). Ánh xạ thích hợp trong XML / chú thích sẽ cần thiết, trước Java 8, nhưng nó hoàn toàn có thể làm được.
drrob

Ok, vì vậy hibernate tạo đối tượng từ phương thức khởi tạo mặc định, và sau đó nó sử dụng bộ thiết lập cho các trường nameage? Nếu không thì sau này nó sử dụng hàm tạo khác?
tryHard

2
@tryingHard Có, sau khi được khởi tạo, Hibernate sử dụng bộ thiết lập hoặc trường - Điều này phụ thuộc vào chiến lược truy cập. Theo mặc định, vị trí của chú thích Id cung cấp chiến lược truy cập mặc định. Xem docs.jboss.org/hibernate/orm/5.1/userguide/html_single/cha Chapter/
Arthur Ronald

6

Trên thực tế, bạn có thể khởi tạo các lớp không có hàm tạo 0-args; bạn có thể lấy danh sách các hàm tạo của một lớp, chọn một và gọi nó với các tham số không có thật.

Mặc dù điều này là có thể, và tôi đoán nó sẽ hoạt động và không có vấn đề gì, nhưng bạn sẽ phải đồng ý rằng điều đó khá kỳ lạ.

Việc xây dựng các đối tượng theo cách Hibernate thực hiện (tôi tin rằng nó gọi phương thức khởi tạo 0-arg và sau đó nó có thể sửa đổi trực tiếp các trường của cá thể thông qua Reflection. Có lẽ nó biết cách gọi bộ cài đặt) đi ngược lại một chút về cách một đối tượng phải được xây dựng trong Java- gọi hàm tạo với các tham số thích hợp để đối tượng mới là đối tượng bạn muốn. Tôi tin rằng việc khởi tạo một đối tượng và sau đó biến đổi nó là hơi "phản Java" (hoặc tôi có thể nói là phản Java lý thuyết thuần túy) - và chắc chắn, nếu bạn làm điều này thông qua thao tác trường trực tiếp, nó sẽ đóng gói và tất cả những thứ đóng gói lạ mắt đó .

Tôi nghĩ rằng cách thích hợp để làm điều này sẽ là xác định trong ánh xạ Hibernate cách một đối tượng sẽ được khởi tạo từ thông tin trong hàng cơ sở dữ liệu bằng cách sử dụng hàm tạo thích hợp ... nhưng điều này sẽ phức tạp hơn - có nghĩa là cả hai chế độ Hibernate sẽ đều phức tạp hơn, ánh xạ sẽ phức tạp hơn ... và tất cả đều phải "tinh khiết" hơn; và tôi không nghĩ rằng điều này sẽ có lợi thế hơn so với cách tiếp cận hiện tại (ngoài việc cảm thấy hài lòng khi làm mọi việc theo "cách thích hợp").

Phải nói rằng, và thấy rằng cách tiếp cận Hibernate không được "sạch sẽ" cho lắm, nghĩa vụ phải có một hàm tạo 0-arg là không hoàn toàn cần thiết, nhưng tôi có thể hiểu phần nào yêu cầu này, mặc dù tôi tin rằng họ đã làm điều đó hoàn toàn "đúng cách "căn cứ, khi họ đi lạc khỏi" cách thích hợp "(mặc dù vì lý do hợp lý) trước đó nhiều.


5

Hibernate cần tạo các phiên bản là kết quả của các truy vấn của bạn (thông qua phản chiếu), Hibernate dựa vào hàm tạo no-arg của các thực thể cho điều đó, vì vậy bạn cần cung cấp một hàm tạo no-arg. Điều gì không rõ ràng?


Trong điều kiện nào thì một hàm privatetạo không chính xác? Tôi đang thấy java.lang.InstantiationExceptionngay cả với một phương thức privatekhởi tạo cho thực thể JPA của tôi. tài liệu tham khảo .
Kevin Meredith

Tôi đã thử lớp không có hàm tạo rỗng (nhưng với hàm tạo args) và nó đã hoạt động. Tôi nhận được INFO từ chế độ ngủ đông "INFO: HHH000182: Không có hàm tạo (không đối số) mặc định cho lớp và lớp phải được khởi tạo bởi Interceptor", nhưng không có ngoại lệ và đối tượng đã được nhận thành công từ DB.
lijep đập

2

Việc tạo đối tượng với một phương thức khởi tạo không tham số thông qua phản xạ và sau đó điền vào các thuộc tính của nó bằng dữ liệu thông qua phản chiếu sẽ dễ dàng hơn nhiều so với việc thử và khớp dữ liệu với các tham số tùy ý của một phương thức khởi tạo tham số, với việc thay đổi tên / xung đột đặt tên, logic không xác định bên trong phương thức khởi tạo, các bộ tham số không phù hợp với các thuộc tính của một đối tượng, v.v.

Nhiều ORM và bộ tuần tự hóa yêu cầu các hàm tạo không tham số, bởi vì các hàm tạo tham số thông qua phản xạ rất mỏng manh và các hàm tạo không tham số cung cấp cả tính ổn định cho ứng dụng và kiểm soát hành vi đối tượng cho nhà phát triển.


Tôi lập luận rằng việc buộc khả năng thay đổi hoàn toàn đối với những gì có thể cần là một đối tượng tên miền phong phú vẫn còn mong manh hơn (nó không phải là ORM nhiều nếu các thực thể của bạn cần phải có những túi dữ liệu đặc biệt để hoạt động - Tôi ở đây vì tôi muốn một hàm tạo nhưng thay vào đó có một thứ tự không xác định của các lệnh gọi rich setter) ... Nhưng +1 vì bạn thừa nhận rằng phản xạ có thể được thực hiện trên các hàm tạo có args :)
drrob

2

Hibernate sử dụng proxy để tải chậm. Nếu bạn không xác định phương thức khởi tạo hoặc đặt nó ở chế độ riêng tư, một số thứ vẫn có thể hoạt động - những thứ không phụ thuộc vào cơ chế proxy. Ví dụ: tải đối tượng (không có hàm tạo) trực tiếp bằng cách sử dụng API truy vấn.

Tuy nhiên, nếu bạn sử dụng phương thức session.load (), bạn sẽ phải đối mặt với InstantiationException từ trình tạo proxy lib do không có sẵn phương thức khởi tạo.

Anh chàng này đã báo cáo một tình huống tương tự:

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


0

Kiểm tra phần này của thông số ngôn ngữ Java giải thích sự khác biệt giữa các lớp bên trong tĩnh và không tĩnh: http://java.sun.com/docs/books/jls/third_edition/html/classes.html#8.1.3

Một lớp bên trong tĩnh về mặt khái niệm không khác gì một lớp chung thông thường được khai báo trong tệp .java.

Vì Hibernate cần khởi tạo ProjectPK độc lập với phiên bản Project, nên ProjectPK cần phải là một lớp bên trong tĩnh hoặc được khai báo trong tệp .java của chính nó.

tham chiếu org.hibernate.InstantiationException: Không có hàm tạo mặc định

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.