В приложении с весенней загрузкой у меня есть следующее определение сущности:
@Data
@Entity
@Table(name = "users")
public class User {
@Id
@Column(nullable = false, name = "username", length = 100)
private String username;
@JoinTable(name = "userrole",
joinColumns = { @JoinColumn(name = "username") },
inverseJoinColumns = { @JoinColumn(name = "role") }
)
@OneToMany(
cascade = CascadeType.ALL,
orphanRemoval = true
)
private List<Role> roles;`
Я использую Spring-data-jpa, Hibernate с H2 в качестве базы данных. Проблема в том, что spring-data-jpa, hibernate всегда генерирует/создает «роль пользователя» таблицы соединений (DDL) с одним первичным ключом столбца. например 'имя пользователя'. Таким образом, если такие записи, как {'username', 'user_role'} и {'username', 'admin_role'}, вставляются в таблицу соединения ("userrole"), следующая вставка завершается с ошибкой из-за "дубликата" основной ключ.
Я пытался использовать оба столбца в приведенном выше определении, а также следующий вариант:
@OneToMany(
cascade = CascadeType.ALL,
orphanRemoval = true
)
@JoinColumns({
@JoinColumn(name = "username"),
@JoinColumn(name = "role") })
private List<Role> roles;`
Но что они привели к таким же или более серьезным проблемам, например. а в последнем даже создание таблицы не удается, потому что только один столбец используется в качестве первичного ключа для соединения. Роль — это просто еще одна таблица с двумя столбцами «роль» и «описание», в основном каталог ролей.
Как указать JPA, что @JoinTable должен использовать столбцы «имя пользователя» и «роль» в качестве составных первичных ключей?
редактировать: Я попытался использовать независимую таблицу/объект, как было предложено, спасибо @Kamil Bęben
@Data
@Entity
@Table(name = "users")
public class User {
@Id
@Column(nullable = false, name = "username", length = 100)
private String username;
@OneToMany(
fetch = FetchType.EAGER,
cascade = CascadeType.ALL,
mappedBy = "username",
orphanRemoval = true
)
@ElementCollection
private List<UserRole> roles;
UserRole определяется как таковой
@Data
@NoArgsConstructor
@Entity
@Table(name = "userrole")
public class UserRole {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "userrole_seq")
Long id;
@Column(nullable = false, name = "username", length = 100)
private String username;
@Column(nullable = false, name = "role", length = 50)
private String role;
репозиторий для этой таблицы соединения ролей пользователей определяется как
@Repository
public interface UserRoleRepository extends CrudRepository<UserRole, Long> {
UserRole findByUsernameAndRole(String username, String role);
List<UserRole> findByUsername(String username);
List<UserRole> findByRole(String role);
}
По общему признанию, некрасиво, но это работает. И каким-то образом казалось, что он использует правильный метод findByUsername() для извлечения ролей, относящихся к пользователю, вероятно, связанных с предложением «mappedBy». 'черная магия'! Есть еще много того, что мне все еще нужно найти в JPA, Spring, Spring-data
редактировать2: дальнейшее обновление: оригинальный @JoinTable также работает. Но что отношения должны быть указаны как @ManyToMany
@ManyToMany(
fetch = FetchType.EAGER,
cascade = CascadeType.MERGE
)
@JoinTable(name = "usersroles",
joinColumns = { @JoinColumn(name = "username") },
inverseJoinColumns = { @JoinColumn(name = "role") }
)
private List<Role> roles = new ArrayList<Role>();
Это создает 2 первичных ключа столбца, как и ожидалось, для таблицы «пользователи-роли».
Спасибо @Roman
да, на самом деле это \@ManyToMany, но из родительского класса User это будет больше похоже на \@OneToMany, поскольку у пользователя будет несколько ролей.
Если роль имеет только два столбца, например, user_id и роль, способ сопоставления этого в jpa будет следующим:
@ElementCollection
@CollectionTable(name = "user_roles", joinColumns = @JoinColumn(name = "user_id"))
@Column(name = "role")
List<String> roles = new ArrayList<>();
В противном случае jpa действительно требует, чтобы идентификатор каждой сущности и столбцы соединения были отдельными столбцами, поэтому сущность Role должна была бы иметь такие столбцы, как id, user_id и role_name. Может выглядеть так.:
class Role {
@Id
Long id;
@ManyToOne
@JoinColumn(name = "user_id", referencedColumnName = "id");
User user;
String roleName;
// Other fields
}
И в сущности пользователя
@OneToMany(mappedBy = "user") // user is Field's name, not a column
List<Role> roles = new ArrayList<>();
Спасибо ! Я попробовал метод \@ElementCollection, и он работает. Я думаю, что явное определение таблицы соединения ролей пользователя как объекта, вероятно, является лучшей практикой! Я бы работал над другим, который заключается в том, чтобы таблица соединения ролей пользователей была определена как автономная сущность.
Почему
roles
аннотирован@OneToMany
? Разве так не должно быть@ManyToMany
?