[英]SQl error - Referential integrity constraint violation when persisting several entities at once
I'm trying to model a @ManyToMany
relation between a User
and Role
, such that a user can have n roles, and one role is referenced by several users.我正在尝试 model 一个User
和Role
之间的@ManyToMany
关系,这样一个用户可以有n 个角色,并且一个角色被多个用户引用。 A role can be persisted even if it's not referenced by any user (detached), and a user with no roles is allowed too.一个角色即使没有被任何用户引用(分离)也可以被持久化,并且也允许没有角色的用户。
The same kind of relation must be built between Role
and ResourcePermission
. Role
和ResourcePermission
之间必须建立相同的关系。
To give you an idea about how each entity looks like:为了让您了解每个实体的外观:
ResourcePermission
and Role
have a finite set of values. ResourcePermission
和Role
都有一组有限的值。 For example, if Patient
happens to be a resource, then one resource permission could be "PATIENT:READ"
or "PATIENT:WRITE"
, and the role DOCTOR
has several of these permissions.例如,如果Patient
恰好是一个资源,那么一个资源权限可能是"PATIENT:READ"
或"PATIENT:WRITE"
,角色DOCTOR
拥有其中的几个权限。 I hope it's clear sofar how my data model looks like.我希望到目前为止我的数据 model 的样子很清楚。spring-data-jpa
version 2.4.2
to model my entities, and to create my CRUD repos.目前,我正在使用spring-data-jpa
版本2.4.2
到 model 我的实体,并创建我的 CRUD 存储库。 Except for base path and media type, I don't have any specific configuration (all is set to default).除了基本路径和媒体类型,我没有任何特定配置(全部设置为默认值)。Here's how my entities look like这是我的实体的样子
User.java用户.java
@Table
@Entity
@Data
public class User implements Serializable {
private static final long serialVersionUID = 1123146940559321847L;
@Id
@GeneratedValue(generator = "user-id-generator")
@GenericGenerator(name = "user-id-generator",
strategy = "....security.entity.UserIdGenerator",
parameters = @Parameter(name = "prefix", value = "USER-")
)
@Column(unique = true, nullable = false)
private String id;
@Column
private int age;
@Column(unique = true, nullable = false)
private String username;
@Column(unique = false, nullable = false)
private String password;
@ManyToMany(
fetch = FetchType.LAZY,
cascade = CascadeType.MERGE
)
@JoinTable(
name = "user_role",
joinColumns = @JoinColumn(name = "user_id"),
inverseJoinColumns = @JoinColumn(name = "role_id")
)
private List<Role> roles = Collections.emptyList();
public User withId(final String id) {
this.id = id;
return this;
}
public User withAge(final int age) {
this.age = age;
return this;
}
public User withUsername(final String username) {
this.username = username;
return this;
}
public User withPassword(final String password) {
this.password = password;
return this;
}
public User withRoles(final Role... roles) {
return withRoles(Arrays.stream(roles).collect(Collectors.toList()));
}
public User withRoles(final List<Role> roles) {
this.roles = roles;
return this;
}
}
Role.java角色.java
@Data
@NoArgsConstructor
@Table
@Entity
public class Role implements Serializable {
private static final long serialVersionUID = 812344454009121807L;
@Id
private String roleName;
@ManyToMany(
fetch = FetchType.LAZY,
cascade = { CascadeType.MERGE, CascadeType.PERSIST, CascadeType.DETACH }
)
@JoinTable(
name = "role_resource_permission",
joinColumns = @JoinColumn(name = "role_id"),
inverseJoinColumns = @JoinColumn(name = "resource_permission_id")
)
private Set<ResourcePermission> resourcePermissions = Collections.emptySet();
@ManyToMany(
mappedBy = "roles",
fetch = FetchType.LAZY,
cascade = { CascadeType.MERGE, CascadeType.PERSIST, CascadeType.DETACH }
)
private List<User> users = Collections.emptyList();
public Role(final String roleName) {
setRoleName(roleName);
}
public void setRoleName(final String roleName) {
final RoleType roleType = RoleType.of(roleName);
this.roleName = roleType.getRoleName();
final Set<ResourcePermission> resourcePermissions = roleType.getResourcePermissions().stream()
.map(ResourcePermissionType::getPermissionName)
.map(ResourcePermission::new)
.collect(Collectors.toSet());
setResourcePermissions(resourcePermissions);
}
public void setResourcePermissions(final Set<ResourcePermission> resourcePermissions) {
if (this.resourcePermissions.isEmpty()) {
this.resourcePermissions = resourcePermissions;
}
}
}
ResourcePermission.java资源权限.java
@NoArgsConstructor
@Data
@Table
@Entity
public class ResourcePermission implements Serializable {
private static final long serialVersionUID = 883231454000721867L;
@Id
private String permissionName;
public ResourcePermission(final String permissionName) {
setPermissionName(permissionName);
}
@ManyToMany(
mappedBy = "resourcePermissions",
fetch = FetchType.LAZY,
cascade = { CascadeType.MERGE, CascadeType.PERSIST, CascadeType.DETACH }
)
private Set<Role> roles = Collections.emptySet();
public void setPermissionName(String permissionName) {
final ResourcePermissionType permissionType = ResourcePermissionType.of(permissionName);
this.permissionName = permissionType.getPermissionName();
}
}
RoleType.java RoleType.java
@AllArgsConstructor(access = AccessLevel.PRIVATE)
public enum RoleType {
DOCTOR("DOCTOR", doctorsPermissions()),
TECHNICIAN("TECHNICIAN", technicianPermission()),
ADMIN("ADMIN", adminPermissions());
@Getter
private String roleName;
@Getter
private final List<ResourcePermissionType> resourcePermissions;
public static RoleType of(final String roleName) {
return Arrays.stream(values())
.filter(roleType -> roleType.getRoleName().equals(roleName.toUpperCase()))
.findFirst()
.orElseThrow(IllegalArgumentException::new);
}
private static List<ResourcePermissionType> doctorsPermissions() {
return Arrays.asList(
ENCOUNTER_READ, ENCOUNTER_WRITE,
PATIENT_READ, PATIENT_WRITE
);
}
private static List<ResourcePermissionType> adminPermissions() {
return Arrays.asList(
ENCOUNTER_READ, ENCOUNTER_WRITE,
BUILDING_UNIT_READ, BUILDING_UNIT_WRITE,
ORG_UNIT_READ, ORG_UNIT_WRITE
);
}
private static List<ResourcePermissionType> technicianPermission() {
return Arrays.asList(
ENCOUNTER_READ, ENCOUNTER_WRITE,
BUILDING_UNIT_READ, BUILDING_UNIT_WRITE
);
}
}
ResourcePermissoinType.java ResourcePermissoinType.java
@AllArgsConstructor(access = AccessLevel.PRIVATE)
public enum ResourcePermissionType implements Serializable {
PATIENT_READ("PATIENT:READ"), PATIENT_WRITE("PATIENT:WRITE"),
ENCOUNTER_READ("ENCOUNTER:READ"), ENCOUNTER_WRITE("ENCOUNTER:WRITE"),
BUILDING_UNIT_READ("BUILDING_UNIT:READ"), BUILDING_UNIT_WRITE("BUILDING_UNIT:WRITE"),
ORG_UNIT_READ("ORG_UNIT:READ"), ORG_UNIT_WRITE("ORG_UNIT:WRITE");
@Getter
private String permissionName;
public static ResourcePermissionType of(final String permissionName) {
return Arrays.stream(values())
.filter(v -> v.getPermissionName().equals((permissionName.toUpperCase())))
.findFirst()
.orElseThrow(IllegalArgumentException::new);
}
}
Unfortunately, the javax persistence API does not accept enums as entities.不幸的是,javax 持久性 API 不接受枚举作为实体。 I tried using @Embeddable
and @IdClass
too, but that didn't work out for me either.我也尝试过使用@Embeddable
和@IdClass
,但这对我也不起作用。 I was not able to generate the schema that I had in mind.我无法生成我想到的模式。 On the other hand, the schema was successfully generated using this model.另一方面,使用此 model 成功生成了模式。
At the moment, both the Role repository as well as the Resource Permission repository are not exported ( @RepositoryRestResource(..., exported = false)
), so in order for you to persist those two entities, you'd have to provide that data in User
.目前,角色存储库和资源权限存储库都没有被导出( @RepositoryRestResource(..., exported = false)
),所以为了让你持久化这两个实体,你必须提供User
中的数据。 Keep that in mind, because that's also a part of the discussion that I want to talk about.请记住这一点,因为这也是我想讨论的讨论的一部分。
Now let's examine this integration test for the UserCrudRepository
that will attempt to add a new user after a successful authentication.现在让我们检查一下UserCrudRepository
的集成测试,它将在成功验证后尝试添加新用户。
@TestMethodOrder(OrderAnnotation.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@AutoConfigureMockMvc
class UserCrudRepositoryApiITest {
private final List<User> testUsers = Arrays.asList(
new User().withUsername("dummy_username_01").withPassword("dummy_password_01").withAge(35)
.withRoles(new Role("ADMIN")),
new User().withUsername("dummy_username_02").withPassword("dummy_password_02").withAge(40)
.withRoles(new Role("DOCTOR")),
new User().withUsername("dummy_username_03").withPassword("dummy_password_03").withAge(45)
);
.
.
@Order(1)
@Test
public void afterAuthenticationAddNewUser() throws Exception {
final String generatedToken = login();
// serialize the user
final String requestJson = objectMapper.writeValueAsString(testUsers.get(0));
final RequestBuilder request = MockMvcRequestBuilders.post(USER_CRUD_BASE_URL)
.header(HttpHeaders.AUTHORIZATION, generatedToken)
.contentType(MediaType.APPLICATION_JSON)
.content(requestJson);
final String serializedContent = mvc.perform(request)
.andExpect(status().isCreated())
.andReturn()
.getResponse()
.getContentAsString();
final User storedUser = objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
.readValue(serializedContent, User.class);
assertThat(storedUser).isEqualTo(testUsers.get(0));
}
.
.
}
In here, I'm getting a status code conflict 409, and not able to persist all entities at once.在这里,我得到一个状态码冲突 409,并且无法一次保存所有实体。
Unfortunately, SO allows only 30000 character, so please navigate to this repo if you would like to take a look at the log.不幸的是,SO 只允许 30000 个字符,所以如果你想查看日志,请导航到这个 repo 。
Role
data in the body of a User
, but rather A reference to that entity.但是,问题在于您不再可以在User
的正文中传递Role
数据,而是对该实体的引用。 Is there a way to get the best of both worlds.有没有办法两全其美。I hope I made my problem clear, if not, I'd be happy to elaborate more.我希望我把我的问题说清楚了,如果没有,我很乐意详细说明。
I guess when a User
is persisted, it also does the insert for the user_role
table, but the role wasn't persisted yet.我猜当User
被持久化时,它也会为user_role
表插入,但角色还没有持久化。 You could try to persist the Role
first or use PERSIST cascading at the User#roles
association.您可以尝试先保留Role
或在User#roles
关联中使用 PERSIST 级联。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.