2008-10-07 40 views
20

Trong ứng dụng của tôi, tôi có các loại Hibernate ánh xạ (trường hợp chung):Tại sao Hibernate cố gắng xóa khi tôi cố gắng cập nhật/chèn?

class RoleRule { 
    private Role role; 
    private PermissionAwareEntity entity; // hibernate-mapped entity for which permission is granted 
    private PermissionType permissionType; // enum 

    @ManyToOne 
    @JoinColumn(name = "ROLE_ID") 
    public Role getRole() { 
    return role; 
    } 
    public void setRole(Role role) { 
    this.role = role; 
    } 

} 

class Role { 
    private Set<RoleRule> rules = new HashSet<RoleRule>(0); 

    @OneToMany(cascade=CascadeType.ALL) 
    @JoinColumn(name="ROLE_ID") 
    public Set<RoleRule> getRules() { 
    return rules; 
    } 
    public void setRules(Set<RoleRule> rules) { 
    this.rules = rules; 
    } 

} 

Tất cả các lớp có equals() & hashCode() ghi đè.

Ứng dụng của tôi cho phép tinh chỉnh vai trò (chỉ bởi sysadmin, đừng lo lắng) và trong số các trường khác, cho phép tạo quy tắc vai trò mới. Khi một quy tắc mới được tạo, tôi cố tạo đối tượng RoleRule mới và chèn nó vào trường của vai trò rules. Tôi gọi session.update(role) để áp dụng các thay đổi cho cơ sở dữ liệu.

Bây giờ đến phần xấu xí ... Hibernate quyết định làm như sau khi đóng cửa giao dịch và đỏ bừng:

  1. Chèn quy tắc mới vào cơ sở dữ liệu. Xuất sắc.
  2. Cập nhật các trường vai trò khác (không phải bộ sưu tập). Càng xa càng tốt.
  3. Cập nhật các quy tắc hiện tại, ngay cả khi không có gì thay đổi trong chúng. Tôi có thể sống với điều này.
  4. Cập nhật các quy tắc hiện tại lần nữa. Dưới đây là một dán từ nhật ký, trong đó có những nhận xét tự động:
/* delete one-to-many row Role.rules */ 
update ROLE_RULE set ROLE_ID=null where ROLE_ID=? and ROLE_RULE_ID=?

Tất nhiên, tất cả các lĩnh vực không phải là null, và hoạt động này không thành công ngoạn mục.

Có ai có thể cố gắng giải thích tại sao Hibernate lại làm điều này không ??? Và thậm chí quan trọng hơn, làm thế nào frak để tôi có được xung quanh này ???

EDIT: Tôi đã như vậy chắc chắn nó là cái gì để làm với các bản đồ, và sau đó ông chủ của tôi, trên một ý thích, xóa equals()hashCode() trong cả hai lớp, tái tạo chúng bằng Eclipse, và một cách bí ẩn này đã giải quyết được vấn đề .

Tôi vẫn rất tò mò về câu hỏi của mình. Bất cứ ai có thể đề nghị tại sao Hibernate sẽ làm điều này?

+0

Bạn đang sử dụng trình điều khiển và phương ngữ nào? –

+0

ứng dụng đang chạy trên Oracle 10g, nhưng điều đó không quan trọng. – Yuval

+0

Điều quan trọng nhất, việc thực hiện các phương thức 'equals' và' hashCode' bị thiếu trong bài viết của bạn. Xây dựng phương thức 'equals' và' hashCode' dựa trên giá trị nhận dạng cơ sở dữ liệu ** được tạo bởi hibernate hoặc cơ sở dữ liệu của bạn ** là một ý tưởng tồi, khiến chúng phụ thuộc vào bất kỳ trường nào có thể được cập nhật sau thời gian xây dựng thậm chí còn tồi tệ hơn. –

Trả lời

3

Tôi không chắc chắn nếu điều này là giải pháp, nhưng bạn có thể muốn thử:

@OneToMany(mappedBy = "role") 

Và không có chú thích @JoinColumn? Tôi nghĩ rằng cả hai thực thể đang cố gắng để 'sở hữu' hiệp hội, đó là lý do tại sao các SQL có thể được điều sai lầm?

Ngoài ra, nếu bạn muốn đảm bảo chỉ cột bị ảnh hưởng được cập nhật, bạn có thể sử dụng một chú thích ngủ đông cụ thể trên lớp:

@Entity 
@org.hibernate.annotations.Entity(
    dynamicInsert = true, dynamicUpdate = true 
) 
7

Tôi đã thường được sử dụng hai phương pháp của việc cập nhật một bộ sưu tập (nhiều bên của một-nhiều) trong Hibernate. Cách bạo lực là để xóa bộ sưu tập, gọi lưu trên phụ huynh, và sau đó gọi tuôn ra. Sau đó, thêm tất cả các thành viên của bộ sưu tập trở lại và gọi lưu lại vào phụ huynh. Điều này sẽ xóa tất cả và sau đó chèn tất cả. Sự tuôn ra ở giữa là chìa khóa vì nó buộc việc xóa xảy ra trước khi chèn. Nó có lẽ là tốt nhất để chỉ sử dụng phương pháp này trên các bộ sưu tập nhỏ vì nó tái chèn tất cả chúng.

Cách thứ hai khó mã hơn nhưng hiệu quả hơn.Bạn lặp qua các bộ mới của trẻ em và tự sửa đổi những người mà vẫn còn ở đó, xóa những cái mà không phải là, và sau đó thêm những cái mới. Trong mã giả sẽ là:

copy the list of existing records to a list_to_delete 
for each record from the form 
    remove it from the list_to_delete 
    if the record exists (based on equals()? key?) 
    change each field that the user can enter 
    else if the record doesn't exist 
    add it to the collection 
end for 
for each list_to_delete 
    remove it 
end for 
save 

Tôi đã tìm kiếm các diễn đàn Hibernate trong nhiều giờ cố gắng tìm đúng cách để giải quyết vấn đề này. Bạn sẽ có thể chỉ cập nhật bộ sưu tập của bạn để làm cho nó chính xác và sau đó lưu cha mẹ, nhưng như bạn đã phát hiện ra, Hibernate cố gắng tách con khỏi cha mẹ trước khi xóa chúng và nếu khóa ngoài không phải là null, nó sẽ thất bại.

+2

Đây có phải là giải pháp duy nhất không? Tôi nghĩ rằng toàn bộ các điểm của ngủ đông là để tránh phải mã riêng này cho từng đối tượng, và làm cho mã dễ dàng hơn để duy trì. Có cách nào để làm điều đó mà không cần viết một phương pháp cho từng đối tượng, ví dụ một tùy chọn ngủ đông cần phải được thiết lập? – pete

4

Xem câu trả lời của câu hỏi 'Overriding equals and hashCode in Java'.

Nó giải thích cách ghi đè phương thức equals và hashCode, điều này có vẻ là vấn đề của bạn khi nó hoạt động sau khi chúng được viết lại.

Ghi đè sai chúng có thể dẫn đến ngủ đông để xóa bộ sưu tập của bạn và lắp lại chúng. (vì khóa băm được sử dụng làm các phím trong bản đồ)

+4

Tôi nhận thức rõ tầm quan trọng của bằng và mã băm và cách ghi đè chúng. Bí ẩn lớn ở đây là cái cũ bằng + hashcode giống hệt với cái mới được tạo ... và điều này đã giải quyết được vấn đề của tôi. Bạn có thể giải thích điều đó không? – Yuval

0

Câu trả lời của Brian Deterling đã giúp tôi vượt qua việc xóa ma. Tôi ước anh ta đã đặt một mã thực sự. Đây là những gì tôi nhận được từ đề xuất của mình 1. Đăng cho một người nào đó sử dụng nó hoặc để bình luận mã của tôi.

// snFile and task share many to many relationship 

@PersistenceContext 
private EntityManager em; 

public SnFile merge(SnFile snFile) { 
     log.debug("Request to merge SnFile : {}", snFile); 

     Set<Task> tasks = taskService.findBySnFilesId(snFile.getId()); 
     if(snFile.getTasks() != null) { 
      snFile.getTasks().clear(); 
     } 
     em.merge(snFile); 
     em.flush(); 
     if(tasks != null) { 
      if(snFile.getTasks() != null) 
       snFile.getTasks().addAll(tasks); 
      else 
       snFile.setTasks(tasks); 
     } 

     return em.merge(snFile); 
    } 
Các vấn đề liên quan