【问题标题】:How to avoid duplicate insertions of "many" entity in one-to-many relationship when using a unique constraint in eclipselink/JPA在eclipselink / JPA中使用唯一约束时如何避免在一对多关系中重复插入“许多”实体
【发布时间】:2020-10-22 21:39:56
【问题描述】:

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

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

假设在这个表结构中,我已经在 ROLE 中有一条记录,并且在 ROLE_DOMAIN 中有一条引用它的记录,转换为一个名为 myRoleRole 对象,其中包含 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。

我不明白 db 中的唯一约束如何导致 eclipselink 不一致地执行此操作。我有什么配置错误吗?谢谢。

编辑: 我刚刚尝试用这个替换RoleDomain 类上的@Table 注释:

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

它没有改变任何东西。

【问题讨论】:

  • 打开 SQL 日志记录,因为 EclipseLink 不知道该约束,无论有无它在数据库中的操作都相同。你怎么确定没有这个约束没有重复?如果您在类上错误地实现了自己的 equals 方法,您的集合将不会显示。
  • 我知道它不会在没有约束的情况下插入重复项,因为我可以 SELECT * FROM ROLE_DOMAIN 并且没有重复项。
  • @Chris 在查看了在没有唯一约束的情况下会发生什么之后,它实际上是从 Set 中插入所有记录,无论它们是否重复,但随后它会从之后的桌子,这很奇怪。
  • 我认为您可能需要仔细查看时间。我怀疑您在数据库中有条目,添加到重复记录的集合中-但是将其定义为重复记录的是什么? role_id 和 domain 不是标识符,因此您引用了一个全新的行,并且正在删除现有的行。所以这不是更新情况 - 为什么要在新实体已经存在的情况下放入相同的数据?
  • > 为什么要在新实体已经存在的情况下放入相同的数据? -----我不明白你在这里的意思。我从 Role 对象中的数据库中提取了一组 RoleDomain。如果我向其中添加新记录并保存,我希望 eclipselink 知道不必重新插入已经存在的其他记录。

标签: java mysql jpa eclipselink


【解决方案1】:

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

em.unwrap(org.eclipse.persistence.sessions.UnitOfWork.class)

如果您正在进行交易。这只会在此 EntityManager 中为 UnitOfWork 设置,因此如果您在任何地方都需要它,您将需要一个带有您自己的会话适配器类的 session listener 来监听 postAcquireUnitOfWork 并在其上调用 setShouldPerformDeletesFirst。

【讨论】:

  • 这行得通,它让我看到了 eclipselink 关系映射和查询提交的一些底层机制,非常感谢!
猜你喜欢
  • 2017-09-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-02-03
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多