簡體   English   中英

使用 Spring 數據 JPA EntityGraph with LAZY load mode for NamedAttributeNode field

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

我面臨 2 個問題:N + 1 查詢和 Out Of Memory (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協會將在1個查詢與被裝載Join條款。 啟用sql log查看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 DATA 分頁在數據庫端不起作用。 它將獲取所有數據,然后在內存中進行過濾。 這就是它的工作原理..有一些解決方法,請查看此鏈接:

如何避免警告“用集合獲取指定的 firstResult/maxResults;在內存中應用!” 什么時候使用休眠?

避免“HHH000104:使用集合提取指定的 firstResult/maxResults;在內存中應用!” 使用 Spring 數據

VladMihalcea 博客 修復 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);

}

自定義存儲庫的實現。 它首先獲取沒有實體圖的實體,然后使用實體圖重新獲取它們以加載關聯。 不要忘記重新排序實體以保持順序:

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方法即可獲取具有分頁且沒有N+1 復雜性的記錄。

問題是:為什么hibernate默認沒有這個行為?

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM