Hibernate LEFT JOIN FETCH 带有 ON 子句或替代方案

iqu*_*rio 3 java sql hibernate jpa spring-boot

我有父母子女关系。

父类有以下字段(id, name),子类有以下列(id, name, date, parent_id)。我想要返回的最终结果 JSON 如下。我也总是想返回父母,即使它没有孩子,这就是为什么我LEFT OUTER JOIN在孩子上有ON条款

[

{
    "name": "parnet1",
    "child": {
      "2021-01-01": {
        "name": "child1"
      },
      "2021-01-02": {
        "name": "child2"
      }
    }
  },
  {
    "name": "parnet2",
    "child": {}
    }
  }
]
Run Code Online (Sandbox Code Playgroud)

此示例的数据库示例

parents

id    |     name
1     |     parent 1
2     |     parent 2
Run Code Online (Sandbox Code Playgroud)

child

id    |     name    |    date    |    parent_id
1     |     child1  |  2021-01-01|    1
2     |     child2  |  2021-01-02|    1
3     |     child3  |  2020-12-31|    2
Run Code Online (Sandbox Code Playgroud)

对于以下示例,我传递 2021-01-01 的数据

所以月份是 1 月 (1),年份是 2021

在父类中,我有一个映射来引用子类

@OneToMany(mappedBy = "parent", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
@JsonManagedReference
@MapKey(name = "date")
private Map<LocalDate, Child> children;
Run Code Online (Sandbox Code Playgroud)

这是我的查询

@Query("select p from Parent p left join p.child c on YEAR(c.date) = :dateYear and MONTH(c.date) = :dateMonth
Run Code Online (Sandbox Code Playgroud)

问题是 hibernate 自动运行了第二个查询,如下所示:

select * from Child where c.parent_id = ?
Run Code Online (Sandbox Code Playgroud)

这最终会让所有的子节点的连接条件不成立。

然后我尝试了这个

 @Query("select new Parent(p.id, p.name, c.id, c.date, c.name) from Parent p left join p.child c on YEAR(c.date) = :dateYear and MONTH(c.date) = :dateMonth
Run Code Online (Sandbox Code Playgroud)

并制作了这个构造函数

public Parent(int id, String name, int childId, LocalDate date, String childName) {

    this.id = id;
    this.name = name;
    this.children = new HashMap<LocalDate, Child>();
    if (childId != null) {
        Child child = new Child();
        child.setId(id);
        child.setName(name);
        this.children.put(date, child);
    }
}
Run Code Online (Sandbox Code Playgroud)

但问题是,当我希望数组的顶层长度为 2 时,我得到了一个长度为 4 的 JSON 数组,因为当前数据库中只有 2 个父级。

我如何修改它以获得我想要的 JSON 负载,该负载发布在问题的顶部。

非常感谢

编辑

如果我要传递 date = '2021-01-01',则使用以下内容不起作用,因为不会返回 child2

select distinct p from Parent p 
left join fetch p.children c 
where (YEAR(c.date) = :dateYear and MONTH(c.date) = :dateMont) 
or c.parent is null
Run Code Online (Sandbox Code Playgroud)

编辑2(关于自动生成的查询)

我使用 spring boot 将其作为 API 运行,但没有额外的处理,这是我的 API 和当前查询。

@GetMapping(path = "/parents/{date}", produces = { "application/json" })
@Operation(summary = "Get all parents for date")
public @ResponseBody List<Parent> getParentsByDate(@PathVariable("date") String dateString) {

    DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
    LocalDate dateOccurred = LocalDate.parse(dateString, formatter);

    return parentRepository.getParentsForDate(dateOccurred);


}
Run Code Online (Sandbox Code Playgroud)

然后在我的存储库中我只有我的@Query,这是我目前在课堂上拥有的ParentRepository内容

public interface ParentRepository extends CrudRepository<Parent, Integer> {

    @Query("select p from Parent p left join fetch p.children c where (YEAR(c.date) = :dateYear and MONTH(c.date) = :dateMonth) or c.parent is null")
    public List<Parent> getParentsForDate(@Param("dateOccurred") LocalDate dateOccurred
}
Run Code Online (Sandbox Code Playgroud)

但正如所说,这个问题是空检查确实工作正常,如果父级在另一个月有任何子级,但不是当前的子级,则不会返回父级

sav*_*ver 7

解决方案


我找到了获取和多条件join操作的解决方案:我们可以应用EntityGraph功能:

@NamedEntityGraph(name = "graph.Parent.children", attributeNodes = @NamedAttributeNode("children"))
public class Parent {
 ...
}
Run Code Online (Sandbox Code Playgroud)

在存储库中,您需要将其EntityGraphQuery如下链接:

...
@Query("select distinct p from Parent p left join p.children c on YEAR(c.date) = :dateYear and MONTH(c.date) = :dateMont")
@EntityGraph(value = "graph.Parent.children")
List<Parent> findAllParents(Integer dateYear, Integer dateMont);
...
Run Code Online (Sandbox Code Playgroud)

以前的解决方案


如您所知,Hibernate 不允许在onfor中使用多个条件fetch join,如 result:with-clause not allowed on fetched associations; use filters异常,因此我建议使用以下 HQL 查询:

@NamedEntityGraph(name = "graph.Parent.children", attributeNodes = @NamedAttributeNode("children"))
public class Parent {
 ...
}
Run Code Online (Sandbox Code Playgroud)

注意: distinct避免父实体重复是必需的,关于这一点,有文章:https ://thorben-janssen.com/hibernate-tips-apply-distinct-to-jpql-but-not-sql-query/

结果,Hibernate 将生成一个查询,支持没有孩子的父母,如果孩子应该按照发布的 json 示例进行排序,则需要添加@SortNaturalforchildren字段:

@OneToMany(mappedBy = "parent", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
@JsonManagedReference
@MapKey(name = "date")
@SortNatural
private Map<LocalDate, Child> children;
Run Code Online (Sandbox Code Playgroud)

结果,json 将与发布的相同。

建议:不要在运行时使用计算值,因为这些操作无法应用索引,在您的情况下,我建议使用虚拟列(如果是 Mysql)或仅使用准备好的值进行搜索的单独索引列,并尝试通过以下方式连接表索引,这些建议将加快您的查询执行速度。

  • Stackoverflow 上有无数线程处理这个问题(如症状“在获取的关联上不允许使用 with-clause;使用过滤器”所示),并且您使用 EntityGraph 的解决方案是我能找到的唯一有效的解决方案。做得好! (2认同)