使用 Fetch Lazy 的 Hibernate OnetoMany 给出 LazyInitializationException

Hibernate OnetoMany with Fetch Lazy giving LazyInitializationException

我是 Java Persistence API 和 Hibernate 以及使用 Spring JPA 存储库在数据库中查询的新手。现在我在父 <-> 子关系中有两个实体,父实体与 @OneToMany 和子实体与 @ManyToOne 映射。

父实体:-

@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.

编辑:- 我在服务 class 中有一个方法,我在其中使用 @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();
}
}

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

@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。谁能帮帮忙

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

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

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

中,您还冒着将所有数据库加载到内存中的风险

这就是为什么 LAZY 是推荐的方法。

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

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

LazyInitializationException 通常表示您在打开 Session 时没有获取所需的所有数据。获取关联数据的方法有很多(none,其中在处理请求时保持 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 数据,则可以使用 @EntityGraph 而不是 join fetch

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

这样,每次调用 getPersons 时,它也会提取卡片。当然,如果非得写@Query.

就不能用这个了

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

3。使用 Criteria API

同样,如果您使用 Spring 数据,这只是一个后备解决方案,以防您最终得到 MultipleBagFetchException. I will not go into details for this one, but in case you encounter this exception, you'll find solution in Vlad Mihalcea's blog post The best way to fix the Hibernate MultipleBagFetchException