【问题标题】:JPA + Hibernate - Inner Join between parent and child tablesJPA + Hibernate - 父表和子表之间的内部联接
【发布时间】:2012-09-17 18:39:28
【问题描述】:

编辑(完全重新制定的方法):

我正在尝试在一个新项目中推广 JPA 的使用,但我正在努力解决一个所谓的微不足道的问题:两个表(父表和子表)之间的 INNER JOIN。

我将只提供基本信息,而将其他所有信息排除在外。如果需要,请随时询问更多信息。有两个表 LANGUAGE 和 MESSAGE_RESOURCE,其中父表是 LANGUAGE(主键 ID_LANGUAGE),子表有一个外键到父表也称为 ID_LANGUAGE。

语言(父)类:

@Entity
@Table(name = "PF_LANGUAGE")
public class Language {
    @Id
    @Column(name = "ID_LANGUAGE", nullable = false)
    @GeneratedValue(strategy = GenerationType.AUTO)
    private int idLanguage;

    @OneToMany(mappedBy="language",targetEntity=MessageResource.class, fetch=FetchType.EAGER)
    private Collection<MessageResource> messageResources;
}

子类:

@Entity
@Table(name = "PF_MESSAGE_RESOURCE")
public class MessageResource {

    @Id
    @Column(name = "ID_MESSAGE_RESOURCE", nullable = false)
    @GeneratedValue(strategy = GenerationType.AUTO)
    private int idMessageResource;

    @ManyToOne(optional=false)
    @JoinColumn(name="ID_LANGUAGE")
    private Language language;
}

我正在使用命名查询获取结果:

entityManager.createNamedQuery("select l, r from Language l join l.messageResources r");

这会产生一个结果对象数组,其中每个条目包含一个语言、消息资源对。问题是这是在单独的查询中完成的。

我可以在调试输出中看到第一个查询是两个表之间的 INNER JOIN,包含输出中两个表的列,所以这应该足够了。 但是 JPA 正在执行 2 个额外的查询(LANGUAGE 记录的数量)再次获取每个父表的子表值,这应该没有必要。

第一个查询足以获取所有数据:

select
   language0_.ID_LANGUAGE as ID1_5_0_,
   messageres1_.ID_MESSAGE_RESOURCE as ID1_4_1_,
   language0_.CODE as CODE5_0_,
   language0_.DATE_INS  as DATE3_5_0_,
   language0_.DESCRIPTION  as DESCRIPT4_5_0_,
   messageres1_.DATE_INS  as DATE2_4_1_,
   messageres1_.KEY as KEY4_1_,
   messageres1_.ID_LANGUAGE as ID5_4_1_,
   messageres1_.VALUE as VALUE4_1_ 
from
   PF_LANGUAGE language0_ 
inner join
   PF_MESSAGE_RESOURCE messageres1_ 
on language0_.ID_LANGUAGE=messageres1_.ID_LANGUAGE

在第一次内部连接之后,还会对数据库运行两个冗余查询,如下所示(它们对每个 LANGUAGE 表记录运行一次):

select
    messageres0_.ID_LANGUAGE as ID5_5_1_,
    messageres0_.ID_MESSAGE_RESOURCE as ID1_1_,
    messageres0_.ID_MESSAGE_RESOURCE as ID1_4_0_,
    messageres0_.DATE_INS  as DATE2_4_0_,
    messageres0_.KEY as KEY4_0_,
    messageres0_.ID_LANGUAGE as ID5_4_0_,
    messageres0_.VALUE as VALUE4_0_ 
from
    PF_MESSAGE_RESOURCE messageres0_ 
where
    messageres0_.ID_LANGUAGE=?

我需要消除 JPA 生成的两个多余的附加查询。第一个内连接足以获取所有数据。

在这方面需要帮助。有什么线索吗?

【问题讨论】:

  • 有什么问题?你想要什么行为?
  • 我更新了问题信息,期望的行为是 INNER JOIN,但我认为这是问题标题和文本流所隐含的
  • 您是否尝试将 messageResources 的 fetch=FetchType.EAGER 更改为 fetch=FetchType.LAZY ?由于您是明确加入的,因此更改 FetchType 可能会有所帮助。
  • @bodunbodun 切换到 FetchType.LAZY 成功了。随意写一个答案,说明我可以接受它
  • 刚刚添加为 las 答案。

标签: java hibernate jpa jdbc


【解决方案1】:

尝试将 messageResources 的 fetch=FetchType.EAGER 更改为 fetch=FetchType.LAZY 由于您明确加入,将FetchType 更改为LAZY 可能会有所帮助。

【讨论】:

    【解决方案2】:

    我不确定这是否是实际的解决方案,但我对 Eager fetch 的体验很糟糕。它总是以某种我不期望的方式获取。

    您的查询很奇怪,您同时选择 Language 和 MessageResource 没有意义

    你可以试试:

    删除Language与MessageResource的关系中的fetch=FetchType.EAGER,并将查询更改为某事

    select l from Language l join fetch l.messageResources where ....
    

    它应该在一个 SQL 中为您提供语言和该实例的聚合消息资源。

    有些题外话,我想知道为什么 MessageResource.language 是 insertable=false, updatable=false。这似乎与语言中的矛盾,您指定了此字段映射的关系,但您现在使其不可插入/可更新。

    【讨论】:

    • 我在 JVM 启动时获取了所有语言资源,因此我想使用单个内部连接一次性加载它们,而无需任何 where 子句。关于可插入和可更新的属性,不要介意它们。我放置它们是因为 Hibernate/JPA 的重复列投诉,并且我已经找出了投诉的动机并修复了它(即删除了这些属性)。最后一点,这个查询是有意义的,因为我有 Language 中的语言键(即“en_US”、“es_ES”)和 MessageResource 中的键/值(资源包支持表已标准化)
    • 切换到 FetchType.LAZY 成功了,但是当你说这个查询没有意义时我不同意你的观点(正如我在之前的评论中解释的那样)。我保留了我使用的完全相同的查询
    • @JavaJuggler 实际上我的方式允许你做你正在寻找的事情:一次性加载所有消息资源。由于您已经映射了 Language 和 MessageResource 之间的关系,因此您只需通过 from Language l join fetch l.messageResources 获取所有 Language 即可。结果列表中的每种语言都有相应的消息资源列表。您需要做的只是将数据额外按摩成您想要的任何形式。
    • 事实上,在我的回答中,删除 fetch=FetchType.EAGER 确实意味着更改为惰性获取,因为如果我没记错的话,惰性是默认设置
    【解决方案3】:

    我不太明白在这种情况下您为什么期望inner join

    optional = "false" at @ManyToOne 表示每个MessageResource 必须有一个Language,但并不意味着每个Language 必须至少有一个MessageResource,因此在加载left join 时@987654329 @ 在这种情况下是正确的 - 您加载的 Language 可能没有与之关联的 MessageResources。

    编辑:find() 的目的是加载具有给定 id 的对象(如果它存在于数据库中)。如果你想要一些语义不同的东西(例如,加载所有至少有一个MessageResourceLanguages),你需要为它写一个查询,比如select l from Language l join l.messageResources

    【讨论】:

    • 感谢您的澄清。那么我应该如何配置注释以便我可以强制它执行 INNER JOIN 呢?
    • @JavaJuggler:我认为你不能。为什么在这种情况下你需要inner join,这对我来说没有意义?
    • 这种偏好有很多原因。其中之一是 INNER JOIN 和 LEFT OUTER JOIN 之间的功能差异。我不想要关系不存在的结果。我非常怀疑 JPA 不允许在两个表之间进行 INNER JOIN...
    • 我已经在使用您建议的编写查询的方法。我必须继续这个项目,不能再花一天时间研究这个。如果没有其他答案可以仅使用注释解决此问题,我会将您的答案标记为已接受。
    【解决方案4】:

    似乎没有一种标准方法可以使用标准注释来强制生成内部查询(JPA 是一种标准,您正在使用实现提供程序,例如 Hibernate、TopLink、Open JPA 等)。不同的 JPA 实现使用不同的连接策略。更多详情请转至thisthis

    【讨论】:

    • 感谢您的回答。我今天在研究期间已经阅读了这些文章。他们都使用我想避免的“技巧”。第一个使用 JPA-QL / Criteria API。第二个使用子实例调用实体管理器......我很惊讶内部连接不是直截了当
    猜你喜欢
    • 2015-04-06
    • 2019-02-23
    • 1970-01-01
    • 2014-08-12
    • 2016-12-01
    • 2017-06-16
    • 2010-09-15
    • 1970-01-01
    • 2011-01-28
    相关资源
    最近更新 更多