减少对包含实体的 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 的查询次数的主要内容,如果未能解决你的问题,请参考以下文章