Spring数据findAll()不会急切地获取

时间:2018-03-09 10:59:45

标签: java hibernate jpa spring-data eager-loading

我有两个具有单向one to many关系的实体。

@Entity
public class Basket {

    @Id
    @GeneratedValue
    private Long id;

    private int capacity;
}

@Entity
public class Item {

    @Id
    @GeneratedValue
    private Long id;

    private String name;

    @ManyToOne
    private Basket basket;
}

我保存了几个对象:

    Basket basket1 = new Basket(100);
    Basket basket2 = new Basket(200);
    Basket basket3 = new Basket(300);
    basketRepository.save(asList(basket1, basket2, basket3));

    Item item1 = new Item("item1", basket1);
    Item item11 = new Item("item11", basket1);
    Item item2 = new Item("item2", basket2);
    Item item22 = new Item("item22", basket2);
    Item item3 = new Item("item3", basket3);
    Item item33 = new Item("item33", basket3);
    itemRepository.save(asList(item1, item11, item2, item22, item3, item33));

    // Loading one item. Basket fetched eagerly.
    itemRepository.findOne(1L);

    // Loading many items. Baskets are not loaded (n+1 select problem).
    itemRepository.findAll();

@ManyToOne注释默认使用eager fetch。 当我使用Item加载一个findOne()时,Hibernate会生成left outer join的查询,并在同一查询中获取Basket。 但是,当我使用findAll()时,Hibernate首先获取所有Items,然后执行N selects(每个Basket一个),以便它导致(n+1) select problem。为什么Hiberante不会急切地使用Basket方法获取findAll()个对象以及如何解决这个问题?

2 个答案:

答案 0 :(得分:4)

JPA 2.0规范来看,默认情况下@ManyToOne是EAGER。

现在,当您使用findAll()时,它等同于触发像entityManager.createQuery(...)这样的JPQL查询,默认情况下它首先加载items,然后加载each item它会加载basket实体导致N + 1问题。

您可以采用以下两种方法之一:

  1. 通过在findAll方法上指定@Query注释来覆盖使用的默认查询,并使用select i from Item i left join fetch i.basket之类的加入查询。

  2. basket类上使用名称为Item的{​​{3}},并指定需要急切加载Item图的哪一部分。在findAll方法上,使用@EntityGraph(value = "basket")请注意,根据@NamedEntityGraph,我们还可以使用attributePath通过@EntityGraph without the need of having to explicitly add @NamedEntityGraph to your domain types定义ad-hoc实体图。

答案 1 :(得分:1)

您可以在存储库中使用@Query注释覆盖findAll方法。以下是示例代码

public interface ItemRepository extends CrudRepository<Item, Long> {
    @Override
    @Query("select item from Item item left join fetch item.basket")
    Iterable<Item> findAll();
}

然后,您可以记录您的SQL查询,以查看只有一个查询

Hibernate: select item0_.id as id1_1_0_, basket1_.id as id1_0_1_, item0_.basket_id as basket_i3_1_0_, item0_.name as name2_1_0_, basket1_.capacity as capacity2_0_1_ from item item0_ left outer join basket basket1_ on item0_.basket_id=basket1_.id

之前

2018-03-09 13:26:52.269  INFO 4268 --- [           main] o.h.h.i.QueryTranslatorFactoryInitiator  : HHH000397: Using ASTQueryTranslatorFactory
Hibernate: select item0_.id as id1_1_, item0_.basket_id as basket_i3_1_, item0_.name as name2_1_ from item item0_
Hibernate: select basket0_.id as id1_0_0_, basket0_.capacity as capacity2_0_0_ from basket basket0_ where basket0_.id=?
Hibernate: select basket0_.id as id1_0_0_, basket0_.capacity as capacity2_0_0_ from basket basket0_ where basket0_.id=?
Hibernate: select basket0_.id as id1_0_0_, basket0_.capacity as capacity2_0_0_ from basket basket0_ where basket0_.id=?