【问题标题】:JPA query parent with only selected children仅具有选定子项的 JPA 查询父项
【发布时间】:2021-04-12 16:01:08
【问题描述】:

我有这些课程ParentChild

Parent.java

@Entity
@Table(name = "parents")
public class Parent implements Serializable {

private static final long serialVersionUID = 873222010704108510L;

@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;

private String name;

@OneToMany(cascade=CascadeType.ALL)
@JsonManagedReference
private Set<Child> childs;

// getters and setters

Child.java

@Entity
@Table(name = "childs")
public class Child implements Serializable {

private static final long serialVersionUID = -6756632049453970501L;

@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;

private String name;

@ManyToOne(fetch = FetchType.LAZY)
@JsonBackReference
private Parent parent;

// getters and setters

现在我的 SQL DB 有 2 个子级,child1child2,它们都有相同的父级。

现在我想得到父母和唯一的孩子1,所以在我的ParentRepository.java 我有以下内容

@Query("select p from Parent p LEFT JOIN p.childs c where c.name = 'child1'")
public List<Parent> getParent();

但是当我调用它时,我得到了两个孩子,这是因为我看到 hibernate 正在运行 2 个查询,如下所示:

Query 1(我想要的查询)

/* select
    p 
from
    Parent p 
LEFT JOIN
    p.childs c 
where
    c.name = 'child1' */ select
        parent0_.id as id1_1_,
        parent0_.name as name2_1_ 
    from
        parents parent0_ 
    left outer join
        parents_childs childs1_ 
            on parent0_.id=childs1_.parent_id 
    left outer join
        childs child2_ 
            on childs1_.childs_id=child2_.id 
    where
        child2_.name='child1'

然后它运行这个查询 - 我不想要它,因为它正在获取所有的孩子

 select
    childs0_.parent_id as parent_i1_2_0_,
    childs0_.childs_id as childs_i2_2_0_,
    child1_.id as id1_0_1_,
    child1_.name as name2_0_1_,
    child1_.parent_id as parent_i3_0_1_ 
from
    parents_childs childs0_ 
inner join
    childs child1_ 
        on childs0_.childs_id=child1_.id 
where
    childs0_.parent_id=?

我怎样才能得到它,所以第二个查询没有运行,我得到了父母,只有我想要返回的孩子,即使没有匹配的孩子,我也想返回父母,这就是为什么我有左外加入

【问题讨论】:

    标签: java mysql hibernate jpa


    【解决方案1】:

    选项 1

    换个角度想,在Child 上搜索并获得他们的Parent 怎么样。

    将新的构造函数添加到Parent 类中

    public Parent(Integer id, String name, Child child) {
        this.id = id;
        this.name = name;
        this.childs = new HashSet<>();
        this.childs.add(child);
    }
    
    public Parent() {
    }
    

    使用以下脚本更改 JPA 查询。

    @Query("select new Parent(c.parent.id, c.parent.name, c) from Child c where c.name = 'child1'")
    List<Parent> getParent();
    

    Hibernate 为两个 Child 记录生成 3 个 SQL(所有 Child 计数中的多个查询)

    Hibernate: select child0_.parent_id as col_0_0_, parent1_.name as col_1_0_, child0_.id as col_2_0_ from childs child0_ cross join parents parent1_ where child0_.parent_id=parent1_.id and child0_.name='child1'
    Hibernate: select child0_.id as id1_0_0_, child0_.name as name2_0_0_, child0_.parent_id as parent_i3_0_0_ from childs child0_ where child0_.id=?
    Hibernate: select child0_.id as id1_0_0_, child0_.name as name2_0_0_, child0_.parent_id as parent_i3_0_0_ from childs child0_ where child0_.id=?
    

    如果只执行一条 SQL,可以升级下面的Parent 类和 Jpa 查询。

    父构造函数:

    public Parent(Integer id, String name, Integer childId, String childName) {
        this.id = id;
        this.name = name;
        this.childs = new HashSet<>();
        Child child = new Child();
        child.setId(childId);
        child.setName(childName);
        this.childs.add(child);
    }
    

    在 jpa 查询中添加了新参数

    @Query("select new Parent(c.parent.id, c.parent.name, c.id, c.name) from Child c where c.name = 'child1'")
    List<Parent> getParent();
    

    Hibernate 生成单个查询。

    Hibernate: select child0_.parent_id as col_0_0_, parent1_.name as col_1_0_, child0_.id as col_2_0_, child0_.name as col_3_0_ from childs child0_ cross join parents parent1_ where child0_.parent_id=parent1_.id and child0_.name='child1'
    

    测试

    @SpringBootTest
    class DemoApplicationTests {
    
        @Autowired
        private ParentRepository parentRepository;
        
        @Test
        void contextLoads() {
            Parent parent = new Parent();
            parent.setName("p1");
            parent.setChilds(new HashSet<>());
            parent.getChilds().add(new Child("child1", parent));
            parent.getChilds().add(new Child("child2", parent));
    
            parentRepository.save(parent);
    
            parent = new Parent();
            parent.setName("p2");
            parent.setChilds(new HashSet<>());
            parent.getChilds().add(new Child("child1", parent));
    
            parentRepository.save(parent);
    
            List<Parent> parents = parentRepository.getParent();
            Assertions.assertEquals(2, parents.size());
    
            for (Parent parent1 : parents) {
                System.out.println(parent1);
            }
        }
    }
    

    输出

    Parent(id=1, name=p1, childs=[Child(id=3, name=child1)])
    Parent(id=4, name=p2, childs=[Child(id=5, name=child1)])
    

    选项 2

    获取所有子节点并在 java 端管理 Parent

    public interface ChildRepository extends JpaRepository<Child, Integer> {
        @Query("select c from Child c left join fetch c.parent where c.name = 'child1'")
        List<Child> search();
    }
    

    Hibernate 生成单个 SQL。

    Hibernate: select child0_.id as id1_0_0_, parent1_.id as id1_1_1_, child0_.name as name2_0_0_, child0_.parent_id as parent_i3_0_0_, parent1_.name as name2_1_1_ from childs child0_ left outer join parents parent1_ on child0_.parent_id=parent1_.id where child0_.name='child1'
    

    测试

    @SpringBootTest
    class DemoApplicationTests2 {
    
        @Autowired
        private ParentRepository parentRepository;
    
        @Autowired
        private ChildRepository childRepository;
    
        @Test
        void contextLoads() {
            Parent parent = new Parent();
            parent.setName("p1");
            parent.setChilds(new HashSet<>());
            parent.getChilds().add(new Child("child1", parent));
            parent.getChilds().add(new Child("child2", parent));
    
            parentRepository.save(parent);
    
            parent = new Parent();
            parent.setName("p2");
            parent.setChilds(new HashSet<>());
            parent.getChilds().add(new Child("child1", parent));
    
            parentRepository.save(parent);
    
            // search children
            List<Child> children = childRepository.search();
    
            // create parent hash map
            Map<Integer, Parent> parentMap = children.stream()
                    .map(Child::getParent)
                    .distinct()
                    .peek(p -> {
                        p.setChilds(new HashSet<>());
                    })
                    .collect(Collectors.toMap(Parent::getId, p -> p));
    
            // add children manually
            for (Child child : children) {
                parentMap.get(child.getParent().getId()).getChilds().add(child);
            }
    
            // get parents
            Collection<Parent> parents = parentMap.values();
    
            Assertions.assertEquals(2, parents.size());
    
            for (Parent parent1 : parents) {
                System.out.println(parent1);
            }
        }
    }
    

    输出

    Parent(id=1, name=p1, childs=[Child(id=3, name=child1)])
    Parent(id=4, name=p2, childs=[Child(id=5, name=child1)])
    

    额外

    获取所有Parent 并填充匹配的Child 记录。我们需要对预定义的 Parent 构造函数进行一些更改,并将 where 的新 JPA 查询替换为 and 关键字。

    父构造函数: 有一个额外的childId 控件来避免插入空子记录。

    public Parent(Integer id, String name, Integer childId, String childName) {
        this.id = id;
        this.name = name;
        this.childs = new HashSet<>();
        if (childId != null) {
            Child child = new Child();
            child.setId(childId);
            child.setName(childName);
            this.childs.add(child);
        }
    }
    

    ParentRepository:没有where键,因为它限制了我们的结果,我们只需要and而不是它。

    @Query("select new Parent(p.id, p.name, c.id, c.name) from Parent p left outer join Child c on c.parent = p and c.name = 'child2'")
    List<Parent> getParent();
    

    休眠查询: 刚刚执行了一个查询。

    Hibernate: select parent0_.id as col_0_0_, parent0_.name as col_1_0_, child1_.id as col_2_0_, child1_.name as col_3_0_ from parents parent0_ left outer join childs child1_ on (child1_.parent_id=parent0_.id and child1_.name='child2')
    

    测试

    
    List<Parent> parents = parentRepository.getParent();
    Assertions.assertEquals(2, parents.size());
    
    for (Parent parent1 : parents) {
        System.out.println(parent1);
    }
    

    输出:我们正在搜索child2,一个记录有这个,另一个是空的。

    Parent(id=1, name=p1, childs=[Child(id=2, name=child2)])
    Parent(id=4, name=p2, childs=[])
    

    【讨论】:

    • 嘿,很好的答案,但我的问题还有另一部分,比如我想查询 name='child3' 但子 3 不存在的位置,我仍然想返回父级,这就是为什么我有左外连接
    • 嗨@iqueqiorio 我添加了一个Extra 部分。我认为这是你想要的。我竞选child2。有一个记录有child2,其他没有。于是两人都回来了。此外,对于child3,返回两条记录,而childrens 为空。
    猜你喜欢
    • 2014-02-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-10-30
    • 1970-01-01
    • 2020-03-25
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多