我的应用程序中现有的父子关系最近变得更加复杂,因为我们添加了一个"类型"列到父级和子级的主键。在此之后,添加,阅读和修改孩子很有效,但删除它们是一种痛苦。
使用Vlad Mihalcea在this article中给出的关于@OneToMany关系的建议以及关于复合键的各种示例,我尝试了类似于以下模型的实现。但是,删除孩子仍然无法正常工作,我现在有一个奇怪的错误消息作为奖励。
我正在使用Spring Boot 1.4.1和Hibernate 5.1.9.Final。
父实体的@EmbeddedId ParentPK包含两个字段,children
集合Cascade.ALL
和orphanRemoval
设置为true。
@Entity
@Table(name = "z_parent")
public class Parent {
@EmbeddedId
private ParentPK pk;
@OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL, orphanRemoval = true)
@JoinColumns({
@JoinColumn(name = "parent_code", referencedColumnName = "code"),
@JoinColumn(name = "parent_type", referencedColumnName = "type")
})
List<Child> children = new ArrayList<>();
public Parent() {
}
public Parent(String code, String type) {
this.pk = new ParentPK(code, type);
}
public void addChild(Child child){
child.setParent(this);
children.add(child);
}
public void removeChild(Child child){
child.setParent(null);
children.remove(child);
}
//getters and setters, including delegate getters and setters
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Parent)) return false;
Parent parent = (Parent) o;
return pk.equals(parent.pk);
}
@Override
public int hashCode() {
return pk.hashCode();
}
}
@Embeddable
public class ParentPK implements Serializable {
@Column(name = "code")
private String code;
@Column(name = "type")
private String type;
public ParentPK() {
}
public ParentPK(String code, String type) {
this.code = code;
this.type = type;
}
//getters and setters
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof ParentPK)) return false;
ParentPK parentPK = (ParentPK) o;
if (!getCode().equals(parentPK.getCode())) return false;
return getType().equals(parentPK.getType());
}
@Override
public int hashCode() {
int result = getCode().hashCode();
result = 31 * result + getType().hashCode();
return result;
}
}
Child
实体具有自己的code
标识符,该标识符与标识父项的两个字符串一起构成另一个复合主键。与Parent的关系是双向的,因此Child也有一个用@ManyToOne注释的parent
字段。
@Entity
@Table(name = "z_child")
public class Child {
@EmbeddedId
private ChildPk pk = new ChildPk();
//The two columns of the foreign key are also part of the primary key
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumns({
@JoinColumn(name = "parent_code", referencedColumnName = "code", insertable = false, updatable = false),
@JoinColumn(name = "parent_type", referencedColumnName = "type", insertable = false, updatable = false)
})
private Parent parent;
public Child() {
}
public Child(String code, String parentCode, String parentType) {
this.pk = new ChildPk(code, parentCode, parentType);
}
//getters and setters, including delegate getters and setters
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Child)) return false;
Child child = (Child) o;
return pk.equals(child.pk);
}
@Override
public int hashCode() {
return pk.hashCode();
}
}
@Embeddable
class ChildPk implements Serializable {
@Column(name = "code")
private String code;
@Column(name = "parent_code")
private String parentCode;
@Column(name = "parent_type")
private String parentType;
public ChildPk() {
}
public ChildPk(String code, String parentCode, String parentType) {
this.code = code;
this.parentCode = parentCode;
this.parentType = parentType;
}
//getters and setters
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof ChildPk)) return false;
ChildPk childPk = (ChildPk) o;
if (!getCode().equals(childPk.getCode())) return false;
if (!getParentCode().equals(childPk.getParentCode())) return false;
return getParentType().equals(childPk.getParentType());
}
@Override
public int hashCode() {
int result = getCode().hashCode();
result = 31 * result + getParentCode().hashCode();
result = 31 * result + getParentType().hashCode();
return result;
}
}
由于我使用Spring,我已经为Parent声明了一个简单的CRUD存储库:
@Repository
public interface ParentRepository extends JpaRepository<Parent, ParentPK> {
}
让我们说我已经在数据库中有一个有两个孩子的父母:
z_Parent
&#34;代码&#34;,&#34;输入&#34;
&#34;家长&#34;,&#34;收养&#34;
z_child
&#34;代码&#34;,&#34; parent_code&#34;,&#34; parent_type&#34;
&#34; Child1&#34;,&#34; Parent&#34;,&#34; Adoptive&#34;
&#34; Child2&#34;,&#34; Parent&#34;,&#34; Adoptive&#34;
,我必须坚持更新版本的父版本,只包含第一个孩子:
public Parent mapFromUpperLayer(){
Parent updatedParent =new Parent("Parent", "Adoptive");
List<Child> children = new ArrayList<>();
Child child1 = new Child("Child1", updatedParent);
child1.setParent(updatedParent);
children.add(child1);
updatedParent.setChildren(children);
return updatedParent;
}
如果我只是用一个孩子保存实体:
@Autowired
private ParentRepository parentRepository;
@Test
@Commit
public void saveUpdate(){
Parent updatedParent = mapFromUpperLayer();
parentRepository.save(updatedParent);
}
然后我得到以下结果(我已经清除了一点日志):
Hibernate: select parent0_.code as code1_50_1_, parent0_.type as type2_50_1_, children1_.parent_code as parent_c2_49_3_, children1_.parent_type as parent_t3_49_3_, children1_.code as code1_49_3_, children1_.code as code1_49_0_, children1_.parent_code as parent_c2_49_0_, children1_.parent_type as parent_t3_49_0_ from z_parent parent0_ left outer join z_child children1_ on parent0_.code=children1_.parent_code and parent0_.type=children1_.parent_type where parent0_.code=? and parent0_.type=?
TRACE 12412 --- : binding parameter [1] as [VARCHAR] - [Parent]
TRACE 12412 --- : binding parameter [2] as [VARCHAR] - [Adoptive]
Hibernate: update z_child set parent_code=null, parent_type=null
where parent_code=? and parent_type=? and code=?
TRACE 12412 --- : binding parameter [1] as [VARCHAR] - [Parent]
TRACE 12412 --- : binding parameter [2] as [VARCHAR] - [Adoptive]
TRACE 12412 --- : binding parameter [3] as [VARCHAR] - [Child2]
TRACE 12412 --- : binding parameter [4] as [VARCHAR] - [Parent]
INFO 12412 --- : HHH000010: On release of batch it still contained JDBC statements
WARN 12412 --- : SQL Error: 0, SQLState: 22023
ERROR 12412 --- : L'indice de la colonne est hors limite : 4, nombre de colonnes : 3.
这里有两个问题。 Hibernate正确识别要从父项中删除 Child2 生成更新而不是删除查询。我完全使用双向关系以避免这种情况,但似乎我还没有完全理解它是如何工作的。当然,它生成的更新包含三列的四个参数(&#34; Parent&#34;出现两次),这很奇怪。
首先,我从数据库中检索了实体,删除了它的子节点并将其父节点设置为 null (removeChild
方法)并添加了新的列表每次父节点到我要保存的实例时都要设置(addChild
方法)。
@Test
@Commit
public void saveUpdate2(){
Parent updatedParent = mapFromUpperLayer();
Parent persistedParent = parentRepository.findOne(new ParentPK(updatedParent.getCode(), updatedParent.getType()));
//remove all the children and add the new collection, both one by one
(new ArrayList<>(persistedParent.getChildren()))
.forEach(child -> persistedParent.removeChild(child));
updatedParent.getChildren().forEach(child -> persistedParent.addChild(child));
parentRepository.save(persistedParent);
}
其次我尝试了this question的解决方案,即我已经在ChildPK中直接声明了@ManyToOne部分:
@Embeddable
class ChildPk implements Serializable {
@Column(name = "code")
private String code;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumns({
@JoinColumn(name = "parent_code", referencedColumnName = "code"),
@JoinColumn(name = "parent_type", referencedColumnName = "type")
})
private Parent parent;
public ChildPk() {
}
public ChildPk(String code, Parent parent) {
this.code = code;
this.parent = parent;
}
....
在这两种情况下,我都会得到相同的生成查询和相同的错误。
如何构建我的父子关系,以便在保存新版本的父版本时Hibernate能够删除已删除的子项?理想情况下,我不想过多地更改数据库的结构 - 例如,连接表实现起来相当费时。
不太重要但有趣:为什么Hibernate试图绑定四个参数&#34; [Parent],[Adoptive],[Child2],[Parent]&#34;在更新查询中识别Child2?
感谢您的耐心等待!
答案 0 :(得分:1)
Parent.children
上的注释是问题的根源。
添加mappedBy
,删除父级的@JoinColumns
。
正确的设置方法:
@OneToMany(mappedBy = "parent", fetch = FetchType.EAGER, cascade =
CascadeType.ALL, orphanRemoval = true)
List<Child> children = new ArrayList<>();
我相信为删除生成的查询是理想的结果。
Hibernate: delete from z_child where code=? and parent_code=? and parent_type=?
此外,removeChild
可以简化 - 不需要将子父级设置为null - 无论如何都会处理它。这不会影响生成的查询。
public void removeChild(Child child){
// child.setParent(null); No need to do that
children.remove(child);
}