Vip*_*wal 31 java orm design-patterns hibernate jpa
我知道N + 1问题是执行一个查询以获取N个记录和N个查询以获取某些关系记录.
但是如何在Hibernate中避免它呢?
kar*_*sen 31
假设我们有一个与Contact有多对一关系的类制造商.
我们通过确保初始查询获取在适当初始化状态下加载所需对象所需的所有数据来解决此问题.一种方法是使用HQL提取连接.我们使用HQL
"from Manufacturer manufacturer join fetch manufacturer.contact contact"
Run Code Online (Sandbox Code Playgroud)
使用fetch语句.这导致内部联接:
select MANUFACTURER.id from manufacturer and contact ... from
MANUFACTURER inner join CONTACT on MANUFACTURER.CONTACT_ID=CONTACT.id
Run Code Online (Sandbox Code Playgroud)
使用Criteria查询我们可以得到相同的结果
Criteria criteria = session.createCriteria(Manufacturer.class);
criteria.setFetchMode("contact", FetchMode.EAGER);
Run Code Online (Sandbox Code Playgroud)
这会创建SQL:
select MANUFACTURER.id from MANUFACTURER left outer join CONTACT on
MANUFACTURER.CONTACT_ID=CONTACT.id where 1=1
Run Code Online (Sandbox Code Playgroud)
在这两种情况下,我们的查询返回初始化联系人的制造商对象列表.只需运行一个查询即可返回所需的所有联系人和制造商信息
Vla*_*cea 25
当您忘记获取关联然后需要访问它时,会发生N + 1查询问题.
例如,假设我们有以下JPA查询:
List<PostComment> comments = entityManager.createQuery(
"select pc " +
"from PostComment pc " +
"where pc.review = :review", PostComment.class)
.setParameter("review", review)
.getResultList();
Run Code Online (Sandbox Code Playgroud)
现在,如果我们迭代PostComment实体并遍历post关联:
for(PostComment comment : comments) {
LOGGER.info("The post title is '{}'", comment.getPost().getTitle());
}
Run Code Online (Sandbox Code Playgroud)
Hibernate将生成以下SQL语句:
SELECT pc.id AS id1_1_, pc.post_id AS post_id3_1_, pc.review AS review2_1_
FROM post_comment pc
WHERE pc.review = 'Excellent!'
INFO - Loaded 3 comments
SELECT pc.id AS id1_0_0_, pc.title AS title2_0_0_
FROM post pc
WHERE pc.id = 1
INFO - The post title is 'Post nr. 1'
SELECT pc.id AS id1_0_0_, pc.title AS title2_0_0_
FROM post pc
WHERE pc.id = 2
INFO - The post title is 'Post nr. 2'
SELECT pc.id AS id1_0_0_, pc.title AS title2_0_0_
FROM post pc
WHERE pc.id = 3
INFO - The post title is 'Post nr. 3'
Run Code Online (Sandbox Code Playgroud)
这就是生成N + 1查询问题的方法.
因为post在获取PostComment实体时没有初始化关联,所以Hibernate必须Post使用辅助查询来获取实体,并且对于N个PostComment实体,将执行N个更多查询(因此N + 1查询问题).
解决此问题需要做的第一件事是添加适当的SQL日志记录和监视.如果没有日志记录,您在开发某个功能时就不会注意到N + 1查询问题.
其次,要修复它,你可以只是JOIN FETCH导致这个问题的关系:
List<PostComment> comments = entityManager.createQuery(
"select pc " +
"from PostComment pc " +
"join fetch pc.post p " +
"where pc.review = :review", PostComment.class)
.setParameter("review", review)
.getResultList();
Run Code Online (Sandbox Code Playgroud)
如果需要获取多个子关联,最好在初始查询中获取一个集合,而在第二个SQL查询中获取第二个集合.
这个问题最好被集成测试捕获.您可以使用自动JUnit断言来验证生成的SQL语句的预期计数.在DB-util的项目已经提供了这个功能,并且它是开源的,而且依赖性可Maven的中央.
Rad*_*ler 14
Hibernate中1 + N的本机解决方案称为:
使用批量提取,如果访问一个代理,Hibernate可以加载几个未初始化的代理.批量提取是延迟选择提取策略的优化.我们可以通过两种方式配置批量提取:1)类级别和2)集合级别...
查看这些问答:
使用注释我们可以这样做:
一个class级别:
@Entity
@BatchSize(size=25)
@Table(...
public class MyEntity implements java.io.Serializable {...
Run Code Online (Sandbox Code Playgroud)
一个collection级别:
@OneToMany(fetch = FetchType.LAZY...)
@BatchSize(size=25)
public Set<MyEntity> getMyColl()
Run Code Online (Sandbox Code Playgroud)
延迟加载和批量提取一起表示优化,其中:
oot*_*ero 10
如果您使用Spring Data JPA来实现存储库,则可以在关联中指定延迟获取JPA:
@Entity
@Table(name = "film", schema = "public")
public class Film implements Serializable {
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "language_id", nullable = false)
private Language language;
@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "film")
private Set<FilmActor> filmActors;
...
}
@Entity
@Table(name = "film_actor", schema = "public")
public class FilmActor implements Serializable {
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "film_id", nullable = false, insertable = false, updatable = false)
private Film film;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "actor_id", nullable = false, insertable = false, updatable = false)
private Actor actor;
...
}
@Entity
@Table(name = "actor", schema = "public")
public class Actor implements Serializable {
@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "actor")
private Set<FilmActor> filmActors;
...
}
Run Code Online (Sandbox Code Playgroud)
并添加@EntityGraph到您的Spring Data JPA基于存储库:
@Repository
public interface FilmDao extends JpaRepository<Film, Integer> {
@EntityGraph(
type = EntityGraphType.FETCH,
attributePaths = {
"language",
"filmActors",
"filmActors.actor"
}
)
Page<Film> findAll(Pageable pageable);
...
}
Run Code Online (Sandbox Code Playgroud)
我的博客文章https://tech.asimio.net/2020/11/06/Preventing-N-plus-1-select-problem-using-Spring-Data-JPA-EntityGraph.html可以帮助您防止 N+1使用Spring Data JPA和选择问题@EntityGraph。
您甚至可以让它工作,而无需@BatchSize在任何地方添加注释,只需将属性设置hibernate.default_batch_fetch_size为所需的值即可全局启用批量获取。有关详细信息,请参阅 Hibernate 文档。
当您这样做时,您可能还想更改BatchFetchStyle,因为默认值 ( LEGACY) 很可能不是您想要的。因此,全局启用批量获取的完整配置如下所示:
hibernate.batch_fetch_style=PADDED
hibernate.default_batch_fetch_size=25
Run Code Online (Sandbox Code Playgroud)
另外,我很惊讶所提出的解决方案之一涉及连接获取。连接获取很少是可取的,因为它会导致每个结果行传输更多数据,即使依赖实体已经加载到 L1 或 L2 缓存中也是如此。因此我建议通过设置来完全禁用它
hibernate.max_fetch_depth=0
Run Code Online (Sandbox Code Playgroud)
这是一个常见问题,因此我创建了文章《消除 Spring Hibernate N+1 查询》来详细介绍解决方案
为了帮助您检测应用程序中的所有 N+1 查询并避免添加更多查询,我创建了库spring-hibernate-query-utils来自动检测 Hibernate N+1 查询。
以下代码解释了如何将其添加到您的应用程序中:
<dependency>
<groupId>com.yannbriancon</groupId>
<artifactId>spring-hibernate-query-utils</artifactId>
<version>1.0.3</version>
</dependency>
Run Code Online (Sandbox Code Playgroud)
hibernate.query.interceptor.error-level=EXCEPTION
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
36391 次 |
| 最近记录: |