2011-07-07 28 views
6

Hibernate sử dụng proxy để cho phép tải chậm bộ sưu tập và thậm chí cả các kết hợp một lần. Theo tài liệu tham khảo của Hibernate (3.6.5) (Mục 21.1.3, proxy kết hợp một lần), chẳng hạn proxy không thể được xây dựng bởi Hibernate nếu nó chứa "bất kỳ phương thức cuối cùng nào".
Câu hỏi của tôi là, hạn chế này có áp dụng cho getters/setters của các trường liên tục hoặc thực sự cho bất kỳ phương thức nào trong một lớp thực thể không? Vì vậy, thực hiện một phương pháp như thế này:Phương pháp cuối cùng có ngăn Hibernate tạo proxy cho thực thể như vậy không?

public final String toString() { 
    return this.getClass().getSimpleName() + id; 
} 

thực sự ngăn cản việc tạo proxy (CGLIB hoặc Javassist) cho thực thể này? Việc sử dụng truy cập dựa trên trường hoặc thuộc tính có quan trọng không? Kể từ khi CGLIB được thay thế bởi Javassist, điều này có cung cấp thêm bất kỳ tính năng nào theo hướng này không?

Tôi thích sử dụng thừa kế trong phân cấp thực thể và do đó yêu cầu xác định một số phương thức cuối cùng, ví dụ: trong lớp cơ sở để ngăn các lớp con ghi đè các phương thức đó.

cảm ơn trước!

Trả lời

6

Bằng sự giúp đỡ của các mailing list Hibernate (nhờ Emmanuel Bernardt!) Tôi có thể trả lời câu hỏi của riêng mình, tóm tắt là:
Các phương pháp cuối cùng không ngăn Hibernate tạo proxy nói chung trừ khi các phương pháp đó không sử dụng trạng thái nào của trạng thái. .

Một số thông tin cơ bản: Hibernate không sử dụng tăng cường bytecode không có cglib cũng như với Javassist, để proxy khởi tạo thực thể đích của nó một cách lười biếng, nó phải chặn bất kỳ phương thức nào có thể sử dụng trạng thái của thực thể đích đó. Bây giờ là hoàn toàn ok để có một phương pháp chính thức như thế này

public final doSomething(String a, Integer b) { 
    // do complicated stuff using only a and b (no instance members accessed!) 
} 

nhưng ngay sau khi phương pháp này sử dụng bất kỳ lĩnh vực dai dẳng trực tiếp hoặc thông qua một phương pháp dụ này sẽ bỏ qua proxy và do đó dẫn đến hành vi bất ngờ.

Là một sidenote này được lý do tương tự tại sao bạn không nên truy cập vào các lĩnh vực của các trường hợp khác trực tiếp, ví dụ như trong một thực thể equals phương pháp:

// XXX bad code! 
public boolean equals(Object o) { 
    if (this == o) return true; 
    if (!(o instanceof Profile)) return false; 
    Profile profile = (Profile) o; 
    // XXX this bypasses a possible proxy, use profile.getName() instead! 
    return (name == null ? profile.name == null : name.equals(profile.name)); 
} 
+1

Tôi nghĩ rằng _ "ngay sau khi phương pháp này sử dụng bất kỳ trường liên tục hoặc trực tiếp hoặc thông qua một phương pháp dụ khác này sẽ bỏ qua proxy" _ là hơi không chính xác. Trong phương thức cuối cùng 'this' sẽ cần thiết là proxy, vì vậy ngay sau khi một phương thức instance cuối cùng được gọi là target sẽ được khởi tạo. Tất nhiên truy cập vào bất kỳ trường nào trực tiếp trong phương thức cuối cùng sẽ truy cập trường không được khởi tạo trên proxy vì vậy đây luôn là vấn đề. –

+1

Để làm rõ những gì @ MichałPolitowski vừa nói: Các phương pháp cuối cùng không truy cập vào trạng thái là tốt. Các phương thức cuối cùng mà chỉ truy cập trạng thái thông qua một phương thức khác cũng tốt. Nhưng phương thức cuối cùng sẽ thất bại (và có thể thất bại) nếu nó sử dụng một trường cá thể trực tiếp. Có 3 cách để giải quyết vấn đề này. Đầu tiên là không khai báo phương thức cuối cùng. Thứ hai là không truy cập trực tiếp các trường từ các phương thức cuối cùng. Thứ ba là tắt proxy bằng cách cài đặt lazy = "false" (không được khuyến nghị vì lý do hiệu suất). – MarcG

+0

Nếu bạn sử dụng Intellij IDEA, vui lòng bỏ phiếu để tạo ra một cuộc kiểm tra giải quyết vấn đề này: http://youtrack.jetbrains.com/issue/IDEA-128132 – MarcG

1

Tôi chắc chắn rằng, như tham chiếu cho biết, nó áp dụng cho bất kỳ phương pháp nào. Thật vậy, một proxy, trong sự hiểu biết của tôi, không có gì hơn là một lớp con của thực thể không có trạng thái ngoại trừ ID của thực thể ban đầu, và ủy nhiệm mọi phương thức gọi đến một cá thể thực tế của lớp thực thể khi được khởi tạo. do đó nó có ghi đè lên tất cả các phương pháp để

  • khởi tạo chính nó nếu cần thiết
  • đại biểu các cuộc gọi đến phương pháp dụ thực tế
+0

Cả cglib và javassist trực tiếp thao tác mã byte của một lớp, do đó, nó không phải là proxy theo nghĩa thông thường. Do đó theo ý kiến ​​của tôi thì không cần phải thực sự đánh chặn phương pháp _every_ mà chỉ có các getters và setters của các trường liên tục. – Robin

+0

Đồng ý, nhưng tôi vẫn thấy một ngoại lệ: khi thừa kế được sử dụng và một thực thể có một-một với một lớp cơ sở của hai thực thể, proxy là một thể hiện của lớp con thứ ba, vì Hibernat không có cách nào để biết loại thực tế để sử dụng cho proxy. Nó sẽ không phải ghi đè lên tất cả các phương pháp của lớp cơ sở sau đó? –

0

Lấy cảm hứng từ câu hỏi này tôi đã tạo ra một plugin cho Intellij IDEA , giải quyết vấn đề này.Ở đây là:

https://plugins.jetbrains.com/plugin/7866

Các mô tả đơn giản là thế này:

Hibernate âm thầm thất bại trong những tình huống nhất định, dẫn đến lỗi mà rất khó để theo dõi. Plugin này giúp tìm và sửa chữa một số vấn đề này. Trong Cài đặt> Thanh tra> Hibernate kiểm tra, nó bổ sung các kiểm tra sau đây: • Lớp học kiên trì là cuối cùng; • Phương pháp cuối cùng của một lớp liên tục sử dụng truy cập trường trực tiếp.

======================== ==

một lưu ý phụ:

như @BartvanHeukelom nói trong các ý kiến, thực tế là phương pháp cuối cùng không thể ủy quyền có thể được sử dụng như một tài sản: sau đó, bạn có thể nhận được id của đối tượng từ getter, mà không khởi tạo proxy và tải các trường của nó. Đây là mã tôi sử dụng:

@SuppressWarnings ("AccessingFieldFromAFinalMethodOfPersistedClass") 
public final Id getId() { 
    if (this instanceof HibernateProxy) return (Id)((HibernateProxy)this).getHibernateLazyInitializer().getIdentifier(); 
    else return id; 
    } 

Xin lưu ý @SuppressWarnings. Điều này là cần thiết chỉ khi bạn sử dụng IntelliJ IDEA với plugin của tôi.

Các vấn đề liên quan