在 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。
我正在使用 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。