SQl错误-一次持久化多个实体时违反了引用完整性约束



我试图解决的问题

我正在尝试为UserRole之间的@ManyToMany关系建模,这样一个用户可以拥有n个角色,并且一个角色由多个用户引用。角色可以持久化,即使它没有被任何用户引用(分离(,也允许没有角色的用户。

RoleResourcePermission之间必须建立相同的关系。

让您了解每个实体的外观:

  • ResourcePermissionRole都有一组有限的值。例如,如果Patient恰好是一个资源,那么一个资源权限可以是"PATIENT:READ""PATIENT:WRITE",而角色DOCTOR具有其中的几个权限。我希望现在我的数据模型看起来很清楚

我正在使用什么

  • 目前,我使用spring-data-jpa版本2.4.2来建模我的实体,并创建我的CRUD repo。除了基本路径和介质类型,我没有任何特定的配置(全部设置为默认(
  • Hibernate是我的持久性提供者atm
  • 关于我的数据源,我在内存中使用H2作为我的开发环境,同样也没有特定的配置

我是如何解决的

下面是我的实体看起来像的样子

User.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;
}
}

角色.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

@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

@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

@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);
}
}

遗憾的是,javax持久性API不接受enum作为实体。我也尝试过使用@Embeddable@IdClass,但也没有成功。我无法生成我心目中的模式。另一方面,使用该模型成功地生成了模式。

目前,角色存储库和资源权限存储库都没有导出(@RepositoryRestResource(..., exported = false)(,因此为了持久化这两个实体,您必须在User中提供该数据。记住这一点,因为这也是我想讨论的一部分。

现在,让我们检查一下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));
}
.
.
}

在这里,我得到了一个状态代码冲突409,并且不能同时持久化所有实体。

不幸的是,SO只允许30000个字符,因此如果您想查看日志,请导航到此repo。

我的问题

  1. 我一辈子都不明白引用完整性约束的违反在哪里正在发生。知道吗
  2. 欢迎就如何更好地建立这些关系提出任何建议
  3. JPA repos的另一个问题是,保持角色和资源权限的唯一方法是在用户体内提供这些数据。我希望这些实体独立于用户进行管理(每个实体都有自己独立的存储库(,所以我尝试导出它们的存储库。然而,问题是,您不能再在User的主体中传递Role数据,而是传递对该实体的引用。有没有办法两全其美

我希望我把我的问题说清楚了,如果没有,我很乐意详细说明。

我想当User被持久化时,它也会为user_role表插入,但角色还没有持久化。您可以先尝试持久化Role,或者在User#roles关联处使用persist级联。

最新更新