【问题标题】:Spring Data/Hibernate Generates two queries instead of a JOINSpring Data/Hibernate 生成两个查询而不是一个 JOIN
【发布时间】:2024-04-27 22:25:02
【问题描述】:

上下文:我有两个表格:问卷和问题部分。问卷可以有多个问题部分。问卷和问题部分都有开始日期和结束日期,以确定它们是否是活动记录。

这是我写的实体:

@Entity
@Data
public class Questionnaire {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private UUID id;
private String name;
private Date startDate;
private Date endDate;
private String description;

@OneToMany(cascade = CascadeType.All,
          fetch = FetchType.LAZY,
          mappedBy = "questionnaire")
@JsonManagedReference
private List<QuestionSection> questionSections = new ArrayList<QuestionSection>();
}

@Entity
@Data
public class QuestionSection {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private UUID id;
private String name;
private String description;
private int sectionLevel;
private Date startDate;
private Date endDate;

@ManyToOne(fetch = FetchType.LAZY, optional = false)
@JoinColumn(name = "QUESTIONNAIRE_ID", nullable = false)
@JsonBackReference
private Questionnaire questionnaire;
}

这里是我的 Spring Data Repository,只有一个声明的方法:

public interface QuestionnaireRepository extends JpaRepository<Questionnaire, UUID> {

  Questionnaire findByNameAndEndDateIsNull(String name);

  // Previous goal query, but worked all the way back to the above simple query
  // Questionnaire findByIdAndQuestionSectionsEndDateIsNull(UUID id);
  }

上述派生查询生成两个如下所示的查询:

-- For brevity

select questionnaire.id as id
  questionnaire.description as description
  questionnaire.end_date as end_date
  questionnaire.start_date as start_date
  from questionnaire 
  where questionnaire.name='Foo' and (questionnaire.end_date is null)

select questionsection.questionnaire_id as questionnaire id
    ...rest of fields here...
    from question_section
    where questionsection.questionnaire_id = id from above query

然后 Spring Data 或 Hibernate 将上述两个查询组合成一个代表问卷对象的数据对象并返回。

我的问题是我希望 一个 查询在两个表之间运行,而不是两个表,然后将结果合并到内存中。一般来说,我对 Spring Data 和 ORM 非常有经验,并且无法找到任何关于为什么会发生这种情况的文档。老实说,我不在乎,只是我的初衷是查询父实体并“过滤”出具有结束日期(未激活)的子实体。这个派生查询(上面已注释掉)表现出相同的行为,最终导致返回的数据集包含结束日期的问题部分。 我知道还有 100 种其他方法可以解决这个问题(这很好),所以如果有人对这种行为有任何见解,这对我来说更具教育意义。我可能会遗漏一些非常简单的东西。

【问题讨论】:

  • 只执行一次查询就可以得到问卷。但是随后您(或您的 JSON 序列化程序)可能会遍历问题部分,因此 Hibernate 需要初始化惰性集合,因此它会生成第二个查询。如果您想一次加载所有内容,则需要左连接提取,如文档所述:docs.jboss.org/hibernate/orm/current/userguide/html_single/…
  • @JBNizet 当然,我同意您在 Hibernate 文档中指出的内容,但前提是我没有尝试通过派生查询来执行此操作。我想我应该指定我想通过 Spring Data 派生查询而不是使用指定的查询来完成此操作。将 fetch 模式更改为 eager 仍会生成两个查询。
  • 你想使用错误的工具来完成这项工作......

标签: java hibernate spring-data-jpa spring-data


【解决方案1】:

您应该能够使用 JPA 2.1 中引入的实体图功能来执行此操作。

https://www.baeldung.com/jpa-entity-graph

Spring Data 通过 @NamedEntityGraph@EntityGraph 注释提供对实体图的支持:

https://www.baeldung.com/spring-data-jpa-named-entity-graphs

所以在你的代码中:

实体:

@Entity
@NamedEntityGraph(name = "Questionnaire.questionSections",
    attributeNodes = @NamedAttributeNode("questionSections ")
)
public class Questionnaire{
    //...
}

存储库:

public interface QuestionnaireRepository extends JpaRepository<Questionnaire, UUID> {

  @NamedEntityGraph("Questionnaire.questionSections")
  Questionnaire findByNameAndEndDateIsNull(String name);
}

【讨论】:

    【解决方案2】:
    public interface QuestionnaireRepository extends JpaRepository<Questionnaire, UUID> {
      @EntityGraph(attributePaths = { "questionSections" })
      Questionnaire findByNameAndEndDateIsNull(String name);
    }
    

    【讨论】: