hibernate - 如何避免 1 + n 数据库调用在 Hibernate 中进行双向可选的一对一关联?

标签 hibernate jpa spring-data-jpa

具有以下简化实体:

@MappedSuperclass
public abstract class AbstractEntity implements Serializable {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    protected Long id;
}

@Entity
@Table(name = "t_invoice")
public class Invoice extends AbstractEntity {
    @OneToOne(optional = false, fetch = FetchType.EAGER)
    @JoinColumn(name = "order_id")
    @Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
    private Order order;
}

@Entity
@Table(name = "t_order")
public class Order extends AbstractEntity {
    @OneToMany(mappedBy = "order", fetch = FetchType.LAZY, cascade = CascadeType.ALL, orphanRemoval = true)
    @Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
    @SortNatural
    private SortedSet<OrderLine> orderLines = new TreeSet<>();

    @OneToOne(optional = true, mappedBy = "order", fetch = FetchType.EAGER)
    @Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
    private Invoice invoice;
}

并使用 Spring 数据跟踪存储库
public interface InvoiceRepository extends JpaRepository<Invoice, Long> {
    List<Invoice> findDistinctByInvoiceDateBetween(LocalDate from, LocalDate until);
}

使用存储库方法获取发票时,将执行 1 + n 个 SQL 语句,如日志中所示:
SELECT DISTINCT i.id, ... FROM t_invoice i WHERE i.invoice_date BETWEEN ? AND ?;
SELECT i.id, ... FROM t_invoice i WHERE i.order_id = ?;
SELECT i.id, ... FROM t_invoice i WHERE i.order_id = ?;
... n

来自 this所以回答我明白,当有一对一的可选关联时,Hibernate 需要进行 n 次数据库调用以确定订单中的可选发票是否为空。
让我感到困惑的是,Hibernate 已经在初始查询中获取了有问题的发票,那么为什么它不使用已经获取的发票中的数据呢?

我还尝试通过使用 @NamedEntityGraph 和 @NamedSubgraph 按顺序热切地填充发票来避免 n 次调用。

因此现在发票实体看起来像:
@Entity
@NamedEntityGraph(
        name = Invoice.INVOICE_GRAPH,
        attributeNodes = {
                @NamedAttributeNode(value = "order", subgraph = "order.subgraph")
        },
        subgraphs = {
                @NamedSubgraph(name = "order.subgraph", attributeNodes = {
                        @NamedAttributeNode("invoice"),
                        @NamedAttributeNode("orderLines")
                }),
        }
)
@Table(name = "t_invoice")
public class Invoice extends AbstractEntity {
@OneToOne(optional = false, fetch = FetchType.EAGER)
    @JoinColumn(name = "order_id")
    @Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
    private Order order;
}

存储库中的方法如下所示:
@EntityGraph(value = Invoice.INVOICE_GRAPH, type = EntityGraph.EntityGraphType.LOAD)
List<Invoice> findDistinctByInvoiceDateBetween(LocalDate from, LocalDate until);

但是,即使第一个 sql select 子句包含两次发票数据,它仍然会调用 n 次数据库,如您所见:
SELECT DISTINCT
    invoice0_.id                      AS id1_13_0_,
    order1_.id                        AS id1_14_2_,
    orderlines4_.id                   AS id1_15_4_,
    invoice5_.id                      AS id1_13_5_,
    invoice0_.created                 AS created2_13_0_,
    order1_.created                   AS created2_14_2_,
    orderlines4_.created              AS created2_15_4_,
    invoice5_.created                 AS created2_13_5_,
FROM t_invoice invoice0_ ... more join clausules ... 
WHERE invoice0_.order_id = order1_.id AND (invoice0_.invoice_date BETWEEN ? AND ?)

所以现在我想知道您将如何避免按顺序填充发票的 n 个额外调用?

最佳答案

I understand that when having a one to one optional association, Hibernate needs to make the n database calls to determine if the optional invoice in order is null or not



是的。更准确地说,hibernate 不支持可选 ToOne 关联的惰性,因此它将始终加载关联数据。

What confuses me is that Hibernate already has the invoice in question fetched in the initial query, so why would it not use the data from invoice already fetched?



Hibernate 没有意识到它已经加载了该发票。为此,它要么必须通过 order_id 保留 Invoice 对象的映射,要么对相互的 OneToOne 关联进行特殊处理。 OneToOne 关联很少见,它没有这样的处理。

这可以通过以下任何一种方法来解决:
  • 仅映射关联的一端,并使用查询在另一个方向导航(即,当您需要订单发票时,请执行“从发票中选择发票,其中 invoice.order = ?”)
  • 映射关联的两端,但将可选端作为主要映射,即将外键从订单移动到发票。这样,订单的发票由其主键引用, hibernate 将足够智能以首先在持久性上下文中查找,并且发票的订单是懒惰的,因此 hibernate 将仅在以下情况下发出冗余查询属性被实际访问。

  • 对于您的问题,哪些是更好的解决方案取决于对该数据进行操作的其他查询。一般来说,我更喜欢第一个选项,因为对于程序员来说,查询未映射的数据比使用神秘的技巧让 JPA 不加载隐式请求的数据更容易理解。

    关于hibernate - 如何避免 1 + n 数据库调用在 Hibernate 中进行双向可选的一对一关联?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/39065065/

    相关文章:

    java - Hibernate:使用带有命名参数的选择插入实体

    java - native 查询 spring data JPA 的问题返回带有空对象的空 JSON 数组

    java - Spring MVC 和 Spring Data Repository 不起作用

    java - Hibernate - 使用包含父 ID 的复合键 - OneToMany

    java - 如何在 Spring Boot 应用程序中将数据库架构更改从源数据库同步到目标数据库

    java - JPA1 - ID 是父类(super class)的一部分,由于 @Entity 注释类中缺少 @Id,导致错误

    java - 将 IN 子句列表添加到 JPA 查询

    Spring 数据 JPA : Creating Specification Subquery from different tables

    Hibernate:映射 3 个表

    java - Hibernate 验证注解——验证至少一个字段不为空