在 JPA/hibernate 中选择新建(JPQL 构造函数表达式)会导致每行"lazy"加载



最近我发现在jpa/hibernate中使用'select new/constructor expression'时没有明显的行为。它对结果集中每行中的每个实体使用延迟加载,这是不高效的。

测试例子
@Value
public class PojoTuple {
Entity1 e1;
Entity2 e2;
}
@Entity
@Table(name = "entity1", schema = DEFAULT_DATABASE_SCHEMA)
@NoArgsConstructor(access = PROTECTED)
public class Entity1 {
@Id
@Column(name = "id", nullable = false)
private String id;
@Column(name = "field1", nullable = false)
private String field1;
}
@Entity
@Table(name = "entity2", schema = DEFAULT_DATABASE_SCHEMA)
@NoArgsConstructor(access = PROTECTED)
public class Entity2 {
@Id
@Column(name = "id", nullable = false)
private String id;
@Column(name = "fkentity1", nullable = false)
private String entity1Id;
@Column(name = "field2", nullable = false)
private String field2;
}
create table entity1
(
id     varchar2(22 char) not null primary key,
field1 varchar2(50 char) not null
);
create table entity2
(
id        varchar2(22 char) not null primary key,
fkentity1 varchar2(22 char) not null,
field2    varchar2(50 char) not null
);
insert into entity1 (id, field1) values ('10', 'anyvalue1');
insert into entity1 (id, field1) values ('11', 'anyvalue2');
insert into entity2 (id, fkentity1, field2) VALUES ('20', '10', 'anyvalue3');
insert into entity2 (id, fkentity1, field2) VALUES ('21', '11', 'anyvalue4');

第一例使用select new技术发出查询:

Query query = entityManager.createQuery("select new my.package.PojoTuple(e1, e2) " +
"from Entity1 e1 " +
"join Entity2 e2 on e1.id=e2.entity1Id ");
query.getResultList();

发出一个查询,只获取id查询e1和e2,然后对结果集中的每一行查询e1, e2:

查询:("选择entity1x0_。Id为col_0_0_, entity2x1_。Id为col_1_0_从模式。Entity1 entity1x0_内部连接模式。Entity2 entity2x1_ on(entity1x0_.id = entity2x1_.fkentity1)")

(">

查询:选择entity1x0_。Id为id1_1_0_, entity1x0_。field1作为Field2_1_0_ from schema。entity1x0_ where entity1x0_.id=?"]参数((10)):

(">

查询:选择entity2x0_。Id为id1_2_0_, entity2x0_。fkentity1作为fkentity2_2_0_ entity2x0_。字段2作为字段3_2_0_从schema.entity2entity2x0_ where entity2x0_.id=?"] Params:[(20)]

(">

查询:选择entity1x0_。Id为id1_1_0_, entity1x0_。field1作为Field2_1_0_ from schema。entity1x0_ where entity1x0_.id=?"]参数((11)):

(">

查询:选择entity2x0_。Id为id1_2_0_, entity2x0_。fkentity1作为fkentity2_2_0_ entity2x0_。字段2作为字段3_2_0_从schema.entity2entity2x0_ where entity2x0_.id=?"] Params:[(21)]

第二种情况而将上面的样本重写为:

Query query = entityManager.createQuery("select e1, e2 " +
"from Entity1 e1 " +
"join Entity2 e2 on e1.id=e2.entity1Id ");
query.getResultList();

只向数据库发出一个查询,并选择所有必需的字段:

查询:("选择entity1x0_。Id为id1_1_0_, entity2x1_。我是id1_2_1_,entity1x0_。Field1作为field2_1_0_, entity2x1_。fkentity1作为fkentity2_2_1_ entity2x1_。Field2作为field3_2_1_从schema.entity1Entity1x0_内部连接模式。Entity2 entity2x1_ on(entity1x0_.id = entity2x1_.fkentity1)")参数(()):

从我的角度来看,这两个查询应该如何执行没有太大的区别。第一个案例发出了许多查询,我不认为这是非常低效的。第二种情况与预期的一样,只向数据库发出一个查询。这是一个bug,次优解决方案还是一些我看不到的隐藏功能?

环境hibernate core: 5.6.9.Final

所以我终于从我所知道的关于hibernate的最权威的知识来源中找到了部分解释- Vlad Mihalcea:段落:在DTO投影中返回实体

然而,当您想要在DTO投影中选择一个实体时,可能会有这样的用例。(…)

当您执行如下的JPQL查询时:

List<PersonAndCountryDTO> personAndAddressDTOs = entityManager.createQuery(
"select new " +
"   com.vladmihalcea.book.hpjp.hibernate.query.dto.PersonAndCountryDTO(" +
"       p, " +
"       c.name" +
"   ) " +
"from Person p " +
"join Country c on p.locale = c.locale " +
"order by p.id", PersonAndCountryDTO.class) .getResultList();

Hibernate生成以下SQL查询:

SELECT p.id AS col_0_0_,
c.name AS col_1_0_ FROM   Person p INNER JOIN
Country c ON
( p.locale = c.locale ) ORDER BY
p.id   
SELECT p.id AS id1_1_0_,
p.locale AS locale2_1_0_,
p.name AS name3_1_0_ FROM   Person p WHERE  p.id = 3   
SELECT p.id AS id1_1_0_,
p.locale AS locale2_1_0_,
p.name AS name3_1_0_ FROM   Person p WHERE  p.id = 4

Hibernate 5.2的DTO投影实现不能从ResultSet实现DTO投影,而不执行二次查询。然而,这对性能非常不利,因为它可以导致N+1查询问题。

这个HQL限制已经讨论过了,Hibernate 6.0新的SQM解析器可能会解决这个问题,所以请继续关注!

总结一下:

  1. 我问的行为是已知的hibernate开发人员,有一个希望它将被修复。
  2. 到目前为止,我们必须知道,使用构造函数表达式提取完整的托管实体作为一种设计是完全可以的,但是对于hibernate 5。由于hibernate
  3. 发出的许多查询,x可能导致非最佳解决方案。

使用构造函数表达式创建的对象不应该返回实体。这不是这个特性的目的,这也是为什么它用许多查询加载数据的原因。

https://docs.jboss.org/hibernate/orm/current/userguide/html_single/Hibernate_User_Guide.html hql-select-new