2013-07-18 36 views
9

Tôi khá mới với JPA và Hibernate (tôi đang học chăm chỉ!) Và tôi đang đấu tranh với một vấn đề mà tôi dường như không thể tìm thấy một giải pháp tầm thường Vì vậy, ở đây nó được.Hibernate: Lazy initialization vs hashcode bị hỏng/bằng conundrum

Tôi có một thực thể mà tựa hồ như sau:

@Entity 
@Table(name = "mytable1") 
public class EntityOne { 
    // surrogate key, database generated 
    @Id 
    @GeneratedValue(strategy = GenerationType.IDENTITY) 
    @Column(name = "id") 
    private Long id; 

    // business key 
    @Column(name = "identifier", nullable = false, unique = true) 
    private String identifier; 

    @ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.REFRESH) 
    @JoinColumn(name = "twoId", nullable = false) 
    private EntityTwo two; 

    @OneToMany(mappedBy = "entityOne", fetch = FetchType.EAGER, 
    cascade = {CascadeType.ALL}, orphanRemoval = true) 
    private Set<EntityThree> resources = new HashSet<>(); 

    // getters/setters omitted 

    @Override 
    public int hashCode() { 
    // the business key should always be defined (through constructor/query) 
    // if this is null the class violates the general hashcode contract 
    // that the integer value returned must always be the same 
    Assert.notNull(identifier); 
    // a dirty alternative would be: 
    // if(identifier==null) return 0; 
    return identifier.hashCode(); 
    } 

    @Override 
    public boolean equals(Object o) { 
    return o instanceof ResourceGroup 
     && ((ResourceGroup) o).identifier.equals(identifier); 
    } 
} 

Dự án của tôi được thiết lập với Spring JPA, vì vậy tôi có tôi CrudRepository<EntityOne,Long> tiêm trong một lớp dịch vụ mà có một vài @Transactional phương pháp và tôi quét các gói miền/dịch vụ của tôi cho JPA và các giao dịch tương ứng.

Một trong các phương pháp dịch vụ gọi phương thức findAll() của kho và trả về danh sách EntityOne s. Tất cả mọi thứ hoạt động tốt, trừ khi tôi cố gắng truy cập vào các getter cho two, mà rõ ràng là ném:

org.hibernate.LazyInitializationException: could not initialize proxy - no Session 

tôi nghĩ rằng nó có thể có ích để có đối tượng này được khởi tạo, vì vậy tôi chuyển sang kiểu quyến rũ từ lười biếng để háo hức. Tuy nhiên, nếu tôi làm điều đó tôi nhận được như sau:

java.lang.IllegalArgumentException: [Assertion failed] - this argument is required; it must not be null 
    at org.springframework.util.Assert.notNull(Assert.java:112) 
    at org.springframework.util.Assert.notNull(Assert.java:123) 
    at my.pkg.domain.EntityOne.hashCode(ResourceGroup.java:74) 
    at java.util.HashMap.hash(HashMap.java:351) 
    at java.util.HashMap.put(HashMap.java:471) 
    at java.util.HashSet.add(HashSet.java:217) 
    at java.util.AbstractCollection.addAll(AbstractCollection.java:334) 
    at org.hibernate.collection.internal.PersistentSet.endRead(PersistentSet.java:346) 
    at org.hibernate.engine.loading.internal.CollectionLoadContext.endLoadingCollection(CollectionLoadContext.java:243) 
    at org.hibernate.engine.loading.internal.CollectionLoadContext.endLoadingCollections(CollectionLoadContext.java:233) 
    at org.hibernate.engine.loading.internal.CollectionLoadContext.endLoadingCollections(CollectionLoadContext.java:209) 
    at org.hibernate.loader.Loader.endCollectionLoad(Loader.java:1149) 
//... 

Tôi nhìn một thời gian ngắn vào mã nguồn của Hibernate và có vẻ như nó đang cố gắng để đưa EntityOne đối tượng của tôi trong một bộ trước chìa khóa kinh doanh của họ được khởi tạo. Cách giải thích của tôi có đúng không? Có cách nào để giái quyết vấn đề này không? Tôi đang làm điều gì đó vô cùng ngu ngốc?

Tôi đánh giá cao sự giúp đỡ của bạn

EDIT: Tôi chỉ muốn làm rõ rằng những gì tôi đang cố gắng để hiểu đây là những gì thực tiễn tốt nhất là đặc biệt đối với JPA với Hibernate và. Nếu đây là một POJO đơn giản, tôi có thể làm cho trường nhận dạng cuối cùng (tôi thực sự sẽ làm cho toàn bộ lớp không thay đổi được) và được an toàn. Tôi không thể làm điều này bởi vì tôi đang sử dụng JPA. Vì vậy, các câu hỏi: bạn có vi phạm hợp đồng hashCode và theo cách nào không? Hibernate đối phó với vi phạm này như thế nào? JPA đề nghị cách làm điều này nói chung là gì? Tôi có nên loại bỏ hoàn toàn các bộ sưu tập dựa trên băm và sử dụng các danh sách thay thế không?

Giovanni

+0

Tôi đã thay thế tất cả giá trị ban đầu của 'Set' fields 'quan hệ bằng' new LinkedHashMap()' thay vì 'new HashMap()' và nó bắt đầu hoạt động. Nó có lạ không? –

Trả lời

0

Tôi tin rằng tôi thực sự đã tìm ra cách để làm cho công việc này tốt hơn một chút, tức là, buộc Hibernate (hoặc bất kỳ nhà cung cấp JPA) nào có sẵn khóa trước khi gắn các đối tượng vào bộ sưu tập. Trong trường hợp này, đối tượng sẽ được khởi tạo đúng cách và chúng ta có thể chắc chắn rằng khóa nghiệp vụ sẽ không rỗng.

Ví dụ, dưới đây là cách lớp EntityTwo sẽ phải xem xét:

@Entity 
@Table(name = "mytable2") 
public class EntityTwo { 
    // other code omitted ... 
    @OneToMany(mappedBy = "entityTwo", fetch = FetchType.EAGER, 
    cascade = {CascadeType.ALL}, orphanRemoval = true) 
    @MapKey(name = "identifier") 
    private Map<String, EntityOne> entityOnes = new HashMap<>(); 
} 

tôi đã không kiểm tra mã cụ thể này nhưng tôi có ví dụ làm việc khác và nó sẽ làm việc tốt theo JPA docs. Trong trường hợp này, nhà cung cấp JPA bị dồn vào chân tường: nó phải biết giá trị của identifier trước khi nó có thể đặt đối tượng trong bộ sưu tập. Bên cạnh đó, các đối tượng hashCodeequals của đối tượng thậm chí không được gọi vì ánh xạ được nhà cung cấp JPA xử lý rõ ràng.

Đây là trường hợp trong đó buộc công cụ phải hiểu cách mọi thứ được mô hình hóa và liên quan với nhau dẫn đến lợi ích to lớn.

0

Cách diễn giải của bạn là chính xác. Bước đầu tiên đầu tiên là mã số hashCode()equals() của bạn với trường id - trường bạn đang nói Hibernate là id của bạn.

Bước thứ hai triển khai chính xác hashCode()equals() để giúp bạn tránh khỏi sự cố trong tương lai. Có rất nhiều tài nguyên nếu bạn google nó. Here là một trên trang web này

8

Không, bạn không làm bất cứ điều gì câm. Việc thực hiện equals và hashCode trên một thực thể JPA là vấn đề của nhiều debate được làm nóng, và tất cả các cách tiếp cận mà tôi biết đều có những hạn chế đáng kể. Không có giải pháp rõ ràng, tầm thường nào mà bạn vừa bỏ lỡ.

Bạn có, tuy nhiên, nhấn vào trường hợp không được thảo luận rất nhiều vì một lý do nào đó. Wiki hibernate recommends sử dụng khóa kinh doanh như bạn đang làm và "Java Persistence with Hibernate" (Bauer/King, 2007, được coi là công việc tham khảo Hibernate tiêu chuẩn) trên trang 398 đề xuất cùng một điều.Nhưng trong một số trường hợp, như bạn quan sát, Hibernate có thể thêm một thực thể vào một Set trước khi các trường của nó được khởi tạo, do đó, hashCode dựa trên khóa doanh nghiệp không hoạt động, giống như bạn chỉ ra. Xem Hibernate issue HHH-3799 để thảo luận về trường hợp này. Có một dự kiến ​​để thất bại test case trong mã nguồn Hibernate chứng minh vấn đề, thêm vào năm 2010, do đó, ít nhất một nhà phát triển Hibernate coi nó là một lỗi và muốn sửa chữa nó, nhưng chưa có bất kỳ hoạt động nào kể từ năm 2010 Vui lòng xem xét bỏ phiếu cho vấn đề đó.

Một giải pháp mà bạn có thể xem xét là mở rộng phạm vi phiên của bạn để tất cả quyền truy cập của bạn vào các thực thể xảy ra trong cùng một phiên. Sau đó, bạn có thể làm cho số Set<EntityThree> trở nên lười biếng, thay vì háo hức, và bạn sẽ tránh được vấn đề háo hức trong HHH-3799. Hầu hết các ứng dụng tôi đã làm việc trên chỉ làm cho việc sử dụng các đối tượng trong tình trạng tách rời. Có vẻ như bạn đang tải thực thể của mình và sau đó sử dụng nó một lúc sau khi phiên kết thúc; đó là một mô hình tôi khuyên bạn nên chống lại. Nếu bạn đang viết một ứng dụng web, hãy xem mẫu "phiên mở trong chế độ xem" và OpenSessionInViewFilter của Spring để biết các ý tưởng về cách thực hiện điều này.

Ngẫu nhiên, tôi thích cách bạn ném ngoại lệ khi khóa doanh nghiệp không được khởi tạo; theo cách đó bạn có thể bắt gặp lỗi mã hóa nhanh chóng. Ứng dụng của chúng tôi có một lỗi khó chịu do HHH-3799 mà chúng tôi có thể đã bị phát hiện nếu chúng tôi đã sử dụng xác nhận không null của bạn.

+0

Thật không may để làm cho nó hoạt động tôi đã phải loại bỏ khẳng định. Tôi nghĩ rằng nó không thành công ngay cả trong cùng một giao dịch, tôi phải kiểm tra lại. Cảm ơn vì lời đáp sâu sắc! –