我是 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。任何人都可以帮忙。
答案 0 :(得分:1)
首先,最好使用 FetchType.LAZY
而不是 FetchType.EAGER
。为什么?因为您可能并不每次都需要所有数据。如果您想返回一个 Person
列表并以某种方式在某处显示它们,您是否还需要获取所有卡片?如果没有,那么 FetchType.LAZY
将是更好的选择,然后您可以控制所需的数据量。
LazyInitializationException
通常表示您在打开 Session
时没有获取所需的所有数据。获取关联数据的方法有很多种(在处理请求时,没有一种方法使 Session
保持打开状态):
join fetch
@Query("select p from Person p join fetch p.cards where ...")
List<Person> getCardsPerPerson(Timestamp param1, Timestamp param2);
@EntityGraph
而不是 join fetch
@EntityGraph(attributePaths = { "cards" })
List<Person> getPersons();
这样,每次调用 getPersons
时,它也会获取卡片。当然,如果你必须写 @Query
,你就不能使用这个。
如果您将 Spring Data's naming conventions 用于一些简单的查询,那么 @EntityGraph
将是获取关联的一个选项。
同样,如果您使用的是 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
,它可能会导致问题。