it-roy-ru.com

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

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

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

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

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

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

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

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

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

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

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

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

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

36
Damian

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

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

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

Спасибо!

Код выше взят из другого post related.

24
jelies

В качестве альтернативы решениям 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).

7
Pierre Henry

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

3
NBW

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

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

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

В моем случае я удалил 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;
1
szachMati

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

@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), это сделает весь процесс за один сеанс, сэкономит время.

0
Mehul Katpara