休眠 OnetoMany 与 Fetch Lazy 给 LazyInitializationException
Posted
技术标签:
【中文标题】休眠 OnetoMany 与 Fetch Lazy 给 LazyInitializationException【英文标题】:Hibernate OnetoMany with Fetch Lazy giving LazyInitializationException 【发布时间】:2021-08-21 05:50:44 【问题描述】:我是 Java Persistence API 和 Hibernate 的新手,我使用 Spring JPA 存储库在 DB 中进行查询。现在我在 Parent Child 关系中有两个实体,Parent 实体与 @OneToMany 和 Child 实体与 @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)
现在我发现每个人都说在 Hibernate 中使用 LAZY 是最好的方法,而且它还更多地说明了代码的正确设计。我同意我使用 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。谁能帮忙。
【问题讨论】:
你在哪里调用 person.getCards()?在服务类中对吗?我假设您没有在您的服务方法中使用“@Transactional”,在这种情况下,当您从存储库中获取人员时,会话将关闭并且您的延迟初始化如果没有用。您的服务方法上应该有“@Transactional”,以便在您的所有业务案例在单个事务中完成后关闭会话。 最好将获取策略设置为Lazy
。如果你需要卡片和人,你应该通过你的存储库调用来获取它(例如,在方法上使用@Query("select p from Person p join fetch Cards c on p.id = c.person.id")
,或者使用实体图)。这样,您只需调用一次 DB 即可获取您需要的所有数据。
@Lucia 请看我的编辑。我已经添加了所有东西并使用“@Transactional”但得到了同样的错误
@StefanGolubović 我以同样的方式使用了查询,使用 join fetch 来获取与某人关联的卡片列表。请在我的编辑中查看。仍然没有成功并得到同样的异常
@user2594 抱歉,我混合使用了 SQL 和 JPQL。你可以试试@Query("select p from Person p join fetch p.cards where ...")
。您总是可以在 vladmihalcea.com 博客上找到解决持久性问题的方法。
【参考方案1】:
您误以为EAGER
加载意味着Hibernate 将使用一条语句获取所有数据,这是错误的。使用EAGER
作为策略,框架将只执行获取每个实体的所有数据所需的每个查询。
示例:如果一个实体有 2 个EAGER
关系,则获取一个将导致 3 个语句,一个用于加载实体,一个用于其每个关系。如果您有 3 个实体,您将有 7 个语句,初始语句加载 3 个对象,每个对象加上 2 个。
当您的治疗需要一切时,目前没有真正的性能影响。但大多数应用程序不是由一种治疗方法制成的。这意味着您的应用程序中的每个处理都将加载 EAGER
的所有内容,即使不需要。这将有效地减慢一切。如果所有内容都在 EAGER
中,您也有将所有数据库加载到内存中的风险。
这就是为什么LAZY
是推荐的方法。
至于您的LazyInitializationException
,在您的堆栈跟踪中似乎您正在使用stream
API。由于缺少细节,这是一个疯狂的猜测,但 JPA/Hibernate 不处理线程之间的共享会话,因此如果您使用parrallelStream
,它可能会导致问题。
【讨论】:
【参考方案2】:首先,使用FetchType.LAZY
而不是FetchType.EAGER
总是更好。为什么?因为您可能并不每次都需要所有数据。如果您想返回Person
s 的列表并以某种方式在某处显示它们,您是否还需要获取他们所有的卡片?如果没有,那么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找到解决办法。
【讨论】:
以上是关于休眠 OnetoMany 与 Fetch Lazy 给 LazyInitializationException的主要内容,如果未能解决你的问题,请参考以下文章