Powiedzmy, że mam dwie jednostki: grupę i użytkownika. Każdy użytkownik może być członkiem wielu grup, a każda grupa może mieć wielu użytkowników.
@Entity
public class User {
@ManyToMany
Set<Group> groups;
//...
}
@Entity
public class Group {
@ManyToMany(mappedBy="groups")
Set<User> users;
//...
}
Teraz chcę usunąć grupę (powiedzmy, że ma wielu członków).
Problem polega na tym, że kiedy wywołuję EntityManager.remove () na jakiejś grupie, dostawca JPA (w moim przypadku Hibernate) nie usuwa wierszy z tabeli łączenia, a operacja usuwania nie udaje się z powodu ograniczeń klucza obcego. Wywołanie funkcji remove () na User działa dobrze (przypuszczam, że ma to coś wspólnego z posiadaniem strony relacji).
Jak więc mogę usunąć grupę w tym przypadku?
Jedynym sposobem, jaki mogłem wymyślić, jest załadowanie wszystkich użytkowników w grupie, a następnie dla każdego użytkownika usunięcie bieżącej grupy ze swoich grup i zaktualizowanie użytkownika. Ale wydaje mi się absurdalne wywoływanie funkcji update () na każdym użytkowniku z grupy tylko po to, aby móc usunąć tę grupę.
Poniższe działa dla mnie. Dodaj następującą metodę do jednostki, która nie jest właścicielem relacji (grupa)
@PreRemove private void removeGroupsFromUsers() { for (User u : users) { u.getGroups().remove(this); } }
Należy pamiętać, że aby to zadziałało, grupa musi mieć zaktualizowaną listę użytkowników (co nie jest wykonywane automatycznie). więc za każdym razem, gdy dodajesz grupę do listy grup w encji Użytkownik, należy również dodać użytkownika do listy użytkowników w encji Grupa.
źródło
Znalazłem możliwe rozwiązanie, ale ... nie wiem, czy to dobre rozwiązanie.
@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; }
Próbowałem tego i działa. Po usunięciu roli usuwane są również relacje (ale nie jednostki uprawnień), a po usunięciu uprawnień relacje z rolą również są usuwane (ale nie instancja roli). Ale odwzorowujemy relację jednokierunkową dwa razy i oba podmioty są właścicielem relacji. Czy może to spowodować problemy z hibernacją? Jaki rodzaj problemów?
Dzięki!
Powyższy kod pochodzi z innego powiązanego postu .
źródło
Jako alternatywę dla rozwiązań JPA / Hibernate: możesz użyć klauzuli CASCADE DELETE w definicji bazy danych klucza obcego w tabeli łączenia, na przykład (składnia Oracle):
CONSTRAINT fk_to_group FOREIGN KEY (group_id) REFERENCES group (id) ON DELETE CASCADE
W ten sposób sam DBMS automatycznie usuwa wiersz wskazujący na grupę, kiedy usuwasz grupę. I działa niezależnie od tego, czy usuwanie jest dokonywane z Hibernate / JPA, JDBC, ręcznie w DB, czy w inny sposób.
funkcja usuwania kaskadowego jest obsługiwana przez wszystkie główne DBMS (Oracle, MySQL, SQL Server, PostgreSQL).
źródło
Co jest warte, używam EclipseLink 2.3.2.v20111125-r10461 i jeśli mam relację jednokierunkową @ManyToMany, obserwuję opisywany przez ciebie problem. Jeśli jednak zmienię go na relację dwukierunkową @ManyToMany, jestem w stanie usunąć jednostkę ze strony niebędącej właścicielem, a tabela JOIN zostanie odpowiednio zaktualizowana. To wszystko bez użycia atrybutów kaskadowych.
źródło
To działa dla mnie:
@Transactional public void remove(Integer groupId) { Group group = groupRepository.findOne(groupId); group.getUsers().removeAll(group.getUsers()); // Other business logic groupRepository.delete(group); }
Zaznacz również metodę @Transactional (org.springframework.transaction.annotation.Transactional), to wykona cały proces w jednej sesji, oszczędza trochę czasu.
źródło
To jest dobre rozwiązanie. Najlepsze jest po stronie SQL - dostrojenie do dowolnego poziomu jest łatwe.
Użyłem MySql i MySql Workbench do kaskadowego usuwania wymaganego klucza obcego.
ALTER TABLE schema.joined_table ADD CONSTRAINT UniqueKey FOREIGN KEY (key2) REFERENCES schema.table1 (id) ON DELETE CASCADE;
źródło
Oto, co ostatecznie zrobiłem. Mam nadzieję, że ktoś uzna to za przydatne.
@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); }
źródło
W moim przypadku usunąłem mappedBy i dołączyłem do tabel w ten sposób:
@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;
źródło
Działa to dla mnie w podobnym problemie, w którym nie udało mi się usunąć użytkownika z powodu odniesienia. Dziękuję Ci
@ManyToMany(cascade = {CascadeType.MERGE, CascadeType.PERSIST,CascadeType.REFRESH})
źródło