简体   繁体   English

使用 Spring 数据 JPA EntityGraph with LAZY load mode for NamedAttributeNode field

[英]Using Spring data JPA EntityGraph with LAZY load mode for NamedAttributeNode field

I am facing with 2 problems: N + 1 query and Out Of Memory (OOM).我面临 2 个问题:N + 1 查询和 Out Of Memory (OOM)。

I solved OOM by paging and lazy loading:我通过分页和懒加载解决了OOM:

@OneToMany(fetch = FetchType.LAZY)
@JoinColumn(name = "department_id")
private Set<Employee> employees;

But when I use lazy loading, N + 1 query happened.但是当我使用延迟加载时,发生了 N + 1 个查询。 So I try to use EntityGraph as https://www.baeldung.com/spring-data-jpa-named-entity-graphs .所以我尝试使用EntityGraph作为https://www.baeldung.com/spring-data-jpa-named-entity-graphs But as my researches and local test, EntityGraph always do eager loading for NamedAttributeNode field - association field, which I want to be lazy loading - do not load all data at first:但是作为我的研究和本地测试, EntityGraph总是对NamedAttributeNode字段 - 关联字段进行预加载,我想延迟加载 - 首先不要加载所有数据:

@Entity
@Table(name = "department")
@NamedEntityGraph(name = "Department",
        attributeNodes = {
                @NamedAttributeNode("employees")
        }
)
public class Department implements Serializable {
    @OneToMany(fetch = FetchType.LAZY)
    @JoinColumn(name = "department_id")
    private Set<Employee> employees;
}

So are there any way to get them both?那么有没有什么办法可以同时得到它们呢? Use EntityGraph to avoid N + 1 and lazy loading to avoid OOM?使用EntityGraph避免N+1和懒加载避免OOM?

UPDATE: Can EntityGraph works fine with Pageable effectively?更新: EntityGraph能否有效地与 Pageable 配合使用? I mean do not load all data in JOIN query.我的意思是不要在 JOIN 查询中加载所有数据。

Using EntityGraph all your NamedAttributeNode associations will be loaded in 1 query with Join clause.使用EntityGraph所有NamedAttributeNode协会将在1个查询与被装载Join条款。 Enable sql log to see how many queries hibernate does for loading entities in different scenarios启用sql log查看hibernate在不同场景下为加载实体做了多少查询

logging.level.org.hibernate.SQL=DEBUG

You will see that using @OneToMany(fetch = FetchType.EAGER) without EntityGraph it loads employees in separate select queries (N + 1), but using EntityGraph it performs only 1 select ... join您将看到使用@OneToMany(fetch = FetchType.EAGER)没有EntityGraph它加载在不同的员工select查询(N + 1),但使用EntityGraph它仅执行1 select ... join

Also don't forget to specify entity graph name in repository like:另外不要忘记在存储库中指定实体图名称,例如:

@EntityGraph(value = "Department")
List<Department> findAll();

UPDATE: Spring DATA Pagination doesn't work on database side.更新: Spring DATA 分页在数据库端不起作用。 It will fetch all data and then filtered in memory.它将获取所有数据,然后在内存中进行过滤。 That's how it works.. There are some workarounds, check this links:这就是它的工作原理..有一些解决方法,请查看此链接:

How can I avoid the Warning "firstResult/maxResults specified with collection fetch; applying in memory!" 如何避免警告“用集合获取指定的 firstResult/maxResults;在内存中应用!” when using Hibernate? 什么时候使用休眠?

Avoiding "HHH000104: firstResult/maxResults specified with collection fetch; applying in memory!" 避免“HHH000104:使用集合提取指定的 firstResult/maxResults;在内存中应用!” using Spring Data 使用 Spring 数据

VladMihalcea Blog The best way to fix the Hibernate HHH000104 VladMihalcea 博客 修复 Hibernate HHH000104 的最佳方法

As for me the solution could be creating custom repository and using EntityManager to construct query manually.至于我,解决方案可能是创建自定义存储库并使用EntityManager手动构建查询。

A simple solution is:一个简单的解决方案是:

  • Fetch first your records with pagination but without @EntityGraph .首先使用分页但不@EntityGraph获取您的记录。
  • Fetch your records with @EntityGraph from previous returned ids.使用@EntityGraph从之前返回的 ID 中获取您的记录。

=> No N+1 complexity, only 2 SQL requests and no native queries, etc. => 没有 N+1 复杂性,只有 2 个 SQL 请求,没有本地查询等。

Example:例子:

On your main repository, add @EntityGraph annotation with associations you want to fetch:在您的主存储库上,添加带有您要获取的关联的@EntityGraph注释:

public interface CarRepository extends JpaRepository<Car, String>, JpaSpecificationExecutor<Car>, CarCustomRepository {

    @Override
    @EntityGraph(attributePaths = { "foo", "bar", "bar.baz" })
    List<Car> findAllById(Iterable<String> ids);

}

Create a custom repository with a findAllWithEntityGraph method:使用findAllWithEntityGraph方法创建自定义存储库:

public interface CarCustomRepository {

    Page<Car> findAllWithEntityGraph(Specification<Car> specification, Pageable pageable);

}

The implementation of the custom repository.自定义存储库的实现。 It first fetch entities without entity graph, then it re-fetch them with entity graph in order to load associations.它首先获取没有实体图的实体,然后使用实体图重新获取它们以加载关联。 Don't forget to re-sort entities in to preserve order:不要忘记重新排序实体以保持顺序:

public class CarCustomRepositoryImpl implements CarCustomRepository {

    @Autowired
    @Lazy
    private CarRepository carRepository;

    @Override
    public Page<Car> findAllWithEntityGraph(Specification<Car> specification, Pageable pageable) {
        Page<Car> page = carRepository.findAll(specification, pageable);
        List<String> ids = page.getContent().stream().map(Car::getId).collect(Collectors.toList());
        List<Car> cars = carRepository.findAllById(ids).stream()
                .sorted(Comparator.comparing(car -> ids.indexOf(car.getId())))
                .collect(Collectors.toList());
        return new PageImpl<>(cars, pageable, page.getTotalElements());
    }

}

Then, you just have to invoke CarRepository#findAllWithEntityGraph method in order to fetch records with pagination and no N+1 complexity.然后,您只需要调用CarRepository#findAllWithEntityGraph方法即可获取具有分页且没有N+1 复杂性的记录。

The question is: why hibernate does not have this behavior by default?问题是:为什么hibernate默认没有这个行为?

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM