Новые связанные записи Spring Boot и JPA обновляются вместо вставки

Я работаю над своим первым проектом с Spring Boot. Я пытаюсь использовать JPA для загрузки и редактирования таблиц user и user_role в базе данных MySQL. Каждый пользователь может иметь несколько ролей.

Я могу запросить существующего пользователя с несколькими ролями, но при сохранении нового пользователя с одной или несколькими ролями роль не сохраняется в таблице user_role. Данные отладки показывают вставку в user, за которой следуют операторы обновления таблицы user_role.

Сохраняемые записи новые, так почему же они не вставляются в user_role?

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

Код:

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

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String username;
    private String password;

    @OneToMany(fetch = FetchType.EAGER)
    @JoinColumn(name = "userId")
    private List<UserRole> userRoles;
...
}

@Entity
@Table(name = "user_role")
public class UserRole {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String rolename;
    private Long userId;
...
}

...
User user = new User(testUserName, testUserPassword1);
user.addUserRole("test");
user.addUserRole("user");
userRepository.save(user);
...

Отладочный вывод:

insert into user (password,username) values (?,?)
binding parameter (1:VARCHAR) <- [password]
binding parameter (2:VARCHAR) <- [Test User]
update user_role set user_id=? where id=?
binding parameter (1:BIGINT) <- [27]
binding parameter (2:BIGINT) <- [null]

Я ожидаю вставки с включенным именем роли. Таблицы user и user_role в настоящее время содержат только 1 и 2 строки соответственно. Должна появиться новая запись пользователя (есть), две новые записи user_role со сгенерированными идентификаторами и user_id, соответствующий новому пользователю.

Я лучше использую private User user; вместо private Long userId; в таблице UserRole затем добавьте mappedBy = "user" в свою @OneToMany (и удалите @JoinColumn). В любом случае, если у пользователя может быть несколько ролей, а роль может быть связана с несколькими пользователями, вам понадобится @ManyToMany.

Yann39 09.08.2024 09:14

Полностью согласен с @Yann39. Кроме того, при использовании двунаправленных отношений может быть недостаточно просто добавить роли вашему пользователю, а установить пользователя в объектах UserRole.

Karsten 09.08.2024 11:51

Спасибо, Yann39, я попробовал, но в user_role все равно ничего не было вставлено. Трассировка даже не показала вставки или обновления user_role. @OneToMany(fetch = FetchType.EAGER, mappedBy = "user")private List<UserRole> userRoles;@ManyToOne@JoinColumn(name = "user_id", insertable = false, updatable = false)private User user;

Steve 10.08.2024 00:12

На данный момент я склоняюсь к сохранению UserRole отдельно от User. По сути, мы оборачиваем методы сохранения и удаления для пользователя, чтобы также сохранять/удалять записи в таблице user_role.

Steve 10.08.2024 00:22

С атрибутом cascadeType это должно работать. Я опубликовал ответ, чтобы объяснить, что я имел в виду, поскольку он слишком длинный для комментария. Дайте мне знать, если это соответствует вашим потребностям.

Yann39 10.08.2024 11:48

Спасибо @Yann39! cascadeType — это то, чего мне не хватало. И, к вашему сведению, я собирался сделать что-то очень простое, и мне нужно было только имя роли (пользователь может иметь несколько ролей), поэтому user_role, по сути, представляет собой таблицу-мост, только без таблицы ролей на другой стороне. Итак, нет, это не удаление роли при удалении пользователя, а только ассоциация пользователя с ролью.

Steve 12.08.2024 23:11
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
0
6
50
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Если роль уникальна для каждого пользователя (поэтому вы хотите удалить роль при удалении пользователя), просто используйте двунаправленную ассоциацию «один-ко-многим»:

(звучит странно для меня, но кажется, это то, что вы пытаетесь сделать):

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

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String username;

    private String password;

    @OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true)
    private Set<UserRole> userRoles = new HashSet<>();

}
@Entity
@Table(name = "user_role")
public class UserRole {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String rolename;

    @ManyToOne
    @JoinColumn(name = "user_id")
    private User user;

}

Затем, чтобы назначить роль:

final UserRole roleAdmin = new UserRole();
roleAdmin.setRoleName("ADMIN");

final User user = new User();
user.setUsername("user1");
user.setPassword("xxxxx");
user.getUserRoles.add(roleAdmin);

roleAdmin.setUser(user);

userService.save(user);

Если у пользователя может быть несколько ролей, а роль может быть связана с несколькими пользователями (что мне кажется более правильным), используйте ассоциацию «многие-ко-многим»:

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

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String username;

    private String password;

    @ManyToMany
    private Set<Role> roles = new HashSet<>();

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

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String rolename;

}

Это создаст таблицу user_roles с user_id и roles_id в качестве составного первичного ключа (вы можете настроить имена таблиц/столбцов, используя аннотацию @JoinTable в дополнение к @ManyToMany).

Вы также можете добавить @ManyToMany к сущности Role, если вам нужен двунаправленный тип «многие-ко-многим».

Затем, чтобы назначить роль:

final Role roleAdmin = roleService.getByName("ADMIN");

final User user = new User();
user.setUsername("user1");
user.setPassword("xxxxx");
user.getRoles.add(roleAdmin);

userService.save(user);

Если вам нужны дополнительные столбцы в вашей таблице ассоциаций (user_role), вам необходимо явно определить таблицу ассоциаций и использовать две двунаправленные ассоциации «один-ко-многим»:

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

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String username;

    private String password;

    @OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true)
    private Set<UserRole> userRoles = new HashSet<>();

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

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String rolename;

    @OneToMany(mappedBy = "role", cascade = CascadeType.ALL, orphanRemoval = true)
    private Set<UserRole> userRoles = new HashSet<>();

}
@Entity
@Table(name = "user_role")
public class UserRole {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @ManyToOne(name = "user_id")
    private User user;

    @ManyToOne(name = "role_id")
    private Role role;

    private boolean deserveThisRole;

}

Здесь вы получите таблицу user_role с user_id и role_id в качестве внешних ключей и дополнительный столбец deserve_this_role.

Другие вопросы по теме