函数指针使用场景和选择_在N + 1场景中使用@NamedEntityGraph更有选择地加载JPA实体...

函数指针使用场景和选择

N + 1问题是使用ORM解决方案时的常见问题。 当您将某些@OneToMany关系的fetchType设置为lazy时,就会发生这种情况,以便仅在访问Set / List时才加载子实体。 假设我们有一个具有两个关系的Customer实体:每个客户的一组订单和一组地址。

@OneToMany(mappedBy = "customer", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private Set<OrderEntity> orders;

@OneToMany(mappedBy = "customer", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private Set<AddressEntity> addresses;

要加载所有客户,我们可以发出以下JPQL语句,然后加载每个客户的所有订单:

List<CustomerEntity> resultList = entityManager.createQuery("SELECT c FROM CustomerEntity AS c", CustomerEntity.class).getResultList();
for(CustomerEntity customerEntity : resultList) {
    Set<OrderEntity> orders = customerEntity.getOrders();
    for(OrderEntity orderEntity : orders) {
	...
    }
}

Hibernate 4.3.5(与JBoss AS Wildfly 8.1.0CR2一起提供)将从数据库中仅为两个(!)客户生成以下一系列SQL语句:

Hibernate: 
     select
         customeren0_.id as id1_1_,
         customeren0_.name as name2_1_,
         customeren0_.numberOfPurchases as numberOf3_1_ 
     from
         CustomerEntity customeren0_
Hibernate: 
     select
         orders0_.CUSTOMER_ID as CUSTOMER4_1_0_,
         orders0_.id as id1_2_0_,
         orders0_.id as id1_2_1_,
         orders0_.campaignId as campaign2_2_1_,
         orders0_.CUSTOMER_ID as CUSTOMER4_2_1_,
         orders0_.timestamp as timestam3_2_1_ 
     from
         OrderEntity orders0_ 
     where
         orders0_.CUSTOMER_ID=?
Hibernate: 
     select
         orders0_.CUSTOMER_ID as CUSTOMER4_1_0_,
         orders0_.id as id1_2_0_,
         orders0_.id as id1_2_1_,
         orders0_.campaignId as campaign2_2_1_,
         orders0_.CUSTOMER_ID as CUSTOMER4_2_1_,
         orders0_.timestamp as timestam3_2_1_ 
     from
         OrderEntity orders0_ 
     where
         orders0_.CUSTOMER_ID=?

如我们所见,第一个查询从表CustomerEntity中选择所有客户。 接下来的两个选择先提取,然后在第一个查询中加载我们已加载的每个客户的订单。 当我们有100个客户而不是2个客户时,我们将获得101个查询。 一个初始查询可加载所有客户,然后针对100个客户中的每个客户,另外查询一个订单。 这就是为什么将此问题称为N + 1的原因。

解决此问题的常见习惯是强制ORM生成内部联接查询。 在JPQL中,这可以通过使用JOIN FETCH子句来完成,如以下代码片段所示:

entityManager.createQuery("SELECT c FROM CustomerEntity AS c JOIN FETCH c.orders AS o", CustomerEntity.class).getResultList();

正如预期的那样,ORM现在使用OrderEntity表生成一个内部联接,因此只需要一个SQL语句即可加载所有数据:

select
    customeren0_.id as id1_0_0_,
    orders1_.id as id1_1_1_,
    customeren0_.name as name2_0_0_,
    orders1_.campaignId as campaign2_1_1_,
    orders1_.CUSTOMER_ID as CUSTOMER4_1_1_,
    orders1_.timestamp as timestam3_1_1_,
    orders1_.CUSTOMER_ID as CUSTOMER4_0_0__,
    orders1_.id as id1_1_0__
from
    CustomerEntity customeren0_
inner join
    OrderEntity orders1_
        on customeren0_.id=orders1_.CUSTOMER_ID

在您知道必须为每个客户加载所有订单的情况下,JOIN FETCH子句将SQL语句的数量从N + 1减少到1。这当然具有缺点,即您现在要转移一个订单的所有订单。客户一次又一次的客户数据(由于查询中的其他客户列)。

JPA规范从版本2.1引入,即所谓的NamedEntityGraphs。 通过此批注,您可以描述JPQL查询应加载的图形,而不是JOIN FETCH子句可以执行的图形,从而为N + 1问题提供了另一种解决方案。 下面的示例演示了我们的客户实体的NamedEntityGraph,该实体应该仅加载客户名称及其订单。 订单在子图ordersGraph中有更详细的描述。 在这里,我们看到我们只想加载订单的字段ID和CampaignId。

@NamedEntityGraph(
        name = "CustomersWithOrderId",
        attributeNodes = {
                @NamedAttributeNode(value = "name"),
                @NamedAttributeNode(value = "orders", subgraph = "ordersGraph")
        },
        subgraphs = {
                @NamedSubgraph(
                        name = "ordersGraph",
                        attributeNodes = {
                                @NamedAttributeNode(value = "id"),
                                @NamedAttributeNode(value = "campaignId")
                        }
                )
        }
)

在通过NameManager通过EntityManager加载JPQL查询后,将其命名为JPQL查询的提示:

EntityGraph entityGraph = entityManager.getEntityGraph("CustomersWithOrderId");
entityManager.createQuery("SELECT c FROM CustomerEntity AS c", CustomerEntity.class).setHint("javax.persistence.fetchgraph", entityGraph).getResultList();

Hibernate从版本4.3.0.CR1开始支持@NamedEntityGraph批注,并为上面显示的JPQL查询创建以下SQL语句:

Hibernate: 
    select
        customeren0_.id as id1_1_0_,
        orders1_.id as id1_2_1_,
        customeren0_.name as name2_1_0_,
        customeren0_.numberOfPurchases as numberOf3_1_0_,
        orders1_.campaignId as campaign2_2_1_,
        orders1_.CUSTOMER_ID as CUSTOMER4_2_1_,
        orders1_.timestamp as timestam3_2_1_,
        orders1_.CUSTOMER_ID as CUSTOMER4_1_0__,
        orders1_.id as id1_2_0__ 
    from
        CustomerEntity customeren0_ 
    left outer join
        OrderEntity orders1_ 
            on customeren0_.id=orders1_.CUSTOMER_ID

我们看到Hibernate不会发出N + 1查询,而是@NamedEntityGraph注释强制Hibernate为每个左外部联接加载订单。 当然,这与FETCH JOIN子句有微妙的区别,在子句中,Hibernate创建了内部联接。 与FETCH JOIN子句相反,左外部联接还将加载不存在订单的客户,在FETCH JOIN子句中,我们仅加载具有至少一个订单的客户。

有趣的是,Hibernate加载的负载比表CustomerEntity和OrderEntity的指定属性更多。 由于这与@NamedEntityGraph的规范(第3.7.4节)相冲突,因此我为此创建了一个JIRA问题

结论

我们已经看到,在JPA 2.1中,我们为N + 1问题提供了两种解决方案:我们可以使用FETCH JOIN子句来急切地获取一个@OneToMany关系,这将导致一个内部联接,或者我们可以使用@NamedEntityGraph功能使我们通过左外部联接指定要加载的@OneToMany关系。

翻译自: https://www.javacodegeeks.com/2014/07/using-namedentitygraph-to-load-jpa-entities-more-selectively-in-n1-scenarios.html

函数指针使用场景和选择

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值