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

74

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

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

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

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

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

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

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

Ваш Ответ

8   ответов
1

Тонкая настройка на любой уровень проста.

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

ALTER TABLE schema.joined_table 
ADD CONSTRAINT UniqueKey
FOREIGN KEY (key2)
REFERENCES schema.table1 (id)
ON DELETE CASCADE;
Добро пожаловать в ТАК. Пожалуйста, найдите время и посмотрите на это, чтобы улучшить форматирование и чтение корректуры:stackoverflow.com/help/how-to-ask
1

м:

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

вы можете использовать предложение 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).

24

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

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

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

Спасибо!

Код выше от другогосообщение связанные с.

Проблемы с обновлением коллекции?
Это не правильно. Двунаправленный ManyToMany должен иметь одну и только одну сторону владельца отношений. Это решение делает обе стороны в качестве владельцев и в конечном итоге приведет к дублированию записей. Чтобы избежать дублирования записей, наборы должны использоваться вместо списков. Но использование Set - это просто обходной путь для выполнения того, что не рекомендуется.
тогда каков наилучший метод для такого распространенного сценария?
Почему этот ответ не одобрен как одобренный?
3

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

36

который не является владельцем отношения (группа)

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

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

cascade = CascadeType.ALL не должен быть установлен, когда вы хотите использовать это решение. В противном случае это работает отлично!
71
The ownership of the relation is determined by where you place the 'mappedBy' attribute to the annotation. The entity you put 'mappedBy' is the one which is NOT the owner. There's no chance for both sides to be owners. If you don't have a 'delete user' use-case you could simply move the ownership to the Group entity, as currently the User is the owner. On the other hand, you haven't been asking about it, but one thing worth to know. The groups and users are not combined with each other. I mean, after deleting User1 instance from Group1.users, the User1.groups collections is not changed automatically (which is quite surprising for me), All in all, I would suggest you decide who is the owner. Let say the User is the owner. Then when deleting a user the relation user-group will be updated automatically. But when deleting a group you have to take care of deleting the relation yourself like this:
entityManager.remove(group)
for (User user : group.users) {
     user.groups.remove(group);
}
...
// then merge() and flush()
Это не обновляет таблицу отношений, в таблице отношений все еще остаются строки об этом отношении. Я не тестировал, но это могло вызвать проблемы.
Thnx! У меня была такая же проблема, и ваше решение ее решило. Но я должен знать, есть ли другой способ решить эту проблему. Это производит ужасный код. Почему это не может бытьem.remove(entity) и это все?
Это действительно плохой подход, что если в этой группе несколько тысяч пользователей?
Это оптимизировано за кулисами? Потому что я не хочу запрашивать весь набор данных.
0

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

Похожие вопросы