JPA (및 해당 조인 테이블 행)에서 ManyToMany 관계가있는 엔티티를 제거하는 방법은 무엇입니까?
그룹과 사용자라는 두 개의 엔티티가 있다고 가정 해 보겠습니다. 모든 사용자는 여러 그룹의 구성원이 될 수 있으며 모든 그룹은 여러 사용자를 가질 수 있습니다.
@Entity
public class User {
@ManyToMany
Set<Group> groups;
//...
}
@Entity
public class Group {
@ManyToMany(mappedBy="groups")
Set<User> users;
//...
}
이제 그룹을 제거하고 싶습니다 (구성원이 많다고 가정 해 보겠습니다).
문제는 일부 그룹에서 EntityManager.remove ()를 호출 할 때 JPA 공급자 (내 경우에는 Hibernate) 가 조인 테이블에서 행을 제거하지 않고 외래 키 제한으로 인해 삭제 작업이 실패한다는 것입니다. 사용자에 대해 remove ()를 호출하면 잘 작동합니다 (나는 이것이 관계의 소유와 관련이 있다고 생각합니다).
그렇다면이 경우 어떻게 그룹을 제거 할 수 있습니까?
내가 생각 해낼 수있는 유일한 방법은 그룹의 모든 사용자를로드 한 다음 모든 사용자에 대해 그룹에서 현재 그룹을 제거하고 사용자를 업데이트하는 것입니다. 그러나이 그룹을 삭제할 수 있도록 그룹의 모든 사용자에 대해 update ()를 호출하는 것은 우스꽝스러운 것 같습니다.
- 관계의 소유권은 주석에 'mappedBy'속성을 배치하는 위치에 따라 결정됩니다. 'mappedBy'를 넣은 엔티티는 소유자가 아닙니다. 양측이 모두 소유자가 될 기회는 없습니다. '사용자 삭제'사용 사례가없는 경우
Group
현재User
소유자이므로 소유권을 엔티티 로 간단히 이동할 수 있습니다. - 반면에, 당신은 그것에 대해 묻지 않았지만 알아야 할 가치가 있습니다.
groups
와는users
서로 결합되지 않습니다. 즉, Group1.users에서 User1 인스턴스를 삭제 한 후 User1.groups 컬렉션이 자동으로 변경되지 않습니다. - 대체로 누가 소유자인지 결정하는 것이 좋습니다.
User
주인이 있다고합시다 . 그런 다음 사용자를 삭제할 때 관련 사용자 그룹이 자동으로 업데이트됩니다. 그러나 그룹을 삭제할 때는 다음과 같이 관계를 삭제해야합니다.
entityManager.remove(group)
for (User user : group.users) {
user.groups.remove(group);
}
...
// then merge() and flush()
다음은 나를 위해 작동합니다. 관계 (그룹)의 소유자가 아닌 엔티티에 다음 메소드를 추가하십시오.
@PreRemove
private void removeGroupsFromUsers() {
for (User u : users) {
u.getGroups().remove(this);
}
}
이것이 작동하려면 그룹에 업데이트 된 사용자 목록이 있어야합니다 (자동으로 수행되지 않음). 따라서 사용자 엔터티의 그룹 목록에 그룹을 추가 할 때마다 그룹 엔터티의 사용자 목록에도 사용자를 추가해야합니다.
가능한 해결책을 찾았지만 ... 좋은 해결책인지 모르겠습니다.
@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;
}
나는 이것을 시도했고 작동합니다. 역할을 삭제하면 관계도 삭제되고 (권한 엔터티 제외) 권한을 삭제하면 역할과의 관계도 삭제됩니다 (역할 인스턴스는 제외되지 않음). 그러나 우리는 단방향 관계를 두 번 매핑하고 두 엔티티가 관계의 소유자입니다. 이것이 Hibernate에 문제를 일으킬 수 있습니까? 어떤 유형의 문제입니까?
감사!
위의 코드는 관련된 다른 게시물 에서 가져온 것 입니다.
As an alternative to JPA/Hibernate solutions : you could use a CASCADE DELETE clause in the database definition of your foregin key on your join table, such as (Oracle syntax) :
CONSTRAINT fk_to_group
FOREIGN KEY (group_id)
REFERENCES group (id)
ON DELETE CASCADE
That way the DBMS itself automatically deletes the row that points to the group when you delete the group. And it works whether the delete is made from Hibernate/JPA, JDBC, manually in the DB or any other way.
the cascade delete feature is supported by all major DBMS (Oracle, MySQL, SQL Server, PostgreSQL).
For what its worth, I am using EclipseLink 2.3.2.v20111125-r10461 and if I have a @ManyToMany unidirectional relationship I observe the problem that you describe. However, if I change it to be a bi-directional @ManyToMany relationship I am able to delete an entity from the non-owning side and the JOIN table is updated appropriately. This is all without the use of any cascade attributes.
This is a good solution. The best part is on the SQL side – fine tuning to any level is easy.
I used MySql and MySql Workbench to Cascade on delete for the Required Foreign KEY.
ALTER TABLE schema.joined_table
ADD CONSTRAINT UniqueKey
FOREIGN KEY (key2)
REFERENCES schema.table1 (id)
ON DELETE CASCADE;
For my case, I deleted the mappedBy and joined tables like this:
@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;
This works for me:
@Transactional
public void remove(Integer groupId) {
Group group = groupRepository.findOne(groupId);
group.getUsers().removeAll(group.getUsers());
// Other business logic
groupRepository.delete(group);
}
Also, mark the method @Transactional (org.springframework.transaction.annotation.Transactional), this will do whole process in one session, saves some time.
'Program Tip' 카테고리의 다른 글
vi / vim에서 돌아 가기 (ctrl + z) 방법 (0) | 2020.10.07 |
---|---|
LocalDate 및 LocalDateTime에서 Epoch를 추출하는 방법은 무엇입니까? (0) | 2020.10.07 |
Safari의 iPad WebApp 전체 화면 (0) | 2020.10.07 |
정규식 및 역 참조로 Ruby switch 문 (case… when)을 작성하는 방법은 무엇입니까? (0) | 2020.10.07 |
항목 X에 대한 게시 속성을 적용 할 수 없습니다. (0) | 2020.10.07 |