简体   繁体   English

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

[英]SQl error - Referential integrity constraint violation when persisting several entities at once

Problem I'm trying to solve我试图解决的问题

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 一个UserRole之间的@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 . RoleResourcePermission之间必须建立相同的关系。

To give you an idea about how each entity looks like:为了让您了解每个实体的外观:

  • Both ResourcePermission and Role have a finite set of values. ResourcePermissionRole都有一组有限的值。 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 的样子很清楚。

What I'm using我正在使用什么

  • Currently, I'm using 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).除了基本路径和媒体类型,我没有任何特定配置(全部设置为默认值)。
  • Hibernate is my persistence provider atm. Hibernate 是我的持久性提供者 atm。
  • Concerning my datasource, I'm using in-memory H2 for my development environment, and again no specific configuration there either.关于我的数据源,我在我的开发环境中使用内存 H2,并且那里也没有特定的配置。

How I'm solving it我是如何解决的

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

My Questions我的问题

  1. I couldn't for the life of me understand where that referential integrity constraint violation is occurring.我一生都无法理解违反参照完整性约束的情况发生在哪里。 Any idea?任何想法?
  2. Any suggestions on how to model these relations in a better way are welcome!欢迎任何关于如何以更好的方式处理 model 这些关系的建议!
  3. Another problem I'm having with JPA repos is that the only way to persist roles and resource permissions is by providing that data in the user's body.我在使用 JPA 存储库时遇到的另一个问题是,保持角色和资源权限的唯一方法是在用户正文中提供该数据。 I would like those entities to be managed independently of the user (each with its own separate repository), so I tried exporting their repositories.我希望这些实体独立于用户进行管理(每个实体都有自己的单独存储库),所以我尝试导出他们的存储库。 However, the problem then is that you no longer can pass 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.

相关问题 持久保留关联实体时休眠约束违规 - Hibernate Constraint Violation when persisting associated entities 参照完整性约束违规 - 添加超过2条记录时失败 - Referential integrity constraint violation - failing when add more then 2 records 无继承关系的参照完整性约束违规 - Referential integrity constraint violation for none inheritance relationship 如何防止在测试中违反参照完整性约束? - How to prevent referential integrity constraint violation in tests? H2参照完整性约束违反 - H2 Referential integrity constraint violation 当删除带有播放框架2.2.x的对象时,违反引用完整性约束 - Referential integrity constraint violation when deleting an object with play-framework 2.2.x 违反参照完整性约束:删除 Hibernate 中的实体时(memory DBMS 中的 H2) - Referential integrity constraint violation: when deleting Entity in Hibernate (H2 in memory DBMS) 所有者删除时违反参照完整性约束(OneToMany单向) - Referential integrity constraint violation on owner delete (OneToMany unidirectional) Hibernate / H2 @OneToMany移除子代时违反“参照完整性约束”? - Hibernate/H2 @OneToMany “Referential integrity constraint violation” on remove of child? 更新和/或删除时违反 Hibernate H2 参照完整性约束 - Hibernate H2 Referential Integrity Constraint Violation on Update and/or Remove
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM