在 eclipselink/JPA 中使用唯一约束时,如何避免在一对多关系中重复插入 "many" 实体

How to avoid duplicate insertions of "many" entity in one-to-many relationship when using a unique constraint in eclipselink/JPA

我正在使用 Spring MVC 和 EclipseLink 2.5.2 在 mysql 数据库上开发大型代码库。数据库及其结构是直接创建的,而不是通过任何代码优先的方法。我的问题涉及一对多关系中的 2 table。

CREATE TABLE ROLE (
  ID BIGINT(20) PRIMARY KEY,
  -- OTHER FIELDS --
);

CREATE TABLE ROLE_DOMAIN (
  ID BIGINT(20) PRIMARY KEY,
  ROLE_ID BIGINT(20) NOT NULL,
  DOMAIN VARCHAR(255) NOT NULL
  -- OTHER FIELDS --
);

ALTER TABLE ROLE_DOMAIN ADD CONSTRAINT FK_ROLE_DOMAIN_ROLE_ID FOREIGN KEY (ROLE_ID) REFERENCES ROLE_BASE (ID) ON DELETE CASCADE;
ALTER TABLE ROLE_DOMAIN ADD CONSTRAINT UQ_ROLE_DOMAIN_ROLE_ID_DOMAIN UNIQUE (ROLE_ID, DOMAIN);

在 java 中,这就是我配置两个实体的方式。

@Entity
public class Role {
  private Long id;
  private Set<RoleDomain> roleDomains = new HashSet<>();

  @Id
  @TableGenerator(name = "ROLE.ID", allocationSize = 1, initialValue = 1)
  @GeneratedValue(strategy = GenerationType.TABLE, generator = "ROLE.ID")
  public Long getID() {
    return this.id;
  }

  public void setId(Long id) {
    this.id = id;
  }

  @OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, orphanRemoval = true)
  @JoinColumn(name = "ROLE_ID", referencedColumnName = "ID", insertable = false, updatable = false)
  public Set<RoleDomain> getRoleDomains() {
    return roleDomains;
  }
    
  public void setRoleDomains(Set<RoleDomain> roleDomains) {
    this.roleDomains = roleDomains;
  }
}


@Entity
@Table(name = "ROLE_DOMAIN")
public class RoleDomain {
  private Long id;
  private Long roleId;
  private String domain;

  @Id
  @TableGenerator(name = "ROLE_DOMAIN.ID", allocationSize = 1, initialValue = 1)
  @GeneratedValue(strategy = GenerationType.TABLE, generator = "ROLE_DOMAIN.ID")
  public Long getId() {
    return id;
  }

  public void setId(Long id) {
    this.id = id;
  }

  @Column(name = "ROLE_ID", nullable = false)
  public Long getRoleId() {
    return roleId;
  }

  public void setRoleId(Long roleId) {
    this.roleId = roleId;
  }

  @Column(name = "DOMAIN", length = 255)
  public String getDomain() {
    return domain;
  }

  public void setDomain(String domain) {
    this.domain = domain;
  }
}

说在这个 table 结构中,我已经在 ROLE 中有一个记录,在 ROLE_DOMAIN 中有一个引用它的记录,转换为一个名为 [=18= 的 Role 对象] 包含 roleDomains 中的 RoleDomain

现在,当我添加一个新的 RoleDomain 并使用 spring 数据存储库保存时,如下所示:

myRole.add(new RoleDomain("some string"));
roleRepository.save(myRole);

我收到一个重复插入的异常,该插入违反了我对数据库中 ROLE_ID 和 DOMAIN 的唯一约束。

[EL Warning]: 2020-10-22 14:53:22.405--UnitOfWork(994047815)--Exception [EclipseLink-4002] (Eclipse Persistence Services - 2.6.8.v20190620-d6443d8be7): org.eclipse.persistence.exceptions.DatabaseException
Internal Exception: java.sql.SQLIntegrityConstraintViolationException: Duplicate entry '198732-some string' for key 'UQ_ROLE_DOMAIN_ROLE_ID_DOMAIN'
Error Code: 1062
Call: INSERT INTO ROLE_DOMAIN (ID, DOMAIN, ROLE_ID) VALUES (?, ?, ?)
    bind => [27, some other string, 198732]

关于这个问题最奇怪的事情是,如果我从数据库中删除唯一约束(注意:保持 java 注释配置完全相同。字面上只是数据库中的“DROP CONSTRAINT ...” ) 那么 save 调用就可以正常工作了。它不会在 ROLE_DOMAIN 中创建重复项。它完全按照预期的方式工作,只是将新记录添加到 ROLE_DOMAIN.

我不明白数据库中的唯一约束如何导致 eclipselink 的行为不一致。我配置有误吗?谢谢。

编辑: 我刚刚尝试将 RoleDomain class 上的 @Table 注释替换为:

@Table(name = "ROLE_DOMAIN", uniqueConstraints =
  @UniqueConstraint(columnNames = {"ROLE_ID", "DOMAIN"}))

它没有改变任何东西。

您的约束的问题是 EclipseLink 对语句进行批处理,将删除放在最后 - 这是为了让您有机会清理其他约束,在行被删除之前修改现有行。这可以更改,以便首先使用 UnitOfWork 上的 setShouldPerformDeletesFirst 方法发出删除。由于这是本机 api,您必须使用

打开 EntityManager 才能获取它
em.unwrap(org.eclipse.persistence.sessions.UnitOfWork.class)

如果您正在进行交易。这将只为这个 EntityManager 中的 UnitOfWork 设置,所以如果你总是在任何地方都需要它,你会想要一个 session listener with your own session adaptor class to listen for postAcquireUnitOfWork 并在其上调用 setShouldPerformDeletesFirst。