使用 JPQL 和 Hibernate 进行嵌套获取连接

问题描述 投票:0回答:3

我正在编写一个 JPQL 查询(使用 Hibernate 作为我的 JPA 提供程序)来获取一个实体

Company
及其几个关联。这与我的“简单”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 创建一个查询来获取上述内容,这很好。但是,我的

Company
实体还与中间表中存储的数据具有多对多关联,因此为什么将其映射为三个实体之间的
@OneToMany
@ManyToOne
关联。

公司<-- CompanyService -->服务

这些是我的代码中的三个实体。因此,一个

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

现在我想通过修改上述查询来获取给定公司的服务。我提前了解到 JPA 不允许嵌套获取连接,甚至不允许连接别名,但一些 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?我可以避免吗?
  • Hibernate 是否支持嵌套关联获取,即使 JPA 不支持?如果是这样,我该如何处理?
  • 这种行为正常吗,还是因为我想做的事情不受支持,因此我得到了奇怪的结果?这看起来很奇怪,因为查询 #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
3个回答
1
投票

好吧,看来我已经明白了。通过将

FetchType.LAZY
中的获取类型设置为
CompanyService
,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 执行另一个查询来获取服务。在我的特定用例中,这比通过一个查询获取大量冗余数据要好。

我希望这对某人有帮助。如果有人有更好的解决方案/改进,那么我当然很高兴听到他们。


0
投票

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

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

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


0
投票

我知道他的主题很旧,但这可能会对某人有所帮助。我认为 hibernate 在连接获取期间发出额外查询的原因是您还需要使用多对一的连接获取 - 当您使用 jpql 时,默认情况下不会加载这些查询。

© www.soinside.com 2019 - 2024. All rights reserved.