将子实体持久化操作级联到其父实体
Cascading a Child entity persist operation to its Parent entity
我与使用 Hibernate 注释设置的两个实体建立了 OneToMany
关联。该关联的 Child
实体有一个复合主键,由外键 parent
列和另一个标识符 childName
组成。当我尝试通过保存子实体将提交级联到父实体时,这似乎会导致 "Referential integrity constraint violation"。
我已经创建了一个简单的问题工作示例,它从我使用此关系建模的实际场景中抽象出来,但这意味着我知道问题是由于此关联和使用复合主键引起的。
为什么我不能通过保存子实体来提交两个实体?为什么会违反外键约束?
主要测试方法*:
// Create some detached entities
Parent p = new Parent();
p.setName("Fooson");
// New child id
Child.ChildPK pk = new Child.ChildPK();
pk.setParentName(p);
pk.setChildName("Barty");
// Set id to new Child
Child c = new Child();
c.setChildPK(pk);
// Add child to parent
p.getChildren().add(c);
// Saving the parent
// service.parentDao.save(p); // if this is uncommented, it works fine
// Cascade child and associated parents in one transaction
service.childDao.save(c); // ConstraintViolationException
Child.java
@Entity
@Table(name="Child")
public class Child implements Serializable {
@EmbeddedId
private ChildPK id;
public ChildPK getChildPK() {
return id;
}
public void setChildPK(ChildPK childPK) {
this.id = childPK;
}
@Embeddable
public static class ChildPK implements Serializable
{
@ManyToOne(cascade=CascadeType.ALL)
@JoinColumn(name="name")
Parent parent;
@Column(name="childName")
String childName;
public Parent getParentName() {
return parent;
}
public void setParentName(Parent parentName) {
this.parent = parentName;
}
public String getChildName() {
return childName;
}
public void setChildName(String childName) {
this.childName = childName;
}
@Override
public int hashCode() {
int hash = 5;
hash = 67 * hash + Objects.hashCode(this.parent);
hash = 67 * hash + Objects.hashCode(this.childName);
return hash;
}
@Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final ChildPK other = (ChildPK) obj;
if (!Objects.equals(this.parent, other.parent)) {
return false;
}
return Objects.equals(this.childName, other.childName);
}
@Override
public String toString() {
return "ChildPK{" + "parentName=" + parent.getName() + ", childName=" + childName + '}';
}
}
@Override
public String toString() {
return "Child{" + "id=" + id + '}';
}
}
Parent.java
@Entity
@Table(name="Parent")
public class Parent implements Serializable {
@Id
@Column(name="name")
private String name;
@OneToMany(mappedBy="id.parentName", cascade=CascadeType.ALL)
Set<Child> children = new HashSet<>(0);
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Set<Child> getChildren() {
return children;
}
public void setChildren(Set<Child> children) {
this.children = children;
}
@Override
public int hashCode() {
int hash = 3;
hash = 73 * hash + Objects.hashCode(this.name);
return hash;
}
@Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final Parent other = (Parent) obj;
if (!Objects.equals(this.name, other.name)) {
return false;
}
return true;
}
@Override
public String toString() {
return "Parent{" + "name=" + name + ", children=" + children + '}';
}
}
完整的异常信息为:
Exception in thread "main" org.hibernate.exception.ConstraintViolationException:
Could not execute JDBC batch update
...
Caused by: org.h2.jdbc.JdbcBatchUpdateException:
Referential integrity constraint violation:
"FK3E104FCBA07683D: PUBLIC.CHILD FOREIGN KEY(NAME) REFERENCES
PUBLIC.PARENT(NAME) ('Fooson')"; SQL statement:
insert into Child (childName, name) values (?, ?)
...
* main 方法指的是 DAO 和 ServiceLayer 对象,我没有在这里显示,因为我认为它与问题无关。但是,如果需要,我可以 post 这些。
将保存操作从 Child 级联到 Parent 实体没有意义,恰恰相反。
那是因为操作将是:
- 保存 Child
- 将保存操作传播到 Parent
但是 Child 取决于 Parent.id 来设置其 FK。
你也需要从数据库的角度来可视化这个操作流程。你能拯救一个引用了 non-existing Parent 的 Child 吗?当然不是。
想一想删除 Child 实体时会发生什么。级联将传播到 Parent,然后传播到其所有 Children.
因此,您必须从 Embeddable 中删除级联 class:
@ManyToOne(cascade=CascadeType.ALL)
并取消注释可以正常工作的代码:
// Saving the parent
service.parentDao.save(p); // if this is uncommented, it works fine
我与使用 Hibernate 注释设置的两个实体建立了 OneToMany
关联。该关联的 Child
实体有一个复合主键,由外键 parent
列和另一个标识符 childName
组成。当我尝试通过保存子实体将提交级联到父实体时,这似乎会导致 "Referential integrity constraint violation"。
我已经创建了一个简单的问题工作示例,它从我使用此关系建模的实际场景中抽象出来,但这意味着我知道问题是由于此关联和使用复合主键引起的。
为什么我不能通过保存子实体来提交两个实体?为什么会违反外键约束?
主要测试方法*:
// Create some detached entities
Parent p = new Parent();
p.setName("Fooson");
// New child id
Child.ChildPK pk = new Child.ChildPK();
pk.setParentName(p);
pk.setChildName("Barty");
// Set id to new Child
Child c = new Child();
c.setChildPK(pk);
// Add child to parent
p.getChildren().add(c);
// Saving the parent
// service.parentDao.save(p); // if this is uncommented, it works fine
// Cascade child and associated parents in one transaction
service.childDao.save(c); // ConstraintViolationException
Child.java
@Entity
@Table(name="Child")
public class Child implements Serializable {
@EmbeddedId
private ChildPK id;
public ChildPK getChildPK() {
return id;
}
public void setChildPK(ChildPK childPK) {
this.id = childPK;
}
@Embeddable
public static class ChildPK implements Serializable
{
@ManyToOne(cascade=CascadeType.ALL)
@JoinColumn(name="name")
Parent parent;
@Column(name="childName")
String childName;
public Parent getParentName() {
return parent;
}
public void setParentName(Parent parentName) {
this.parent = parentName;
}
public String getChildName() {
return childName;
}
public void setChildName(String childName) {
this.childName = childName;
}
@Override
public int hashCode() {
int hash = 5;
hash = 67 * hash + Objects.hashCode(this.parent);
hash = 67 * hash + Objects.hashCode(this.childName);
return hash;
}
@Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final ChildPK other = (ChildPK) obj;
if (!Objects.equals(this.parent, other.parent)) {
return false;
}
return Objects.equals(this.childName, other.childName);
}
@Override
public String toString() {
return "ChildPK{" + "parentName=" + parent.getName() + ", childName=" + childName + '}';
}
}
@Override
public String toString() {
return "Child{" + "id=" + id + '}';
}
}
Parent.java
@Entity
@Table(name="Parent")
public class Parent implements Serializable {
@Id
@Column(name="name")
private String name;
@OneToMany(mappedBy="id.parentName", cascade=CascadeType.ALL)
Set<Child> children = new HashSet<>(0);
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Set<Child> getChildren() {
return children;
}
public void setChildren(Set<Child> children) {
this.children = children;
}
@Override
public int hashCode() {
int hash = 3;
hash = 73 * hash + Objects.hashCode(this.name);
return hash;
}
@Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final Parent other = (Parent) obj;
if (!Objects.equals(this.name, other.name)) {
return false;
}
return true;
}
@Override
public String toString() {
return "Parent{" + "name=" + name + ", children=" + children + '}';
}
}
完整的异常信息为:
Exception in thread "main" org.hibernate.exception.ConstraintViolationException:
Could not execute JDBC batch update
...
Caused by: org.h2.jdbc.JdbcBatchUpdateException:
Referential integrity constraint violation:
"FK3E104FCBA07683D: PUBLIC.CHILD FOREIGN KEY(NAME) REFERENCES
PUBLIC.PARENT(NAME) ('Fooson')"; SQL statement:
insert into Child (childName, name) values (?, ?)
...
* main 方法指的是 DAO 和 ServiceLayer 对象,我没有在这里显示,因为我认为它与问题无关。但是,如果需要,我可以 post 这些。
将保存操作从 Child 级联到 Parent 实体没有意义,恰恰相反。
那是因为操作将是:
- 保存 Child
- 将保存操作传播到 Parent
但是 Child 取决于 Parent.id 来设置其 FK。
你也需要从数据库的角度来可视化这个操作流程。你能拯救一个引用了 non-existing Parent 的 Child 吗?当然不是。
想一想删除 Child 实体时会发生什么。级联将传播到 Parent,然后传播到其所有 Children.
因此,您必须从 Embeddable 中删除级联 class:
@ManyToOne(cascade=CascadeType.ALL)
并取消注释可以正常工作的代码:
// Saving the parent
service.parentDao.save(p); // if this is uncommented, it works fine