【问题标题】:How to keep JPA Fetch child data when using CompletableFuture?使用 CompletableFuture 时如何保留 JPA Fetch 子数据?
【发布时间】:2021-04-28 14:57:52
【问题描述】:

您好,我尝试对执行具有不同条件的查询的函数进行多线程处理,但我认为 JPA 在我执行此操作时丢失了它的持久性包,因为数据不会显示并且出现错误。我将首先从我的同步代码开始,因为它工作,我将展示我的异步代码不工作。

这是我的实体类:

TransSalesOrder 类

@OneToMany(mappedBy = "transSalesOrder")
private List<TransDeliveryOrder> transDeliveryOrder;

TransDeliveryOrder 类

@OneToMany(mappedBy = "deliveryOrder", orphanRemoval = true, cascade = CascadeType.ALL)
private List<TransDeliveryOrderDetail> transDeliveryOrderDetail;

@JsonIgnore
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "transSalesOrderId")
private TransSalesOrder transSalesOrder;

这里是 TransDeliveryOrderDetail 类:

@JsonIgnoreProperties({ "hibernateLazyInitializer", "handler" })
@JsonIgnore
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "delivery_order_id", referencedColumnName = "id")
private TransDeliveryOrder deliveryOrder;

这是我的同步工作方式: 这是我的功能,执行其他 3 个功能:

public List<TransSalesOrderOnlyResponseDto> findUnAssignedSO() {
    List<TransSalesOrderOnlyResponseDto> transSalesOrdersResponseNew = findSOHaveItemLeftOverOnly();
    transSalesOrdersResponseNew.addAll(findPureUnAssignedSO());
    transSalesOrdersResponseNew.addAll(findSalesOrderWithBpsjInDeliveryOrder());

    return transSalesOrdersResponseNew;
} 

这是执行的 3 个函数:

private List<TransSalesOrderOnlyResponseDto> findSOHaveItemLeftOverOnly() {
    List<TransSalesOrder> transSalesOrders = iTransSalesOrderQdslRepository.findSOHaveLeftOverButDone();
    return buildTransSalesOrdersResponseNew(transSalesOrders);
}

private List<TransSalesOrderOnlyResponseDto> findPureUnAssignedSO() {
    return iSalesOrderMapper.entityToSOOnlyDto(iTransSalesOrderQdslRepository.findUnAssignedSalesOrder());
}

private List<TransSalesOrderOnlyResponseDto> findSalesOrderWithBpsjInDeliveryOrder() {
    List<TransSalesOrder> transSalesOrders = iTransSalesOrderQdslRepository.findSalesOrderWithBpsjInDeliveryOrder();

    return buildTransSalesOrdersBpsjOnlyResponseNew(transSalesOrders);
}

这种方式是可行的,但后来我尝试异步我的 3 个方法,因为我的 3 个函数可以以异步方式执行,它们不必互相等待完成

这是我使用 CompletableFuture 的异步方式:

public List<TransSalesOrderOnlyResponseDto> findUnAssignedSO() {
    List<TransSalesOrderOnlyResponseDto> transSalesOrdersResponseNew = new ArrayList<>();

    findSOHaveItemLeftOverOnly()
            .thenAccept( transSalesOrdersResponseNew::addAll )
            .thenCompose( v -> findPureUnAssignedSO() )
            .thenAccept( transSalesOrdersResponseNew::addAll )
            .thenCompose( v -> findSalesOrderWithBpsjInDeliveryOrder() )
            .thenAccept( transSalesOrdersResponseNew::addAll )
            .join();

    return transSalesOrdersResponseNew;
}

private CompletableFuture<List<TransSalesOrderOnlyResponseDto>> findSOHaveItemLeftOverOnly() {
    return CompletableFuture.supplyAsync(() -> {
        List<TransSalesOrder> transSalesOrders = iTransSalesOrderQdslRepository.findSOHaveLeftOverButDone();
        return buildTransSalesOrdersResponseNew(transSalesOrders);
    });
}

private CompletableFuture<List<TransSalesOrderOnlyResponseDto>> findPureUnAssignedSO() {
    return CompletableFuture.supplyAsync(() -> iSalesOrderMapper.entityToSOOnlyDto(iTransSalesOrderQdslRepository.findUnAssignedSalesOrder()));
}

private CompletableFuture<List<TransSalesOrderOnlyResponseDto>> findSalesOrderWithBpsjInDeliveryOrder() {
    return CompletableFuture.supplyAsync(() -> {
        List<TransSalesOrder> transSalesOrders = iTransSalesOrderQdslRepository.findSalesOrderWithBpsjInDeliveryOrder();

        return buildTransSalesOrdersBpsjOnlyResponseNew(transSalesOrders);
    });
}

当我运行我的 findUnAssignedSO 函数时,我得到了这个错误:

org.hibernate.LazyInitializationException: 延迟初始化失败 角色集合: com.bit.microservices.b2b.warehouse.entity.TransSalesOrder.transSalesOrderDetail, 无法初始化代理 - 没有会话

当我调试它时,我发现子数据没有加载,这是我尝试的选项:

  • 我尝试使用 @Transactional 保持 JPA 附加,但它不起作用。
  • 我不想更改与 EAGER 相关的 @OneToMany 中的 fetchType,我在互联网上阅读了以后会出现性能问题的所有内容
  • 使用 Set insted of List,这也会导致性能问题

我没有显示我的存储库,因为我使用了混合的 JpaRepository 和 QueryDSL,但我对任何不需要附加到 QueryDSL 的解决方案持开放态度。如何正确获取数据?

【问题讨论】:

  • 您应该检查所有内容是否在同一个线程中运行。会话不是线程安全的,并且在被不同线程使用时可能会出现奇怪的行为。另外,为什么要使用CompletableFuture?在这种特殊情况下,我认为您不会从中获得任何好处。
  • 错误究竟来自哪里?当您访问惰性(未获取)关系时,您已经知道对象与您正在读取它们的上下文分离,因为您已经关闭了用于读取它们的异步线程/上下文,所以我不太清楚您是什么寻找。您必须在上下文关闭之前预取所需的数据,或者在使用数据的进程中同步运行提取,以便上下文可以保持打开状态。至于“急切”地获取 1:M 关系的效率——这是许多解决方案的一个单独问题。
  • 请参阅baeldung.com/hibernate-fetchmode 以修复您的集合映射,以便您可以让它们保持惰性(或急切)并且在获取时不让它加入主表 - 您目前已经导致 N* 查询作为您的方法遍历返回的数据并触及惰性关系。如果您想让模型关系保持惰性,只需让您的异步方法在返回之前触摸它们,或者使用图形/提示强制 JPA 在上下文关闭并返回数据之前为您获取它们。关键是在需要之前知道自己需要什么。
  • @Davide 哦,我想在单独的线程中执行 3 个数据库查询,所以他们不必互相等待完成,因为每个查询都会得到数千条记录,我想改进使用 CompletableFuture 的性能,有没有更好的方法来做到这一点?
  • @Chris 正如我所提到的,我不想更改 fetchType,因为 fetchMode 会覆盖 fetchType,它会影响我的其他功能性能

标签: java hibernate jpa spring-data-jpa completable-future


【解决方案1】:

获取性能是一个困难的话题,我完全理解您不想在实体级别更改获取类型或模式。通常,这可以通过使用实体图来定义关联图以获取每个查询的关联图来解决。在您的特定情况下,问题在于线程不会获取以后需要的所有状态。当您稍后在不同的线程中访问该状态时,代理不再引用持久性上下文(因为它绑定到事务/线程)并且您看到的 LazyInitializationException 失败。您可以通过使用实体图来解决这个问题,这样就不会发生延迟加载,或者启用 hibernate.enable_lazy_load_no_trans 属性,该属性将按需创建会话以获取数据,但我认为这是 Blaze-Persistence Entity Views 的完美用例.

我创建了该库以允许在 JPA 模型和自定义接口或抽象类定义模型之间轻松映射,例如 Spring Data Projections on steroids。这个想法是您按照自己喜欢的方式定义目标结构(域模型),并通过 JPQL 表达式将属性(getter)映射到实体模型。

使用 Blaze-Persistence Entity-Views 的用例的 DTO 模型可能如下所示:

@EntityView(TransSalesOrder.class)
public interface TransSalesOrderOnlyResponseDto {
    @IdMapping
    Long getId();
    String getName();
    List<TransDeliveryOrderDto> transDeliveryOrder;

    @EntityView(TransDeliveryOrder.class)
    interface TransDeliveryOrderDto {
        @IdMapping
        Long getId();
        String getName();
    }
}

查询是将实体视图应用于查询的问题,最简单的就是通过 id 进行查询。

TransSalesOrderOnlyResponseDto a = entityViewManager.find(entityManager, TransSalesOrderOnlyResponseDto.class, id);

Spring Data 集成让您可以像使用 Spring Data Projections 一样使用它:https://persistence.blazebit.com/documentation/entity-view/manual/en_US/index.html#spring-data-features

Page<TransSalesOrderOnlyResponseDto> findAll(Pageable pageable);

最好的部分是,它只会获取实际需要的状态!

【讨论】:

    猜你喜欢
    • 2018-11-12
    • 2012-03-07
    • 2019-11-16
    • 2020-04-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-11-09
    相关资源
    最近更新 更多