SpringBoot/오류

org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: Library.Library.Member.Member.heartList: could not initialize proxy - no Session 에러

se0nghyun2 2023. 8. 29. 21:08
org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: Library.Library.Member.Member.heartList: could not initialize proxy - no Sessionorg.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: Library.Library.Member.Member.heartList: could not initialize proxy - no Session

 

발생상황

- 일대다(Member:Heart) 양방향 관계

- 등록되어있는 멤버는 특정 책에 대하여 찜목록에 추가한다.

- 따로 서비스 로직에 트랜잭션은 걸어두지 않은 상태

 

Member엔티티

@Getter
@Setter
@Entity(name="member_table")
public class Member {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long memberId;

    private String id;

    private String pwd;

    @OneToMany(mappedBy = "member",cascade = CascadeType.ALL )
    private List<Heart> heartList = new ArrayList<>();

    public void heartBook(Heart heartBook) {
        this.heartList.add(heartBook);
    }

    public boolean notInHeartList(Long bookId){
        return !(this.getHeartList().stream().anyMatch(heart->heart.getBookId().equals(bookId))); ★2★
    }
}

- List<Heart> heartList

Member엔티티는 찜목록 리스트를 지니고 있으며

 

- heartBook(Heart heartBook)

Member엔티티를 통해서만 찜추가(heartBook) 할 수 있도록 구현하였다.

 

- notInHeartList(Long bookId)

  • 해당 도서가 이미 찜이 되어있는지 판단하는 로직
  • 양방향 관계로 매핑된 Heart엔티티들을 가져와 비교하여 판단

 

MemberService

@RequiredArgsConstructor
@Service
public class MemberService {

    private final MemberRepository memberRepository;
	
    ...
    
    public void heartBook(Long memberId, Long bookId) {
        Member member = memberRepository.findMemberByMemberId(memberId);  ★1★

        if(member.notInHeartList(bookId)){ //존재하지 않는 찜도서ID인 경우
            member.heartBook(Heart.builder().member(member).bookId(bookId).build());
            
            memberRepository.save(member);
        }
    }
}

로직순서 설명

★1. PK를 통하여 Member엔티티 조회 

★2. 조회된 Member 엔티티의 Heart리스트 내 동일한 책(bookId)을 가지고 있는지 확인

 

 

테스트 코드

 @Test
    public void 기존멤버가찜수행(){
        
        //기존 멤버 ID(PK)
        Long memberId= Long.valueOf(1); 
        
        //찜할 도서 ID(PK)
        Long newHeartBookId=Long.valueOf(99);

        memberService.heartBook(memberId,newHeartBookId);
    }

 

결과

 

org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: Library.Library.Member.Member.heartList: could not initialize proxy - no Sessionorg.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: Library.Library.Member.Member.heartList: could not initialize proxy - no Session

내 잘못된 예상은 아래와 같았다.

fetch 기본 전략은 Lazy이니깐 멤버테이블 엔티티만 조회하고

heart엔티티들은 ★2 라인의 Heart엔티티 get메소드를 통해 사용할 때 로딩이 될거야!! 문제없어!!

 

틀렸다. 왜?? 

JPA 영속성 컨텍스트는 보통 트랜잭션과 생명주기를 같이한다.

위 로직에선 트랜잭션을 걸어주지 않았으니 ★1라인 이후부턴 영속성관리 대상에서 벗어나게 된다.

★2 라인의 heart엔티티 get메소드 수행 시 이미 조회된 엔티티는 준영속 상태이니 변경감지 및 지연로딩이 동작하지 않게 되는 것이다.

 

해결 방안

1. Member엔티티 조회 시 관련 Heart엔티티까지 조회할 수 있도록 즉시로딩(Eager)  전략 사용 

    그러나 해당 방법은 비추

2. 서비스 계층 메소드 내에서 영속성 관리 대상이 될 수 있도록 트랜잭션 선언

3. 

4.

5.

 

 

 

 

참고

@Transactional 옵션을 사용하지 않는다면? :: 혹독한 겨울에 자라 배울수록 겸손한 보리 (tistory.com)

@Transactional은 조회만 할 때 있어야할까? — 꾸준히 성장하는 개발자스토리 (tistory.com)

JPA/ could not initialize proxy - no Session (tistory.com)