Java 使用SpringBoot和Hibernate与复合pks的双向@OneToMany关系

Java 使用SpringBoot和Hibernate与复合pks的双向@OneToMany关系,java,hibernate,spring-data,one-to-many,Java,Hibernate,Spring Data,One To Many,我在应用程序中有一个现有的父子关系,最近变得更加复杂,因为我们在父项和子项的主键中都添加了一个“type”列。在这之后,添加、阅读和修改孩子很有效,但删除他们是一件痛苦的事 使用Vlad Mihalcea在@OneToMany关系中给出的建议以及复合键的各种示例,我尝试了一个类似于以下模型的实现。然而,删除孩子仍然不起作用,我现在有一个奇怪的错误消息作为奖励 我使用的是Spring Boot 1.4.1和Hibernate 5.1.9.Final 案件 父实体有一个带有两个字段的@Embedde

我在应用程序中有一个现有的父子关系,最近变得更加复杂,因为我们在父项和子项的主键中都添加了一个“type”列。在这之后,添加、阅读和修改孩子很有效,但删除他们是一件痛苦的事

使用Vlad Mihalcea在@OneToMany关系中给出的建议以及复合键的各种示例,我尝试了一个类似于以下模型的实现。然而,删除孩子仍然不起作用,我现在有一个奇怪的错误消息作为奖励

我使用的是Spring Boot 1.4.1和Hibernate 5.1.9.Final

案件 父实体有一个带有两个字段的@EmbeddedId ParentPK和一个带有级联的
子对象
集合。所有
孤儿移除
都设置为true

父母亲
Child
实体有自己的
code
标识符,该标识符与标识父项的两个字符串一起构成另一个复合主键。与父项的关系是双向的,因此子项还有一个
Parent
字段,用@manytone注释

小孩 ChildPK 因为我使用的是Spring,所以我为父级声明了一个简单的CRUD存储库:

@Repository
public interface ParentRepository extends JpaRepository<Parent, ParentPK> {
}
然后我得到以下结果(我已经清除了一点日志):

这里有两个问题。Hibernate正确识别要从父级删除的Child2,并生成更新而不是删除查询。我使用双向关系正是为了避免这种情况,但似乎我还没有完全理解它是如何工作的。当然,它生成的更新包含三列的四个参数(“父”出现两次),这很奇怪

我已经试过了 首先,我从数据库中检索了实体,删除了它的两个子实体,并将它们的父实体设置为null(
removeChild
method),并添加了新的列表,同时注意每次将父实体设置为我要保存的实例(
addChild
method)

在这两种情况下,我得到相同的生成查询和相同的错误

问题
  • 如何构建父子关系,以便在保存新版本的父对象时,Hibernate能够删除删除的子对象?理想情况下,我不希望对数据库的结构做太多更改—例如,连接表的实现相当耗时

  • 不那么重要但很有趣的是:Hibernate到底为什么要绑定四个参数“[Parent]、[Adoptive]、[Child2]、[Parent]”来在更新查询中标识Child2


  • 谢谢你的耐心

    父项上的注释。子项是问题的根源。 添加
    mappedBy
    ,删除父端的
    @JoinColumns

    正确的设置方法是:

    @OneToMany(mappedBy = "parent", fetch = FetchType.EAGER, cascade = 
    CascadeType.ALL, orphanRemoval = true)
    List<Child> children = new ArrayList<>(); 
    
    此外,
    removeChild
    可以简化-不需要将子级的父级设置为null-无论如何都会处理它。这不会影响生成的查询

     public void removeChild(Child child){
        // child.setParent(null); No need to do that
        children.remove(child);
    }
    

    谢谢,你说得对!那么你认为第二个问题也与此相关吗?也就是说,Hibernate准备了四个参数,因为每边有两个@JoinColumns?
    @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;
        }
    }
    
    @Repository
    public interface ParentRepository extends JpaRepository<Parent, ParentPK> {
    }
    
    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.
    
    @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);
    }
    
    @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;
        }
        ....
    
    @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=?
    
     public void removeChild(Child child){
        // child.setParent(null); No need to do that
        children.remove(child);
    }