Spring 数据 findAll() 没有急切获取
Spring data findAll() does not fetch eagerly
我有两个具有单向 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
对象以及如何解决这个问题?
根据 JPA 2.0 规范,@ManyToOne 默认为 EAGER。
现在,当您使用 findAll()
时,它相当于触发一个像 entityManager.createQuery(...)
这样的 JPQL 查询,默认情况下它首先加载 items
然后再加载 each item
加载 basket
实体并导致 N+1 问题。
您可以采用以下两种方法之一:
通过在 findAll
方法上指定 @Query 注释来覆盖使用的默认查询,并使用带有连接的查询,如 select i from Item i left join fetch i.basket
.
使用@NamedEntityGraph with name say basket
on Item
class and specify which part of the Item
graph needs to be loaded eagerly. On the findAll
method, use @EntityGraph(value = "basket")
. Note that as per spring jpa entity graph,我们也可以使用attributePath
通过@EntityGraph without the need of having to explicitly add @NamedEntityGraph to your domain types
.[=27定义ad-hoc实体图=]
您可以在存储库中使用@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=?
我有两个具有单向 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
对象以及如何解决这个问题?
根据 JPA 2.0 规范,@ManyToOne 默认为 EAGER。
现在,当您使用 findAll()
时,它相当于触发一个像 entityManager.createQuery(...)
这样的 JPQL 查询,默认情况下它首先加载 items
然后再加载 each item
加载 basket
实体并导致 N+1 问题。
您可以采用以下两种方法之一:
通过在
findAll
方法上指定 @Query 注释来覆盖使用的默认查询,并使用带有连接的查询,如select i from Item i left join fetch i.basket
.使用@NamedEntityGraph with name say
basket
onItem
class and specify which part of theItem
graph needs to be loaded eagerly. On thefindAll
method, use@EntityGraph(value = "basket")
. Note that as per spring jpa entity graph,我们也可以使用attributePath
通过@EntityGraph without the need of having to explicitly add @NamedEntityGraph to your domain types
.[=27定义ad-hoc实体图=]
您可以在存储库中使用@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=?