Я работаю над своим первым проектом с 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, соответствующий новому пользователю.
Полностью согласен с @Yann39. Кроме того, при использовании двунаправленных отношений может быть недостаточно просто добавить роли вашему пользователю, а установить пользователя в объектах UserRole.
Спасибо, 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;
На данный момент я склоняюсь к сохранению UserRole отдельно от User. По сути, мы оборачиваем методы сохранения и удаления для пользователя, чтобы также сохранять/удалять записи в таблице user_role.
С атрибутом cascadeType
это должно работать. Я опубликовал ответ, чтобы объяснить, что я имел в виду, поскольку он слишком длинный для комментария. Дайте мне знать, если это соответствует вашим потребностям.
Спасибо @Yann39! cascadeType — это то, чего мне не хватало. И, к вашему сведению, я собирался сделать что-то очень простое, и мне нужно было только имя роли (пользователь может иметь несколько ролей), поэтому 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 = "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
.
Я лучше использую
private User user
; вместоprivate Long userId;
в таблицеUserRole
затем добавьтеmappedBy = "user"
в свою@OneToMany
(и удалите@JoinColumn
). В любом случае, если у пользователя может быть несколько ролей, а роль может быть связана с несколькими пользователями, вам понадобится@ManyToMany
.