Подтвердить что ты не робот

Как сохранить @ManyToMany отношение - дублировать запись или отдельный объект

Я хочу сохранить свою сущность с помощью отношения ManyToMany. Но у меня есть проблемы во время сохраняющегося процесса.

Мои объекты:

@Entity
@Table(name = "USER")
public class User implements Serializable {

    private static final long serialVersionUID = 1L;

    @Id
    @Column(name = "ID")
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    Long userId;

    @Column(name = "NAME", unique = true, nullable = false)
    String userName;

    @Column(name = "FORNAME")
    String userForname;

    @Column(name = "EMAIL")
    String userEmail;

    @ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
    @JoinTable(name = "USER_USER_ROLES", joinColumns = @JoinColumn(name = "ID_USER"), inverseJoinColumns = @JoinColumn(name = "ID_ROLE"))
    List<UserRoles> userRoles = new ArrayList<UserRoles>();

    // getter et setter
}

и

@Entity
@Table(name = "USER_ROLES")
public class UserRoles implements Serializable {

    private static final long serialVersionUID = 1L;

    @Id
    @Column(name = "ID")
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    Long userRolesId;

    @Column(unique = true, nullable = false, name = "ROLE_NAME")
    String roleName; 

    // getter et setter
}

Код службы:

User user = new User();
UserRoles role;
try {
    role = userRolesServices.getUserRoleByName("ROLE_USER"); // find jpql - transaction
} catch (RuntimeException e) {
    LOGGER.debug("No Roles found");
    role = new UserRoles("ROLE_USER"); // create new
}
user.addUserRole(role);
user.setUserName(urlId);
user.setUserForname(fullName);
user.setUserEmail(email);
userServices.createUser(user); // em.persist(user) - transaction

В первый раз, когда я пытаюсь сохранить пользователя с UserRoles "ROLE_USER", не проблема. Вставляются пользовательские и UserRoles и таблицы объединений.

Моя проблема в том, что я пытаюсь сохранить второго пользователя с теми же UserRoles. Я проверяю, существует ли UserRoles, найдя его (userRolesServices.getUserRoleByName(...)). Если существует → добавить этот список UserRoles в User (id + имя роли) else, я создаю новый (только имя роли).

Когда я пытаюсь сохранить второго пользователя, я получаю следующее исключение: ", чтобы сохранить:..... UserRoles" (возможно, потому что getUserRoleByName выполняется в другой транзакции)

Если я не использую getUserRoleByName (только * new UserRoles ( "ROLE_USER" ); *), я получаю следующее исключение: "... ConstraintViolation: Дублированная запись для 'ROLE_NAME'..."

Итак, как правильно сохранить объект с отношением @ManyToMany?

4b9b3361

Ответ 1

Для вышеуказанной проблемы я бы сказал, что ваш каскад отношений с сущностью неверен. Рассмотрим это: пользователь может иметь несколько ролей, но может быть зафиксировано число ролей, которые могут существовать в системе. Таким образом, CASCADE ALL из User не имеет никакого смысла, поскольку жизненный цикл UserRoles не должен зависеть от жизненного цикла объекта User. Например. когда мы удаляем User, UserRoles не следует удалять.

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

Удалите каскад, и ваша проблема будет решена. Единственное, что вам нужно решить, - это то, как вы собираетесь вставлять роли пользователя. По моему мнению, для этого должны быть отдельные функции.

Также не используйте ArrayList, используйте HashSet. ArrayList позволяет дублировать.

Ответ 2

Я предоставлю свой ответ, если у кого-нибудь возникнут проблемы со мной и автором.

В основном я столкнулся с ситуацией, когда у меня была одна таблица, которая представляла собой ПОСТОЯННЫЕ значения. А другой изменится, но он должен отобразить (many to many) эти КОНСТАНТЫ.

Точная проблема USERS, а ROLES.

Диаграмма

ROLES будет известен и добавлен при запуске системы, поэтому они никогда не должны удалять . Даже если у пользователя не будет Role, он должен оставаться в системе.

Реализация класса с использованием JPA:

Пользователь

@Entity
@Table(name = "USERS")
public class User{

    @Id
    private String login;
    private String name;
    private String password;

    @ManyToMany(cascade = {CascadeType.MERGE})
    private Set<Role> roles = new HashSet<>();

Роль

@Entity
@Table(name = "ROLE")
public class Role {

    @Id
    @Enumerated(value = EnumType.STRING)
    private RoleEnum name;

    @ManyToMany(mappedBy = "roles")
    private Set<User> users = new HashSet<>();

Использование

Эта настройка легко добавит/удалит Role в User. Просто передав массив, f.e.: user.getRoles().add(new Role("ADMIN")); и merge User. Удаление работы с передачей пустого списка.

Если вы забыли добавить Role, прежде чем добавлять его к пользователю, скорее всего, вы получите сообщение об ошибке:

javax.persistence.RollbackException: java.lang.IllegalStateException: During synchronization a new object was found through a relationship that was not marked cascade PERSIST: [email protected]

Что и почему

  • Атрибут
  • mappedBy добавляется к дочернему Entity, как описано в JPA Docs

Если вы решите сопоставить отношения в обоих направлениях, то один направление должно быть определено как владелец , а другое должно использовать mappedBy для определения его отображения (...)

  • cascade = {CascadeType.MERGE} добавлен для правильных каскадов JPA Docs

Каскадирование операции EntityManager.merge(). Если merge() вызывается родительский, тогда ребенок также будет объединен. Обычно это должно быть для зависимых отношений. Обратите внимание, что это влияет только на каскадирование слияния, сама ссылка на отношения всегда будет быть объединенным.

Ответ 3

(возможно, потому что getUserRoleByName выполняется в другой транзакции)

Как бы это ни казалось, сделайте запрос в том же менеджере транзакций/сущностей. В противном случае перейдите в текущую транзакцию, используя find().