减少对包含实体的 JPQL POJO 的查询次数

Posted

技术标签:

【中文标题】减少对包含实体的 JPQL POJO 的查询次数【英文标题】:Reduce number of queries for JPQL POJO containing an entity 【发布时间】:2017-02-14 19:42:48 【问题描述】:

实体关系:事务(@ManyToOne - 默认为渴望)-> 帐户

String sql = "SELECT new com.test.Pojo(t.account, SUM(t.value)) FROM Transaction t GROUP BY t.account";
List list = entityManager.createQuery(sql).getResultList();

默认情况下,使用 Hibernate 实现的 JPA 将生成 1 + n 个查询。这 n 个查询用于延迟加载帐户实体。

我怎样才能使这个查询急切并用一个查询加载所有内容? sql 等价物类似于

SELECT account.*, SUM(t.value) FROM transactions JOIN accounts on transactions.account_id = accounts.id GROUP BY account.id

,一种在 PostgreSQL 上运行良好的语法。根据我的发现,Hibernate 正在生成一个查询来证明延迟加载是合理的。

SELECT account.id, SUM(t.value) FROM transactions JOIN accounts on transactions.account_id = accounts.id GROUP BY account.id

【问题讨论】:

我必须在 hibernate 实现中访问 HQLQueryPlan.java,以防有人想进入兔子洞。它会在执行前清理 HQL。 您是否尝试过使用 @BatchSize 注释 Account 实体本身? (抱歉,我现在没有时间亲自尝试一下,看看是否真的有帮助) 请贴出相关映射。 映射与结果无关。 @ManyToOne 交易 -> 账户 【参考方案1】:

尝试将@ManyToOne 字段标记为惰性:

@ManyToOne(fetch = FetchType.LAZY)
private Account account;

并使用account 字段的JOIN FETCH 更改您的查询,以仅生成一个包含您需要的所有查询,如下所示:

String sql = "SELECT new com.test.Pojo(acc, SUM(t.value)) "
    + "FROM Transaction t JOIN FETCH t.account acc GROUP BY acc";

更新:

对不起,你是对的,@ManyToOne 的 fetch 属性不是必需的,因为在 Hibernate 中这是默认值。 JOIN FETCH 不起作用,它导致 QueryException:“查询指定连接提取,但提取的关联的所有者不存在”。

我尝试了其他一些方法,避免执行 n + 1 查询的最简单方法是从查询中删除 Pojo 对象的创建并处理结果列表,手动创建对象:

String hql = "SELECT acc, SUM(t.value)"
  + " FROM " + Transaction.class.getName() +  " t"
  + " JOIN t.account acc"
  + " GROUP BY acc";

Query query = getEntityManager().createQuery(hql);
List<Pojo> pojoList = new ArrayList<>();
List<Object[]> list = query.getResultList();

for (Object[] result : list)
    pojoList.add(new Pojo((Account)result[0], (BigDecimal)result[1]));

【讨论】:

1.为什么要放 FetchType.LAZY?对于这种情况,这不会以任何方式影响输出; 2. 这种情况下HIbernate无法执行Join Fetch。 我可以确认使用与我开始的查询类似的更简单的查询 String sql = "SELECT t.account, SUM(t.value) FROM Transaction t GROUP BY t.account";将生成一个查询。所以这是一个解决方案。我的问题是:你可以指示 Hibernate 在映射到 pojo 时自动执行此操作吗?它看起来像一个配置/实现细节。【参考方案2】:

PostgreSQL(以及任何其他 SQL 数据库)会阻止您使用上述查询:您必须按帐户表的所有列分组,而不是按 id。这就是 Hibernate 生成查询、按帐户 ID 分组的原因——这就是预期的结果,然后获取其他部分。因为它无法以一般方式预测,还需要加入和分组(!!!),通常这可能会产生情况,当获取具有相同 ID 的多个实体时(只需创建一个适当的查询并获取查看执行计划,当您的Account 实体或Account 实体的任何其他ManyToOne 部分中有OneToMany 字段时,这将特别重要)这就是Hibernate 以这种方式运行的原因。

此外,在一级缓存中拥有具有上述 ID 的帐户,将强制 Hibernate 从中提取它们。或者如果它们是很少被修改的实体,你可以把它们放在二级缓存中,hibernate不会查询数据库,而是从二级缓存中提取它们。

如果您需要在单一提示中从数据库中获取这些信息,但不使用 Hibernate 的所有优点,只需使用基于 Native 查询的纯 JPA 方法,如下所示:

@NamedNativeQuery(
        name = "Pojo.groupedInfo",
        query = "SELECT account.*, SUM(t.value) as sum FROM transactions JOIN accounts on transactions.account_id = accounts.id GROUP BY account.id, account.etc ...",
        resultClass = Pojo.class,
        resultSetMapping = "Pojo.groupedInfo")
@SqlResultSetMapping(
        name = "Pojo.groupedInfo",
        classes = 
            @ConstructorResult(
                targetClass = Pojo.class,
                columns =  
                    @ColumnResult(name = "sum", type = BigDecimal.class),
                    /*
                     * Mappings for Account part of entity.
                     */
                
            )
        
   )
 public class Pojo implements Serializable 
     private BigDecimal sum;
     /* .... */
     public Pojo(BigDecimal sum, ...) 
     /* .... */

确保这对您很有效,除非您将使用此查询在其他实体中获取的帐户。这将使 Hibernate “疯狂” - “实体”,但 Hibernate 不会获取...

【讨论】:

实际上 Psql 允许这种查询,您可以按 id 分组并选择其他字段。但它可能不是标准的 SQL。您仍然可以按所有字段分组并检索所有字段,这在任何 SQL 中都可以。【参考方案3】:

有趣的是,所描述的行为就好像t 实例是从实际查询中返回的,而Pojo 构造函数的第一个参数中的t.account 关联实际上是在编组查询结果时在t 实例上导航的(从查询的结果行创建 Pojo 实例时)。我不确定这是否是构造函数表达式的错误或预期功能。

但是以下形式的查询应该可以工作(在构造函数表达式中没有t.account导航,没有获取关联的所有者也没有join fetch,因为急切地初始化实际上没有返回的东西是没有意义的来自查询):

SELECT new com.test.Pojo(acc, SUM(t.value))
FROM Transaction t JOIN t.account acc
GROUP BY acc

编辑

Ilya Dyoshin 对group by 子句的观察非常好;我在这里完全监督它。要留在 HQL 世界中,您可以简单地在执行分组查询之前为所有帐户预加载事务:

SELECT acc FROM Account acc
WHERE acc.id in (SELECT t.account.id FROM Transaction t)

【讨论】:

没有,显式JOIN没有额外作用。 @danidacar 您是否在Pojo 实例的构造函数中访问传递帐户上的任何内容? 当然,当我调用一个属性时,帐户才被延迟加载,这就是问题

以上是关于减少对包含实体的 JPQL POJO 的查询次数的主要内容,如果未能解决你的问题,请参考以下文章

是否有 JPA / JPQL 查询来搜索春季作为 JSON 传递的实体子集?

输入参数集合包含 null 的 JPQL 查询

JPA JPQL简介

JPA 多对多JPQL查询语句怎么写?

JPQL / Hibernate 查询的动态表名

JPQL 更新查询以更新实体而不使用主键