Spring boot Hibernate OnetoMany与Fetch Lazy一起提供LazyInitializationException

Spring boot Hibernate OnetoMany与Fetch Lazy一起提供LazyInitializationException,spring-boot,hibernate,jpa,spring-data-jpa,hibernate-mapping,Spring Boot,Hibernate,Jpa,Spring Data Jpa,Hibernate Mapping,我是Java持久性API和Hibernate的新手,使用Spring JPA存储库在DB中进行查询。现在我在父子关系中有两个实体,父实体为@OneToMany,子实体为@ManyToOne映射 父实体:- @Entity @Table(name = "PERSONS") public class Persons { ... @OneToMany(mappedBy = "person", cascade = CascadeType.ALL

我是Java持久性API和Hibernate的新手,使用Spring JPA存储库在DB中进行查询。现在我在父子关系中有两个实体,父实体为@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;
    ...
}
我正在使用我的个人存储库,如下所示:-

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

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

假设我有1000个人列表,这意味着我必须为每个人分别调用1000个getCards()。这就是为什么如果我亲自@OneToMany使用FETCHTYPE.EAGER会对性能产生什么影响,因为所有内容都会被急切地获取

需要关于针对此类问题所遵循的最佳实践的建议。蒂亚

编辑:- 我在服务类中有一个方法,我在其中使用@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();
}
}
@Transactional(readOnly=true)
public void fetchData(整数参数1、时间戳参数2、时间戳参数3、列表参数4、NavigableMap参数5){
List validPersons=personRepo.getcardsperson(参数2,参数3);
if(validPersons!=null&&!validPersons.isEmpty()){
//根据历元时间戳存储卡
准备TAMPVSCARDSMAP(有效人员,参数4,参数5);
}
}
私有void prepareTimestampVsCardsMap(列出有效人员、列出uList、NavigableMap timestampVsCardsList){
适用于(个人:validPersons){
长历元=order.getOrderTime().getTime();
设置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);
@Query(value=“从个人p中选择p加入p.id上的取货卡c=c.id,其中p.orderTime介于?1和?2之间按orderTime ASC排序”)
公共列表getCardsPerPerson(时间戳param1,时间戳param2);

我仍然得到了上面提到的同样的LazyInitializationException。任何人都可以提供帮助。

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

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

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

这就是为什么建议采用
LAZY


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

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

LazyInitializationException
通常表示在打开
会话时未获取所需的所有数据。获取相关数据有多种方法(在处理请求时,没有一种方法保持
会话
打开):

1.在JPQL/HQL中使用 这样,每次你调用
getPersons
,它也会取卡。当然,如果必须编写
@Query
,则不能使用此选项

如果您使用的是一些简单的查询,那么
@EntityGraph
将是获取关联的一个选项

3.使用
同样,如果您使用的是Spring数据,那么这只是一个备用解决方案,以防您最终使用它。我不会详细介绍这一点,但如果您遇到此异常,您可以在的博客文章中找到解决方案。

您到底在哪里调用person.getCards()?在服务班,对吗?我假设您在服务方法中没有使用“@Transactional”,在这种情况下,当您从存储库中获取person时,会话将关闭,并且您的惰性初始化(如果没有用的话)。您的服务方法上应该有“@Transactional”,以便在单个事务中完成所有业务案例后关闭会话。最好将抓取策略设置为
Lazy
。如果您需要卡片和人一起使用,您应该使用存储库调用来获取它(例如,在方法上使用
@Query(“从Person p选择p加入获取卡片c on p.id=c.Person.id”)
,或者使用实体图)。这样,您只需对DB进行一次调用,即可获取所需的所有数据。@Lucia请查看我的编辑。我已经添加了所有的东西并使用“@Transactional”,但是得到了
@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);
@Query("select p from Person p join fetch p.cards where ...")
List<Person> getCardsPerPerson(Timestamp param1, Timestamp param2);
@EntityGraph(attributePaths = { "cards" })
List<Person> getPersons();