Hibernate 为@ManyToOne JPA 注释属性创建 N+1 查询
Hibernate creating N+1 queries for @ManyToOne JPA annotated property
我有这些课程:
@Entity
public class Invoice implements Serializable {
@Id
@Basic(optional = false)
private Integer number;
private BigDecimal value;
//Getters and setters
}
@Entity
public class InvoiceItem implements Serializable {
@EmbeddedId
protected InvoiceItemPK invoiceItemPk;
@ManyToOne
@JoinColumn(name = "invoice_number", insertable = false, updatable = false)
private Invoice invoice;
//Getters and setters
}
当我运行这个查询时:
session.createQuery("select i from InvoiceItem i").list();
它对 select 来自 InvoiceItem 的记录执行一个查询,如果我有 10000 个发票项目,它会生成 10000 个额外的查询 select 来自每个 InvoiceItem 的发票。
我认为如果所有记录都可以在一个 sql 中获取会更好。实际上,我觉得奇怪为什么它不是默认行为。
那么,我该怎么做呢?
在此方法中,触发了多个 SQL。第一个因检索 Parent table 中的所有记录而被触发。其余的被触发以检索每个父记录的记录。第一个查询从数据库中检索 M 条记录,在本例中为 M 条父记录。对于每个 Parent,一个新查询检索 Child。
试试
session.createQuery("select i from InvoiceItem i join fetch i.invoice inv").list();
它应该通过使用联接在单个 SQL 查询中获取所有数据。
是的,有您需要的设置:@BatchSize(size=25)
。在这里查看:
小引:
Using batch fetching, Hibernate can load several uninitialized proxies if one proxy is accessed. Batch fetching is an optimization of the lazy select fetching strategy. There are two ways you can configure batch fetching: on the class level and the collection level.
Batch fetching for classes/entities is easier to understand. Consider the following example: at runtime you have 25 Cat instances loaded in a Session, and each Cat has a reference to its owner, a Person. The Person class is mapped with a proxy, lazy="true". If you now iterate through all cats and call getOwner() on each, Hibernate will, by default, execute 25 SELECT statements to retrieve the proxied owners. You can tune this behavior by specifying a batch-size in the mapping of Person:
<class name="Person" batch-size="10">...</class>
With this batch-size specified, Hibernate will now execute queries on demand when need to access the uninitialized proxy, as above, but the difference is that instead of querying the exactly proxy entity that being accessed, it will query more Person's owner at once, so, when accessing other person's owner, it may already been initialized by this batch fetch with only a few ( much less than 25) queries will be executed.
因此,我们可以在两者上使用该注释:
- collections/sets
- classes/Entities
也在这里检查:
- @BatchSize but many round trip in @ManyToOne case
这里的问题与Hibernate无关,而是与JPA有关。
在 JPA 1.0 之前,Hibernate 3 对所有关联使用延迟加载。
但是,JPA 1.0 规范仅将 FetchType.LAZY
用于集合关联:
@ManyToOne
and @OneToOne
关联默认使用 FetchType.EAGER
,从性能的角度来看这是非常糟糕的。
此处描述的行为称为 [N+1 查询问题][5],这是因为 Hibernate 需要确保 @ManyToOne
关联在将结果返回给用户之前已初始化.
现在,如果您通过 entityManager.find
使用直接提取,Hibernate 可以使用 LEFT JOIN 来初始化 FetchTYpe.EAGER
关联。
但是,当执行未显式使用 JOIN FETCH 子句的查询时,Hibernate 将不会使用 JOIN 来获取 FetchTYpe.EAGER
关联,因为它无法更改您已经指定的查询方式建。所以,它只能使用辅助查询。
修复很简单。只需对所有关联使用 FetchType.LAZY
:
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "invoice_number", insertable = false, updatable = false)
private Invoice invoice;
此外,您应该使用db-util project断言JPA和Hibernate执行的语句数。
我有这些课程:
@Entity
public class Invoice implements Serializable {
@Id
@Basic(optional = false)
private Integer number;
private BigDecimal value;
//Getters and setters
}
@Entity
public class InvoiceItem implements Serializable {
@EmbeddedId
protected InvoiceItemPK invoiceItemPk;
@ManyToOne
@JoinColumn(name = "invoice_number", insertable = false, updatable = false)
private Invoice invoice;
//Getters and setters
}
当我运行这个查询时:
session.createQuery("select i from InvoiceItem i").list();
它对 select 来自 InvoiceItem 的记录执行一个查询,如果我有 10000 个发票项目,它会生成 10000 个额外的查询 select 来自每个 InvoiceItem 的发票。
我认为如果所有记录都可以在一个 sql 中获取会更好。实际上,我觉得奇怪为什么它不是默认行为。
那么,我该怎么做呢?
在此方法中,触发了多个 SQL。第一个因检索 Parent table 中的所有记录而被触发。其余的被触发以检索每个父记录的记录。第一个查询从数据库中检索 M 条记录,在本例中为 M 条父记录。对于每个 Parent,一个新查询检索 Child。
试试
session.createQuery("select i from InvoiceItem i join fetch i.invoice inv").list();
它应该通过使用联接在单个 SQL 查询中获取所有数据。
是的,有您需要的设置:@BatchSize(size=25)
。在这里查看:
小引:
Using batch fetching, Hibernate can load several uninitialized proxies if one proxy is accessed. Batch fetching is an optimization of the lazy select fetching strategy. There are two ways you can configure batch fetching: on the class level and the collection level.
Batch fetching for classes/entities is easier to understand. Consider the following example: at runtime you have 25 Cat instances loaded in a Session, and each Cat has a reference to its owner, a Person. The Person class is mapped with a proxy, lazy="true". If you now iterate through all cats and call getOwner() on each, Hibernate will, by default, execute 25 SELECT statements to retrieve the proxied owners. You can tune this behavior by specifying a batch-size in the mapping of Person:
<class name="Person" batch-size="10">...</class>
With this batch-size specified, Hibernate will now execute queries on demand when need to access the uninitialized proxy, as above, but the difference is that instead of querying the exactly proxy entity that being accessed, it will query more Person's owner at once, so, when accessing other person's owner, it may already been initialized by this batch fetch with only a few ( much less than 25) queries will be executed.
因此,我们可以在两者上使用该注释:
- collections/sets
- classes/Entities
也在这里检查:
- @BatchSize but many round trip in @ManyToOne case
这里的问题与Hibernate无关,而是与JPA有关。
在 JPA 1.0 之前,Hibernate 3 对所有关联使用延迟加载。
但是,JPA 1.0 规范仅将 FetchType.LAZY
用于集合关联:
@ManyToOne
and @OneToOne
关联默认使用 FetchType.EAGER
,从性能的角度来看这是非常糟糕的。
此处描述的行为称为 [N+1 查询问题][5],这是因为 Hibernate 需要确保 @ManyToOne
关联在将结果返回给用户之前已初始化.
现在,如果您通过 entityManager.find
使用直接提取,Hibernate 可以使用 LEFT JOIN 来初始化 FetchTYpe.EAGER
关联。
但是,当执行未显式使用 JOIN FETCH 子句的查询时,Hibernate 将不会使用 JOIN 来获取 FetchTYpe.EAGER
关联,因为它无法更改您已经指定的查询方式建。所以,它只能使用辅助查询。
修复很简单。只需对所有关联使用 FetchType.LAZY
:
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "invoice_number", insertable = false, updatable = false)
private Invoice invoice;
此外,您应该使用db-util project断言JPA和Hibernate执行的语句数。