简体   繁体   English

使用JPQL和Hibernate嵌套提取连接

[英]Nested fetch join with JPQL and Hibernate

I am writing a JPQL query (with Hibernate as my JPA provider) to fetch an entity Company and several of its associations. 我正在编写一个JPQL查询(使用Hibernate作为我的JPA提供程序)来获取实体Company及其几个关联。 This works fine with my "simple" ManyToMany associations, like so: 这适用于我的“简单”ManyToMany关联,如下所示:

@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 creates a single query to fetch the above, which is good. Hibernate创建一个查询来获取上面的内容,这很好。 However, my Company entity also has a many-to-many association with data stored in the intermediate table, hence why this is mapped as @OneToMany and @ManyToOne associations between three entities. 但是,我的Company实体还与存储在中间表中的数据具有多对多关联,因此将其映射为三个实体之间的@OneToMany@ManyToOne关联。

Company <-- CompanyService --> Service 公司< - CompanyService - > Service

These are the three entities that I have in my code. 这些是我的代码中的三个实体。 So a Company instance has a collection of CompanyService entities, which each has a relation to a Service instance. 因此, Company实例具有CompanyService实体的集合,每个实体都与Service实例有关系。 I hope that makes sense - otherwise please check the source code at the end of the question. 我希望这是有道理的 - 否则请检查问题末尾的源代码。

Now I would like to fetch the services for a given company by modifying the above query. 现在我想通过修改上述查询来获取给定公司的服务。 I read in advance that JPA doesn't allow nested fetch joins or even aliases for joins, but that some JPA providers do support it, and so I tried my luck with Hibernate. 我提前读过JPA不允许嵌套的fetch连接甚至是连接的别名,但是有些JPA提供者确实支持它,所以我试试了Hibernate。 I tried to modify the query as such: 我试图修改查询:

@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 { ... }

Now, instead of creating a single query, Hibernate creates the following queries: 现在,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 = ?

Query #1 I left out the irrelevant joins as these are OK. 查询#1我遗漏了不相关的连接,因为这些都没问题。 It appears to select all of the data that I need, including the services and the intermediate entity data ( CompanyService ). 它似乎选择了我需要的所有数据,包括服务和中间实体数据( CompanyService )。

Query #2 This query simply fetches the company from the database and its City . 查询#2此查询只是从数据库及其City提取公司。 The city association is eagerly fetched, but the query is still generated even if I change it to lazy fetching. 急切地获取城市关联,但即使我将其更改为延迟提取,仍会生成查询。 So honestly I don't know what this query is for. 老实说,我不知道这个查询的用途。

Query #3 + Query #4 These queries are looking up Service instances based on ID, presumably based on service IDs fetched in Query #1. 查询#3 +查询#4这些查询正在查找基于ID的Service实例,可能是基于在查询#1中获取的服务ID。 I don't see the need for this query, because this data was already fetched in Query #1 (just as the data from Query #2 was already fetched in Query #1). 我没有看到这个查询的需要,因为这个数据已经在Query#1中提取了(正如Query#2中的数据已经在Query#1中获取)。 Also, this approach obviously does not scale well if a company has many services. 此外,如果公司有许多服务,这种方法显然不能很好地扩展。

The strange thing is that it seems like query #1 does what I want, or at least it fetches the data that I need. 奇怪的是,似乎查询#1做了我想要的,或者至少它获取了我需要的数据。 I just don't know why Hibernate creates query #2, #3 and #4. 我只是不知道为什么Hibernate会创建查询#2,#3和#4。 So I have the following questions: 所以我有以下问题:

  • Why does Hibernate create query #2, #3 and #4? 为什么Hibernate会创建查询#2,#3和#4? And can I avoid it? 我可以避免吗?
  • Does Hibernate support nested association fetching even though JPA doesn't? Hibernate是否支持嵌套关联提取,即使JPA没有? If so, how would I go about it in my case? 如果是这样,我将如何处理它?
  • Is this behavior normal, or is it because what I am trying to do is just not supported, and therefore I get weird results? 这种行为是正常的,还是因为我试图做的事情不被支持,因此我得到了奇怪的结果? This would seem odd, because query #1 looks perfectly fine 这看起来很奇怪,因为查询#1看起来非常好

Any pointers of mistakes or alternative solutions to accomplish what I want would be much appreciated. 任何错误的指针或完成我想要的替代解决方案将非常感激。 Below is my code (getters and setters excluded). 下面是我的代码(排除了getter和setter)。 Thanks a lot in advance! 非常感谢提前!

Company entity 公司实体

@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 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;
}

Service entity 服务实体

@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;
}

Fetching data 获取数据

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

    return query.getSingleResult();
}

Okay, it seems like I figured it out. 好吧,我好像想通了。 By setting the fetch type to FetchType.LAZY in CompanyService , Hibernate stopped generating all of the redundant queries that were basically fetching the same data again. 通过在CompanyService中将获取类型设置为FetchType.LAZY ,Hibernate停止生成所有基本上再次获取相同数据的冗余查询。 Here is the new version of the entity: 这是实体的新版本:

@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;
}

The JPQL query remains the same. JPQL查询保持不变。

However, in my particular case with the number of associations my Company entity has, I was getting a lot of duplicated data back, and so it was more efficient to let Hibernate execute an additional query. 但是,在我的Company实体拥有的关联数量的特定情况下,我得到了大量重复数据,因此让Hibernate执行额外查询更有效。 I accomplished this by removing the two join fetches from my JPQL query and changing my query code to the below. 我通过从我的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;
    }
}

By initializing the companyServices association, Hibernate executes another query to fetch the services. 通过初始化companyServices关联,Hibernate执行另一个查询来获取服务。 In my particular use case, this is better than fetching a ton of redundant data with one query. 在我的特定用例中,这比通过一个查询获取大量冗余数据更好。

I hope this helps someone. 我希望这可以帮助别人。 If anyone has any better solutions/improvements, then I would of course be happy to hear them. 如果有人有任何更好的解决方案/改进,那么我当然会很高兴听到他们。

From what you wrote, I would say that nested fetching isn't supported. 根据您所写的内容,我会说不支持嵌套提取。 This is my understanding of your results: 这是我对你的结果的理解:

  • Query #1 is ok, and joins everything that it needs, this is good 查询#1没问题 ,加入了所需的一切,这很好
  • However, Query #2 I think gets CompanyService#company (with eager city resulting in inner join City ) 但是, 查询#2我认为得到CompanyService#company (急切的city导致inner join City
  • Query #3 gets CompanyService#service 查询#3获取CompanyService#service
  • Query #4 is a mistery to me 查询#4对我来说是个谜

I know this is not an answer, but it might help you understand what's going on in the background. 我知道这不是一个答案,但它可以帮助您了解后台发生的事情。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM