【问题标题】:Nested fetch join with JPQL and Hibernate使用 JPQL 和 Hibernate 的嵌套提取连接
【发布时间】:2015-02-16 00:53:53
【问题描述】:

我正在编写一个 JPQL 查询(使用 Hibernate 作为我的 JPA 提供程序)来获取一个实体 Company 及其几个关联。这适用于我的“简单”多对多关联,如下所示:

@Entity
@Table(name = "company")
@NamedQueries({
        @NamedQuery(
                name = "Company.profile.view.byId",
                query = "SELECT c " +
                        "FROM Company AS c " +
                        "INNER JOIN FETCH c.city AS city " + <-- @ManyToOne
                        "LEFT JOIN FETCH c.acknowledgements " + <-- @ManyToMany
                        "LEFT JOIN FETCH c.industries " + <-- @ManyToMany
                        "WHERE c.id = :companyId"
        )
})
public class Company { ... }

Hibernate 创建一个查询来获取上述内容,这很好。但是,我的 Company 实体也与存储在中间表中的数据存在多对多关联,因此这被映射为三个实体之间的 @OneToMany@ManyToOne 关联。

公司 服务

这是我的代码中的三个实体。所以Company 实例有一个CompanyService 实体的集合,每个实体都与Service 实例有关系。我希望这是有道理的 - 否则请检查问题末尾的源代码。

现在我想通过修改上述查询来获取给定公司的服务。我事先读到 JPA 不允许嵌套 fetch 连接,甚至不允许连接别名,但是一些 JPA 提供程序确实支持它,所以我用 Hibernate 试试运气。我尝试这样修改查询:

@Entity
@Table(name = "company")
@NamedQueries({
        @NamedQuery(
                name = "Company.profile.view.byId",
                query = "SELECT c " +
                        "FROM Company AS c " +
                        "INNER JOIN FETCH c.city AS city " +
                        "LEFT JOIN FETCH c.acknowledgements " +
                        "LEFT JOIN FETCH c.industries " +
                        "LEFT JOIN FETCH c.companyServices AS companyService " +
                        "LEFT JOIN FETCH companyService.service AS service " +
                        "WHERE c.id = :companyId"
        )
})
public class Company { ... }

现在,Hibernate 不再创建单个查询,而是创建以下查询:

#1
select ...
from company company0_
inner join City city1_ on company0_.postal_code = city1_.postal_code
[...]
left outer join company_service companyser6_ on company0_.id = companyser6_.company_id
left outer join service service7_ on companyser6_.service_id = service7_.id
where company0_.id = ?

#2
select ...
from company company0_
inner join City city1_ on company0_.postal_code = city1_.postal_code
where company0_.id = ?

#3
select service0_.id as id1_14_0_, service0_.default_description as default_2_14_0_, service0_.name as name3_14_0_
from service service0_
where service0_.id = ?

#4
select service0_.id as id1_14_0_, service0_.default_description as default_2_14_0_, service0_.name as name3_14_0_
from service service0_
where service0_.id = ?

查询 #1 我省略了不相关的连接,因为这些都可以。它似乎选择了我需要的所有数据,包括服务和中间实体数据 (CompanyService)。

查询 #2 此查询只是从数据库中获取公司及其City。城市关联是热切获取的,但即使我将其更改为延迟获取,仍然会生成查询。所以说实话,我不知道这个查询是干什么用的。

查询 #3 + 查询 #4 这些查询基于 ID 查找 Service 实例,可能基于在查询 #1 中获取的服务 ID。我认为不需要此查询,因为此数据已在查询 #1 中获取(正如来自查询 #2 的数据已在查询 #1 中获取)。此外,如果公司有很多服务,这种方法显然不能很好地扩展。

奇怪的是,查询 #1 似乎做了我想要的,或者至少它获取了我需要的数据。我只是不知道为什么 Hibernate 创建查询 #2、#3 和 #4。所以我有以下问题:

  • 为什么 Hibernate 创建查询 #2、#3 和 #4?我可以避免吗?
  • 即使 JPA 不支持,Hibernate 是否支持嵌套关联获取?如果是这样,我将如何处理?
  • 这种行为是正常的,还是因为我正在尝试做的事情不受支持,因此我得到了奇怪的结果?这看起来很奇怪,因为查询 #1 看起来非常好

任何错误提示或完成我想要的替代解决方案将不胜感激。下面是我的代码(不包括 getter 和 setter)。提前非常感谢!

公司实体

@Entity
@Table(name = "company")
@NamedQueries({
        @NamedQuery(
                name = "Company.profile.view.byId",
                query = "SELECT c " +
                        "FROM Company AS c " +
                        "INNER JOIN FETCH c.city AS city " +
                        "LEFT JOIN FETCH c.acknowledgements " +
                        "LEFT JOIN FETCH c.industries " +
                        "LEFT JOIN FETCH c.companyServices AS companyService " +
                        "LEFT JOIN FETCH companyService.service AS service " +
                        "WHERE c.id = :companyId"
        )
})
public class Company {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column
    private int id;

    // ...

    @ManyToOne(fetch = FetchType.EAGER, targetEntity = City.class, optional = false)
    @JoinColumn(name = "postal_code")
    private City city;

    @ManyToMany(fetch = FetchType.LAZY)
    @JoinTable(name = "company_acknowledgement", joinColumns = @JoinColumn(name = "company_id"), inverseJoinColumns = @JoinColumn(name = "acknowledgement_id"))
    private Set<Acknowledgement> acknowledgements;

    @ManyToMany(fetch = FetchType.LAZY)
    @JoinTable(name = "company_industry", joinColumns = @JoinColumn(name = "company_id"), inverseJoinColumns = @JoinColumn(name = "industry_id"))
    private Set<Industry> industries;

    @OneToMany(fetch = FetchType.LAZY, mappedBy = "company")
    private Set<CompanyService> companyServices;
}

CompanyService 实体

@Entity
@Table(name = "company_service")
@IdClass(CompanyServicePK.class)
public class CompanyService implements Serializable {
    @Id
    @ManyToOne(targetEntity = Company.class)
    @JoinColumn(name = "company_id")
    private Company company;

    @Id
    @ManyToOne(targetEntity = Service.class)
    @JoinColumn(name = "service_id")
    private Service service;

    @Column
    private String description;
}

服务实体

@Entity
@Table(name = "service")
public class Service {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column
    private int id;

    @Column(length = 50, nullable = false)
    private String name;

    @Column(name = "default_description", nullable = false)
    private String defaultDescription;
}

获取数据

public Company fetchTestCompany() {
    TypedQuery<Company> query = this.getEntityManager().createNamedQuery("Company.profile.view.byId", Company.class);
    query.setParameter("companyId", 123);

    return query.getSingleResult();
}

【问题讨论】:

    标签: java hibernate jpa jpql


    【解决方案1】:

    好吧,看来我想通了。通过在CompanyService 中将获取类型设置为FetchType.LAZY,Hibernate 停止生成所有基本上再次获取相同数据的冗余查询。这是实体的新版本:

    @Entity
    @Table(name = "company_service")
    @IdClass(CompanyServicePK.class)
    public class CompanyService implements Serializable {
        @Id
        @ManyToOne(fetch = FetchType.LAZY, targetEntity = Company.class)
        @JoinColumn(name = "company_id")
        private Company company;
    
        @Id
        @ManyToOne(fetch = FetchType.LAZY, targetEntity = Service.class)
        @JoinColumn(name = "service_id")
        private Service service;
    
        @Column
        private String description;
    }
    

    JPQL 查询保持不变。

    但是,在我的 Company 实体具有的关联数量的特殊情况下,我得到了很多重复的数据,因此让 Hibernate 执行附加查询更有效。我通过从我的 JPQL 查询中删除两个连接提取并将我的查询代码更改为以下来完成此操作。

    @Transactional
    public Company fetchTestCompany() {
        TypedQuery<Company> query = this.getEntityManager().createNamedQuery("Company.profile.view.byId", Company.class);
        query.setParameter("companyId", 123);
    
        try {
            Company company = query.getSingleResult();
            Hibernate.initialize(company.getCompanyServices());
    
            return company;
        } catch (NoResultException nre) {
            return null;
        }
    }
    

    通过初始化companyServices 关联,Hibernate 执行另一个查询来获取服务。在我的特定用例中,这比通过一次查询获取大量冗余数据要好。

    我希望这对某人有所帮助。如果有人有更好的解决方案/改进,我当然很乐意听到。

    【讨论】:

      【解决方案2】:

      根据您写的内容,我会说不支持嵌套提取。这是我对您的结果的理解:

      • 查询 #1 没问题,并且加入了它需要的所有内容,这很好
      • 但是,Query #2 我认为得到CompanyService#company(急切的city 导致inner join City
      • 查询 #3 得到 CompanyService#service
      • 查询 #4 对我来说是个谜

      我知道这不是答案,但它可能会帮助您了解后台发生的事情。

      【讨论】:

      • 谢谢。我想知道为什么最后三个查询甚至被执行,而第一个查询就是 Hibernate 需要做的我要求它做的事情。但也许你是对的,它不受支持 - 但如果是这种情况,那么它正确地组装查询 #1 以完全符合我的预期是很奇怪的。奇怪的! :-)
      • 这很奇怪,但不幸的是我无能为力,这些只是我的假设。希望看到熟悉 Hibernate 内部工作原理的人发表评论。
      猜你喜欢
      • 2011-11-24
      • 2011-03-30
      • 1970-01-01
      • 2023-03-18
      • 2012-11-09
      • 2012-11-17
      • 2012-01-08
      • 2022-08-16
      • 2015-03-04
      相关资源
      最近更新 更多