将 Spring 数据 JPA EntityGraph 与 NamedAttributeNode 字段的延迟加载模式一起使用

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

我面临 2 个问题:N + 1 查询和内存不足 (OOM)。

我通过分页和懒加载解决了OOM:

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

但是当我使用延迟加载时,发生了N + 1次查询。所以我尝试将 EntityGraph 用作 https://www.baeldung.com/spring-data-jpa-named-entity-graphs。但是作为我的研究和本地测试,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;
}

那么有什么方法可以同时获得它们吗?使用 EntityGraph 来避免 N + 1 和延迟加载来避免 OOM?

更新: EntityGraph 可以有效地与 Pageable 配合使用吗?我的意思是不要在 JOIN 查询中加载所有数据。

使用 EntityGraph,您所有的 NamedAttributeNode 关联都将加载到带有 Join 子句的 1 个查询中。启用sql日志查看hibernate在不同场景下加载实体的查询次数

logging.level.org.hibernate.SQL=DEBUG

您会看到使用 @OneToMany(fetch = FetchType.EAGER) 而不使用 EntityGraph 它会在单独的 select 查询 (N + 1) 中加载员工,但使用 EntityGraph 它只执行 1 select ... join

也不要忘记在存储库中指定实体图名称,例如:

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

更新: Spring 数据分页在数据库端不起作用。它将获取所有数据,然后在内存中进行过滤。这就是它的工作原理。有一些解决方法,请查看此链接:

How can I avoid the Warning "firstResult/maxResults specified with collection fetch; applying in memory!" when using Hibernate?

Avoiding "HHH000104: firstResult/maxResults specified with collection fetch; applying in memory!" using Spring Data

VladMihalcea Blog The best way to fix the Hibernate HHH000104

对我来说,解决方案可能是创建自定义存储库并使用 EntityManager 手动构建查询。

一个简单的解决方案是:

  • 首先获取有分页但没有 @EntityGraph 的记录。
  • 使用 @EntityGraph 从之前返回的 ID 中获取您的记录。

=> 没有 N+1 复杂性,只有 2 SQL 个请求,没有本地查询等

示例:

在您的主存储库中,添加 @EntityGraph 带有您要获取的关联的注释:

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

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

}

使用 findAllWithEntityGraph 方法创建自定义存储库:

public interface CarCustomRepository {

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

}

自定义存储库的实现。它首先获取没有实体图的实体,然后 re-fetch 它们带有实体图以加载关联。不要忘记 re-sort 个实体以保持顺序:

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());
    }

}

然后,您只需调用 CarRepository#findAllWithEntityGraph 方法来获取 with 分页和 no N+1 的记录复杂度。

问题是:为什么hibernate默认没有这种行为?