Hibernate OnetoMany with Fetch Lazy 给出 LazyInitializationException

时间:2021-06-03 05:27:38

标签: spring-boot hibernate jpa spring-data-jpa hibernate-mapping

我是 Java Persistence API 和 Hibernate 的新手,并使用 Spring JPA 存储库在 DB 中进行查询。现在,我在 Parent <-> Child 关系中有两个实体,带有 @OneToMany 的 Parent 实体和带有 @ManyToOne 映射的 Child 实体。

父实体:-

@Entity
@Table(name = "PERSONS")
public class Persons {

    ...

    @OneToMany(mappedBy = "person", cascade = CascadeType.ALL, fetch = FetchType.LAZY, orphanRemoval = true)
    public List<Cards> cards = new ArrayList<Cards>();

    ...
}

子实体:-

@Entity
@Table(name = "CARDS")
public class Cards {

    ...

    @ToString.Exclude
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "PERSON_ID", nullable = false, insertable = false, updatable = false)
    public Person person;
    ...
}

我正在使用我的 PersonsRepository,如下所示:-

@Repository
public interface PersonsRepository extends JpaRepository<Persons, String> {

     ....
}

现在关系中使用的 fetchType 在两端都是 LAZY。现在,每当我尝试遍历列表并尝试使用 person.getCards() 处理每个卡片时,它都会给我以下错误:-

org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: com.xxx.abc.Persons.cards, could not initialize proxy - no Session
    at org.hibernate.collection.internal.AbstractPersistentCollection.throwLazyInitializationException(AbstractPersistentCollection.java:606)
    at org.hibernate.collection.internal.AbstractPersistentCollection.withTemporarySessionIfNeeded(AbstractPersistentCollection.java:218)
    at org.hibernate.collection.internal.AbstractPersistentCollection.initialize(AbstractPersistentCollection.java:585)
    at org.hibernate.collection.internal.AbstractPersistentCollection.read(AbstractPersistentCollection.java:149)
    at org.hibernate.collection.internal.PersistentSet.iterator(PersistentSet.java:188)
    at java.util.Spliterators$IteratorSpliterator.estimateSize(Spliterators.java:1821)
    at java.util.Spliterator.getExactSizeIfKnown(Spliterator.java:408)
    at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:481)
    at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:472)
    at java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:708)
    at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
    at java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:499)

现在我发现每个人都说使用 LAZY 是 Hibernate 中最好的方法,它也说明了更多关于正确设计代码的内容。我同意我使用 person.getCards() 的方式不会有任何打开的会话,这就是它给我 LazyInitializationException 的原因,但这背后的目的是为了节省更多的数据库调用。

假设我有 1000 个人列表,这意味着我必须为每个人分别调用 1000 次 getCards()。这就是为什么如果我在 Person @OneToMany 中使用 FETCHTYPE.EAGER,性能影响是什么,因为一切都将被急切地获取。

需要有关针对此类问题所遵循的最佳做法的建议。 TIA。

编辑:- 我在服务类中有一个方法,我在其中使用 @transactional ,如下所示:-

@Transactional(readOnly = true)
    public void fetchData(Integer param1, Timestamp param2, Timestamp param3, List<String> param4, NavigableMap<Long, List<Cards>> param5) {
        List<Persons> validPersons = personRepo.getCardsPerPerson(param2, param3);
        if(validPersons != null && !validPersons.isEmpty()) {
            // store the cards on the basis of epoch timestamp
            prepareTimestampVsCardsMap(validPersons, param4, param5);
        }
    }

private void prepareTimestampVsCardsMap(List<Persons> validPersons, List<String> uList, NavigableMap<Long, List<Cards>> timestampVsCardsList) {
        for(Person person : validPersons) {
            Long epoch = order.getOrderTime().getTime();
            Set<Cards> cardsPerPerson = person.getCards();
}
}

此外,存储库中用于获取与某人相关联的卡片的查询正在使用 join fetch,如下所示:-

@Query(value = "select p from Person p join fetch Cards c on p.id = c.id WHERE p.orderTime BETWEEN ?1 AND ?2 ORDER BY orderTime ASC")
    public List<Person> getCardsPerPerson(Timestamp param1, Timestamp param2);

我仍然遇到上述LazyInitializationException。任何人都可以帮忙。

2 个答案:

答案 0 :(得分:1)

首先,最好使用 FetchType.LAZY 而不是 FetchType.EAGER。为什么?因为您可能并不每次都需要所有数据。如果您想返回一个 Person 列表并以某种方式在某处显示它们,您是否还需要获取所有卡片?如果没有,那么 FetchType.LAZY 将是更好的选择,然后您可以控制所需的数据量。

LazyInitializationException 通常表示您在打开 Session 时没有获取所需的所有数据。获取关联数据的方法有很多种(在处理请求时,没有一种方法使 Session 保持打开状态):

1.在 JPQL/HQL 中使用 join fetch

@Query("select p from Person p join fetch p.cards where ...")
List<Person> getCardsPerPerson(Timestamp param1, Timestamp param2);

2.如果您使用的是 Spring Data,则可以使用 @EntityGraph 而不是 join fetch

@EntityGraph(attributePaths = { "cards" })
List<Person> getPersons();

这样,每次调用 getPersons 时,它也会获取卡片。当然,如果你必须写 @Query,你就不能使用这个。

如果您将 Spring Data's naming conventions 用于一些简单的查询,那么 @EntityGraph 将是获取关联的一个选项。

3.使用 Criteria API

同样,如果您使用的是 Spring Data,这只是一个备用解决方案,以防您最终使用 MultipleBagFetchException。我不会详细介绍这一点,但如果您遇到此异常,您可以在 Vlad Mihalcea 的博客文章 The best way to fix the Hibernate MultipleBagFetchException 中找到解决方案。

答案 1 :(得分:0)

您误解了 EAGER 加载意味着 Hibernate 将使用一条语句获取所有数据,这是错误的。使用 EAGER 作为策略,框架将只执行获取每个实体的所有数据所需的每个查询。

示例:如果一个实体有 2 个 EAGER 关系,获取一个将导致 3 个语句,一个用于加载实体,一个用于每个关系。如果您有 3 个实体,您将有 7 个语句,初始语句加载 3 个对象,每个对象加上 2 个。

当您的治疗需要一切时,目前没有真正的性能影响。但大多数应用程序不是由一种处理方式构成的。这意味着您的应用程序中的每个处理都会加载 EAGER 的所有内容,即使不需要。这将有效地减慢一切。如果所有内容都在 EAGER 中,您还有可能将所有数据库加载到内存中。

这就是为什么推荐使用 LAZY 的原因。

至于您的 LazyInitializationException,在您的堆栈跟踪中似乎您正在使用 stream API。由于缺少细节,这是一个疯狂的猜测,但 JPA/Hibernate 不处理线程之间的会话共享,因此如果您使用 parrallelStream,它可能会导致问题。

相关问题