새소식

Programming/Solving Errors

[JPA] LazyInitializationException 문제 해결하기

 

 

에러 내용

 

org.hibernate.LazyInitializationException: could not initialize proxy [com.hyunrian.project.domain.Member#1] - no Session
	at org.hibernate.proxy.AbstractLazyInitializer.initialize(AbstractLazyInitializer.java:165)
	at org.hibernate.proxy.AbstractLazyInitializer.getImplementation(AbstractLazyInitializer.java:314)
	at org.hibernate.proxy.pojo.bytebuddy.ByteBuddyInterceptor.intercept(ByteBuddyInterceptor.java:44)
	at org.hibernate.proxy.ProxyConfiguration$InterceptorDispatcher.intercept(ProxyConfiguration.java:102)
	at com.hyunrian.project.domain.Member$HibernateProxy$KTB3GaAm.toString(Unknown Source)
	at java.base/java.lang.StringConcatHelper.stringOf(StringConcatHelper.java:453)
	at com.hyunrian.project.domain.MusicPreference.toString(MusicPreference.java:14)
    (이하 생략)

 

LazyInitializationException이 발생하여 프록시를 초기화할 수 없다는 오류 메시지다.

 

 

 

문제 배경

 

개인 프로젝트의 DB 조회 테스트 도중 발생했다.

Querydsl로 작성한 조회 쿼리가 있는데, 원하는대로 데이터가 잘 조회되는지 확인하기 위해 간단히 작성한 테스트 코드에서 막혀버렸다.

 

앞 뒤 부분은 생략하고, 테스트 코드에서 문제가 되는 부분은 다음과 같다.

List<MusicPreference> topPreference = preferenceService.getTopPreference(member);

for (MusicPreference musicPreference : topPreference) {
    log.info("musicPreference={}", musicPreference);
}

 

조회 결과를 로그로 확인할 수 없었다.

 

 

Entity인 MusicPreference는 다음과 같다.

@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Getter
@ToString
public class MusicPreference {

    @Id @GeneratedValue(strategy = GenerationType.SEQUENCE)
    @Column(name = "music_preference_id")
    private Long id;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "member_id")
    private Member member;

    private String genre;

    private String trackId;

    private LocalDateTime createdDate = LocalDateTime.now();

    public MusicPreference(Member member, String genre, String trackId) {
        this.member = member;
        this.genre = genre;
        this.trackId = trackId;
    }

}

 

 

이 예외가 발생하는 원인을 찾아보니 다음과 같았다.

 

  • Lazy Loading으로 연관된 객체는 바로 초기화되지 않고, 필요할 때 정보가 채워지는 프록시 객체로 채워짐
  • 서비스 계층에 적용한 Transaction으로 인해 메서드가 종료되면 영속성 컨텍스트가 유지되지 않음
  • 영속성 컨텍스트에서 관리되지 않으므로 필요한 값이 있을 때 프록시 객체를 채우지 않아 예외가 발생됨

 

 

 

 

문제 해결

 

방법 1. `@ToString` 삭제

 

우선 객체를 문자로 출력하는 과정에서 발생했기 때문에 `@ToString`을 제거하면 예외는 발생하지 않지만, 객체의 필드값을 확인할 수 없으니 원하는 해결방법이 아니다. 애초에 ToString은 개발 편의를 위한 메서드니까.

 

 

방법 2. Service 계층에서 실행

 

public List<MusicPreference> getTopPreference(Member member) {
    List<MusicPreference> preferenceList = preferenceQueryRepository.findByMemberIdInOrder(member.getId());
    for (MusicPreference musicPreference : preferenceList) {
        log.info("musicPreference={}", musicPreference);
    }
    return preferenceList;
}

 

Transaction 내에서 실행하면 해결되는 부분이니 Service 계층에서 log를 찍어보면 정상적으로 출력된다.

하지만 log로 조회 결과를 확인하는 건 테스트를 위한 것이기 때문에, 서비스단에서 굳이 실행할 필요는 없었다.

 

 

방법 3. 엔티티의 연관 필드를 즉시 로딩 방식(Eager)으로 변경

 

@ManyToOne //FetchType: LAZY -> EAGER
@JoinColumn(name = "member_id")
private Member member;

 

이 방법 역시 문제는 해결되지만 연관된 엔티티의 모든 데이터가 항상 로드되기 때문에 성능 저하의 문제가 발생한다.

 

 

방법 4. `toString()`을 오버라이드하여 필요한 필드만 구현

 

@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Getter
public class MusicPreference {

    @Id @GeneratedValue(strategy = GenerationType.SEQUENCE)
    @Column(name = "music_preference_id")
    private Long id;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "member_id")
    private Member member;

    private String genre;

    private String trackId;

    private LocalDateTime createdDate = LocalDateTime.now();

    public MusicPreference(Member member, String genre, String trackId) {
        this.member = member;
        this.genre = genre;
        this.trackId = trackId;
    }

    @Override
    public String toString() {
        return "MusicPreference{" +
                "genre='" + genre + '\'' +
                ", trackId='" + trackId + '\'' +
                '}';
    }
}

 

최종적으로 적용한 방법이다.

확인하고자 했던 데이터는 member가 아니라 genre와 trackId였기 때문에 이 두 개만 출력되도록 변경하였다.

 

 

JPA에 대해 공부할 때는 영속성 컨텍스트의 생명주기에 대해 이해를 했다 여겼는데, 막상 실제로 적용해 보니 이 부분을 생각하지 못했다.

그래도 이 경험을 통해 JPA의 개념을 한번 더 잡게 되었다.🤪

 

 

 

Contents

Copied URL!

Liked this Posting!