Làm cách nào để xóa thực thể có mối quan hệ ManyToMany trong JPA (và các hàng bảng nối tương ứng)?


91

Giả sử tôi có hai thực thể: Nhóm và Người dùng. Mọi người dùng có thể là thành viên của nhiều nhóm và mọi nhóm đều có thể có nhiều người dùng.

@Entity
public class User {
    @ManyToMany
    Set<Group> groups;
    //...
}

@Entity
public class Group {
    @ManyToMany(mappedBy="groups")
    Set<User> users;
    //...
}

Bây giờ tôi muốn xóa một nhóm (giả sử nó có nhiều thành viên).

Vấn đề là khi tôi gọi EntityManager.remove () trên một số Nhóm, nhà cung cấp JPA (trong trường hợp của tôi là Hibernate) không xóa các hàng khỏi bảng tham gia và thao tác xóa không thành công do các ràng buộc khóa ngoại. Gọi remove () trên Người dùng hoạt động tốt (tôi đoán điều này có liên quan đến phía sở hữu của mối quan hệ).

Vậy làm cách nào để xóa nhóm trong trường hợp này?

Chỉ có cách tôi có thể nghĩ ra là tải tất cả người dùng trong nhóm, sau đó đối với mỗi người dùng, hãy xóa nhóm hiện tại khỏi nhóm của mình và cập nhật người dùng. Nhưng tôi thấy thật nực cười khi gọi update () trên mọi người dùng từ nhóm chỉ để có thể xóa nhóm này.

Câu trả lời:


86
  • Quyền sở hữu của mối quan hệ được xác định bởi vị trí bạn đặt thuộc tính 'mappedBy' vào chú thích. Thực thể bạn đặt 'mappedBy' là thực thể KHÔNG phải là chủ sở hữu. Không có cơ hội để cả hai bên trở thành chủ sở hữu. Nếu bạn không có trường hợp sử dụng 'xóa người dùng', bạn có thể chỉ cần chuyển quyền sở hữu sang Groupthực thể, vì hiện tại người đó Userlà chủ sở hữu.
  • Mặt khác, bạn chưa hỏi về nó, nhưng có một điều đáng để biết. Các groupsuserskhông được kết hợp với nhau. Ý tôi là, sau khi xóa cá thể User1 khỏi Group1.users, bộ sưu tập User1.groups không tự động thay đổi (điều này khá ngạc nhiên đối với tôi),
  • Nói chung, tôi sẽ đề nghị bạn quyết định ai là chủ sở hữu. Giả sử Userlà chủ sở hữu. Sau đó, khi xóa người dùng, nhóm người dùng quan hệ sẽ được cập nhật tự động. Nhưng khi xóa một nhóm, bạn phải tự xóa mối quan hệ như sau:

entityManager.remove(group)
for (User user : group.users) {
     user.groups.remove(group);
}
...
// then merge() and flush()

Thnx! Tôi đã gặp vấn đề tương tự và giải pháp của bạn đã giải quyết được nó. Nhưng tôi phải biết nếu có một cách khác để giải quyết vấn đề này. Nó tạo ra một mã khủng khiếp. Tại sao nó không thể là em.remove (entity) và đó là nó?
Royi Freifeld

2
Điều này có được tối ưu hóa ở hậu trường không? vì tôi không muốn truy vấn toàn bộ tập dữ liệu.
Ced

1
Đây thực sự là cách tiếp cận không tốt, nếu bạn có vài nghìn người dùng trong nhóm đó thì sao?
Sergiy Sokolenko

2
Điều này không cập nhật bảng quan hệ, trong bảng quan hệ, các hàng về quan hệ này vẫn còn. Tôi đã không kiểm tra nhưng Điều đó có thể gây ra sự cố.
Saygın Doğu

@SergiySokolenko không có truy vấn cơ sở dữ liệu nào được thực thi trong vòng lặp for đó. Tất cả chúng đều được xử lý theo lô sau khi chức năng Giao dịch bị bỏ lại.
Lò nung

42

Những điều sau đây làm việc cho tôi. Thêm phương thức sau vào thực thể không phải là chủ sở hữu của mối quan hệ (Nhóm)

@PreRemove
private void removeGroupsFromUsers() {
    for (User u : users) {
        u.getGroups().remove(this);
    }
}

Hãy nhớ rằng để việc này hoạt động, Nhóm phải có danh sách Người dùng được cập nhật (không được thực hiện tự động). vì vậy mỗi khi bạn thêm Nhóm vào danh sách nhóm trong thực thể Người dùng, bạn cũng nên thêm Người dùng vào danh sách người dùng trong thực thể Nhóm.


1
cascade = CascadeType.ALL không nên được đặt khi bạn muốn sử dụng giải pháp này. Nếu không, nó hoạt động hoàn hảo!
ltsstar

@damian Xin chào, tôi đã sử dụng giải pháp của bạn, nhưng tôi đã gặp lỗi concurrentModficationException, tôi nghĩ như bạn đã chỉ ra lý do có thể là Nhóm phải có danh sách Người dùng được cập nhật. Bởi vì nếu tôi thêm nhiều hơn một nhóm người sử dụng tôi nhận được ngoại lệ này ...
Adnan Abdul Khaliq

@Damian Hãy nhớ rằng để điều này hoạt động, Nhóm phải có danh sách Người dùng được cập nhật (không được thực hiện tự động). vì vậy mỗi khi bạn thêm Nhóm vào danh sách nhóm trong thực thể Người dùng, bạn cũng nên thêm Người dùng vào danh sách người dùng trong thực thể Nhóm. Bạn có thể xin hãy giải thích về vấn đề này, cho tôi một ví dụ ,, thnks
Adnan Abdul Khaliq

27

Tôi đã tìm thấy một giải pháp khả thi, nhưng ... tôi không biết đó có phải là một giải pháp tốt hay không.

@Entity
public class Role extends Identifiable {

    @ManyToMany(cascade ={CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REFRESH})
    @JoinTable(name="Role_Permission",
            joinColumns=@JoinColumn(name="Role_id"),
            inverseJoinColumns=@JoinColumn(name="Permission_id")
        )
    public List<Permission> getPermissions() {
        return permissions;
    }

    public void setPermissions(List<Permission> permissions) {
        this.permissions = permissions;
    }
}

@Entity
public class Permission extends Identifiable {

    @ManyToMany(cascade = {CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REFRESH})
    @JoinTable(name="Role_Permission",
            joinColumns=@JoinColumn(name="Permission_id"),
            inverseJoinColumns=@JoinColumn(name="Role_id")
        )
    public List<Role> getRoles() {
        return roles;
    }

    public void setRoles(List<Role> roles) {
        this.roles = roles;
    }

Tôi đã thử điều này và nó hoạt động. Khi bạn xóa Vai trò, các quan hệ cũng bị xóa (nhưng không phải đối tượng Quyền) và khi bạn xóa Quyền, các quan hệ với Vai trò cũng bị xóa (nhưng không phải đối tượng Vai trò). Nhưng chúng ta đang ánh xạ một mối quan hệ một chiều hai lần và cả hai thực thể đều là chủ sở hữu của mối quan hệ. Điều này có thể gây ra một số vấn đề với Hibernate? Loại vấn đề nào?

Cảm ơn!

Đoạn mã trên là từ một bài đăng khác có liên quan.


vấn đề làm mới bộ sưu tập?
jelies

12
Điều này LAF không đúng. ManyToMany phải có một và chỉ một phía chủ sở hữu của mối quan hệ. Giải pháp này khiến cả hai bên đều là chủ sở hữu và cuối cùng sẽ dẫn đến các bản ghi trùng lặp. Để tránh bản ghi trùng lặp, Bộ phải được sử dụng thay vì Danh sách. Nhưng, sử dụng Set chỉ là một giải pháp thay thế cho việc làm điều không được khuyến khích.
L. Holanda

4
thì cách thực hành tốt nhất cho một cây cảnh phổ biến như vậy là gì?
SalutonMondo,

8

Để thay thế cho các giải pháp JPA / Hibernate: bạn có thể sử dụng mệnh đề CASCADE DELETE trong định nghĩa cơ sở dữ liệu của khóa foregin trên bảng nối của bạn, chẳng hạn như (cú pháp Oracle):

CONSTRAINT fk_to_group
     FOREIGN KEY (group_id)
     REFERENCES group (id)
     ON DELETE CASCADE

Bằng cách đó, chính DBMS sẽ tự động xóa hàng trỏ đến nhóm khi bạn xóa nhóm. Và nó hoạt động cho dù việc xóa được thực hiện từ Hibernate / JPA, JDBC, theo cách thủ công trong DB hoặc bất kỳ cách nào khác.

tính năng xóa theo tầng được hỗ trợ bởi tất cả các DBMS chính (Oracle, MySQL, SQL Server, PostgreSQL).


3

Đối với giá trị của nó, tôi đang sử dụng EclipseLink 2.3.2.v20111125-r10461 và nếu tôi có mối quan hệ đơn hướng @ManyToMany, tôi quan sát thấy vấn đề mà bạn mô tả. Tuy nhiên, nếu tôi thay đổi nó thành mối quan hệ @ManyToMany hai chiều, tôi có thể xóa một thực thể khỏi phía không sở hữu và bảng JOIN được cập nhật một cách thích hợp. Đây là tất cả mà không cần sử dụng bất kỳ thuộc tính thác nào.


2

Điều này phù hợp với tôi:

@Transactional
public void remove(Integer groupId) {
    Group group = groupRepository.findOne(groupId);
    group.getUsers().removeAll(group.getUsers());

    // Other business logic

    groupRepository.delete(group);
}

Ngoài ra, hãy đánh dấu phương thức @Transactional (org.springframework.transaction.annotation.Transactional), điều này sẽ thực hiện toàn bộ quá trình trong một phiên, tiết kiệm thời gian.


1

Đây là một giải pháp tốt. Phần tốt nhất là ở phía SQL - việc tinh chỉnh ở bất kỳ cấp độ nào cũng dễ dàng.

Tôi đã sử dụng MySql và MySql Workbench để Cascade khi xóa đối với KEY nước ngoài được yêu cầu.

ALTER TABLE schema.joined_table 
ADD CONSTRAINT UniqueKey
FOREIGN KEY (key2)
REFERENCES schema.table1 (id)
ON DELETE CASCADE;

Chào mừng đến với SO. Hãy dành một chút thời gian và nhìn vào này để cải thiện định dạng của bạn và đọc bằng chứng: stackoverflow.com/help/how-to-ask
petezurich

1

Đây là những gì tôi đã kết thúc. Hy vọng rằng ai đó có thể thấy nó hữu ích.

@Transactional
public void deleteGroup(Long groupId) {
    Group group = groupRepository.findById(groupId).orElseThrow();
    group.getUsers().forEach(u -> u.getGroups().remove(group));
    userRepository.saveAll(group.getUsers());
    groupRepository.delete(group);
}

0

Đối với trường hợp của tôi, tôi đã xóa mappedBy và nối các bảng như sau:

@ManyToMany(cascade = CascadeType.ALL)
@JoinTable(name = "user_group", joinColumns = {
        @JoinColumn(name = "user", referencedColumnName = "user_id")
}, inverseJoinColumns = {
        @JoinColumn(name = "group", referencedColumnName = "group_id")
})
private List<User> users;

@ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
@JsonIgnore
private List<Group> groups;

3
Không sử dụng cascadeType.all trong nhiều-đến-nhiều. Khi xóa, nó khiến bản ghi A xóa tất cả các bản ghi B được liên kết. Và các bản ghi B xóa tất cả các bản ghi A liên quan. Và như thế. Không-không.
Josiah

0

Điều này phù hợp với tôi về một vấn đề tương tự mà tôi không thể xóa người dùng do tham chiếu. Cảm ơn bạn

@ManyToMany(cascade = {CascadeType.MERGE, CascadeType.PERSIST,CascadeType.REFRESH})
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.