【问题标题】:Spring data findAll() does not fetch eagerlySpring 数据 findAll() 不会急切地获取
【发布时间】:2018-08-17 22:28:28
【问题描述】:

我有两个具有单向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。 当我使用findOne() 加载一个Item 时,Hibernate 使用left outer join 生成查询,并且在同一个查询中获取Basket。 但是,当我使用findAll() 时,Hibernate 首先获取所有Items,然后执行N selects(每个Basket 一个),因此它会导致(n+1) select problem。为什么 Hiberante 不急切地使用 findAll() 方法获取 Basket 对象以及如何解决这个问题?

【问题讨论】:

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


    【解决方案1】:

    根据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. Item 类上使用名称为basket@NamedEntityGraph,并指定需要立即加载Item 图形的哪一部分。在findAll 方法上,使用@EntityGraph(value = "basket")请注意,根据spring jpa entity graph,我们还可以使用attributePath 通过@EntityGraph without the need of having to explicitly add @NamedEntityGraph to your domain types 定义临时实体图。

    【讨论】:

    • 或者重写 'findAll' 方法,只需添加 @EntityGrath(attributePaths = "basket") Spring 注解,使方法快速加载 'basket'。
    • 哦,是的..它是@EntityGraph(value = "basket", type = EntityGraphType.FETCH)。我在 spring repo 之外尝试它,所以在 entityManger 上手动使用 setHint。更新答案。
    • 第二个参数不是必需的,因为它被default 使用。 value 参数用于“命名格拉斯”。我们必须在这里使用attributePaths 参数。
    • 是的,但默认是 FETCH。不确定是 value 还是 attributePath,我现在无法尝试使用 spring repo。
    • 现在知道了。根据docs.spring.io/spring-data/jpa/docs/current/reference/html/…,我们可以使用attributePath 通过@EntityGraph 定义临时实体图,而无需将@NamedEntityGraph 显式添加到您的域类型。
    【解决方案2】:

    您可以在存储库中使用 @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=?
    

    【讨论】:

      猜你喜欢
      • 2021-02-17
      • 2012-07-12
      • 1970-01-01
      • 2011-06-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-07-21
      • 1970-01-01
      相关资源
      最近更新 更多