Как удалить объект с отношениями ManyToMany в JPA (и соответствующие строки таблицы соединений)?

Скажем, у меня есть два объекта: Group и User. Каждый пользователь может быть членом многих групп, и каждая группа может иметь много пользователей.

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

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

Теперь я хочу удалить группу (допустим, у нее много членов).

Проблема заключается в том, что когда я вызываю EntityManager.remove() в какой-либо группе, поставщик JPA (в моем случае Hibernate) не удаляет строки из таблицы соединений, а операция удаления не выполняется из-за ограничений внешнего ключа. Вызов функции remove() для пользователя работает отлично (я думаю, это имеет отношение к владению стороной отношений).

Итак, как я могу удалить группу в этом случае?

Единственный способ, которым я мог придумать, - загрузить всех пользователей в группу, а затем для каждого пользователя удалить текущую группу из своих групп и обновить пользователя. Но мне кажется смешным вызывать update() для каждого пользователя из группы, чтобы удалить эту группу.

Ответ 1

  • Собственность на отношение определяется тем, где вы помещаете атрибут "mappedBy" в аннотацию. Объект, который вы помещаете 'mappedBy', является тем, который НЕ является владельцем. У обеих сторон нет шансов стать владельцами. Если у вас нет прецедента "удалить пользователя", вы можете просто перенести собственность на объект Group, так как в настоящее время владельцем является User.
  • С другой стороны, вы не спрашивали об этом, но нужно знать одно. groups и users не объединены друг с другом. Я имею в виду, что после удаления экземпляра User1 из Group1.users коллекции User1.groups не изменяются автоматически (что для меня совершенно неожиданно),
  • В общем, я бы предложил вам решить, кто является владельцем. Скажем, User является владельцем. Затем при удалении пользователя пользовательская группа отношений будет автоматически обновляться. Но при удалении группы вы должны сами позаботиться об удалении отношения:

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

Ответ 2

Следующие работы для меня. Добавьте следующий объект в объект, который не является владельцем отношения (Group)

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

Имейте в виду, что для этого необходимо, чтобы у Группы был обновленный список пользователей (это не делается автоматически). поэтому каждый раз, когда вы добавляете группу в список групп в пользовательском сущности, вы также должны добавить пользователя в список пользователей в объекте Group.

Ответ 3

Я нашел возможное решение, но... Я не знаю, хорошее ли это решение.

@Entity
public class Role extends Identifiable {

    @ManyToMany(cascade ={CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REFRESH})
    @JoinTable(name="Role_Permission",
            [email protected](name="Role_id"),
            [email protected](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",
            [email protected](name="Permission_id"),
            [email protected](name="Role_id")
        )
    public List<Role> getRoles() {
        return roles;
    }

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

Я пробовал это, и он работает. Когда вы удаляете Роль, также удаляются отношения (но не объекты Permission), а когда вы удаляете Permission, отношения с ролью также удаляются (но не экземпляр Role). Но мы сопоставляем однонаправленное отношение два раза, и оба объекта являются владельцем отношения. Может ли это вызвать некоторые проблемы для спящего режима? Какой тип проблем?

Спасибо!

Код выше относится к другому сообщению .

Ответ 4

В качестве альтернативы решениям JPA/Hibernate вы можете использовать предложение CASCADE DELETE в определении базы данных вашего ключа foregin в вашей таблице соединений, например (синтаксис Oracle):

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

Таким образом, СУБД автоматически удаляет строку, указывающую на группу при удалении группы. И он работает независимо от того, выполняется ли удаление из Hibernate/JPA, JDBC вручную в БД или любым другим способом.

функция удаления каскадов поддерживается всеми основными СУБД (Oracle, MySQL, SQL Server, PostgreSQL).

Ответ 5

Для чего это стоит, я использую EclipseLink 2.3.2.v20111125-r10461, и если у меня есть однонаправленное отношение @ManyToMany, я наблюдаю описанную вами проблему. Однако, если я изменю его на двунаправленное отношение @ManyToMany, я могу удалить объект с не-владеющей стороны, и таблица JOIN обновляется соответствующим образом. Это все без использования каких-либо каскадных атрибутов.

Ответ 6

Это хорошее решение. Лучшая часть на стороне SQL - тонкая настройка на любой уровень проста.

Я использовал MySql и MySql Workbench для Cascade для удаления для требуемого внешнего ключа.

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

Ответ 7

В моем случае я удалил mappedBy и соединил таблицы следующим образом:

@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;

Ответ 8

Это работает для меня:

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

    // Other business logic

    groupRepository.delete(group);
}

Кроме того, отметьте метод @Transactional (org.springframework.transaction.annotation.Transactional), это сделает весь процесс за один сеанс, сэкономит некоторое время.