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

时间:2016-10-06 07:05:41

标签: postgresql hibernate jpa hql jpql

实体关系:交易(@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

3 个答案:

答案 0 :(得分:4)

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

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

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

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:&#34;查询指定的联接提取,但获取的关联的所有者不存在&#34;。< / p>

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

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 :(得分:2)

PostgreSQL(以及任何其他SQL数据库)也会阻止您使用上述查询:您必须按帐户表的所有列进行分组,而不是按ID进行分组。这就是为什么Hibernate生成查询,按帐户ID分组 - 这就是目的,然后获取其他部分。因为它无法以一般方式进行预测,所以还需要加入和分组的其他内容(!!!),并且通常这会产生情况,当获取具有相同ID的多个实体时(只需创建正确的查询并采取查看执行计划,当您的OneToMany实体中有Account个字段或ManyToOne实体的任何其他Account部分时,这将是特别重要的,这就是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&#34;疯狂&#34; - &#34;实体&#34;,但不是由Hibernate提取......

答案 2 :(得分:1)

有趣的是,所描述的行为就像从实际查询中返回public void defineMainPlayer1() { BodyDef bDef = new BodyDef(); bDef.position.set(128 / Constants.PPM, 256 / Constants.PPM); bDef.type = BodyDef.BodyType.DynamicBody; b2body = world.createBody(bDef); PolygonShape shape = new PolygonShape(); shape.setAsBox(42 / 2 / Constants.PPM, 94 / 2 / Constants.PPM); FixtureDef fDef = new FixtureDef(); fDef.shape = shape; fDef.filter.categoryBits = Constants.PLAYER1_BIT; // Setting the filter // for my Player fDef.filter.maskBits = Constants.BRICK_BIT; b2body.createFixture(fDef).setUserData(this); EdgeShape head = new EdgeShape(); head.set(new Vector2(-30 / Constants.PPM, 49 / Constants.PPM), new Vector2(30 / Constants.PPM, 49 / Constants.PPM)); fDef.shape = head; fDef.isSensor = true; b2body.createFixture(fDef).setUserData("head"); } 个实例一样,t构造函数的第一个参数中的t.account关联实际上是在Pojo上导航的编组查询结果时的实例(从查询的结果行创建t实例时)。我不确定这是否是构造函数表达式的错误或预期功能。

但是以下形式的查询应该有效(构造函数表达式中没有Pojo导航,没有t.account没有获取关联的所有者,因为急切地初始化某些东西是没有意义的实际上并没有从查询中返回):

join fetch

修改

Ilya Dyoshin关于SELECT new com.test.Pojo(acc, SUM(t.value)) FROM Transaction t JOIN t.account acc GROUP BY acc 条款的非常好的观察;我完全在这里监督它。为了保持在HQL世界中,您可以在使用分组执行查询之前简单地预加载所有具有事务的帐户:

group by