본문 바로가기
Spring

Spring JPA / org.hibernate.LazyInitializationException: could not initialize proxy 장애 극복기

by yang sing 2023. 6. 29.

안녕하세요. 

이번 포스팅은 제가 회사에서 JPA로 개발을 하다가 발생한 이슈에 대해 작성을 하려고 합니다. 

생각보다 너무 간단하게 해결을 했었지만 다시 한번 JPA의 동작 방식에 대해서 생각하게 해주는 이슈라서 잊지 않기 위해 작성을 하기로 결심 했습니다.

 

 

이슈 발생

프로젝트를 진행 하던 중 연관관계가 있는 엔티티에서 연관관계 참조형 필드를 조회 후 해당 데이터를 뽑아오는 과정에서 org.hibernate.LazyInitializationException: could not initialize proxy 에러 메세지가 출력이 되면서 이슈가 발생이 되었습니다.

 

 

문제의 코드

회사 코드를 직접적으로 사용을 할 수가 없어서 기존 포스팅에 사용했던 예제 코드들로 재현을 하고자 합니다.

@Entity
public class Team {
    @Id
    @GeneratedValue
    private Long seq;
    private String name;
    
    @OneToMany(mappedBy="team")
    private List<Member> memberList = new ArrayList<Member>();
}

@Entity
public class Member {
    @Id
    @GeneratedValue
    private Long seq;
    private String name;
    private int age;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name="team_seq")// member 테이블의 team_seq 컬럼 명시
    private Team team;	// 부모 테이블 매핑 객체인 Team 엔티티 참조
}

 

@Service
@RequiredArgsConstructor
public class TestService(){
    private final Test2Service test2Service;

    public void testMethod(){
    	Member member = test2Service.findMember();
    	member.getTeam().getName();	// 이슈 발생
    }
}

@Service
@RequiredArgsConstructor
public class Test2Service(){
    private final MemberRepository memberRepository;
    
    @Transactional 
    public Member findMember(){
    	return memberRepository.findById(1l);
    }
}

testService의 testMethod 내부에서 test2Service의 findMember를 통해 Member 엔티티에 데이터를 받아오는 코드가 있다고 해보자. 

findMember에는 @Transactional 어노테이션으로 인해 트랜잭션이 시작을 하고 이로 인해 영속성 컨텍스트가 생성이 되면서 DB에서 조회를 한 데이터가 return이 되면서 findMember 메소드는 끝이 난다.

testMethod 내부에서 member 엔티티 객체에는 test2Service.findMember() 매소드로 인한 member 데이터가 분명히 존재하는데 왜 getTeam()을 호출을 하게 되면 장애가 나는 것일까?

 

이슈의 원인

JPA에서는 트랜잭션이 시작이 되면서 영속성 컨텍스트가 생성이 되면서 JPA를 통해 데이터를 조회 하면 영속성 컨텍스트에서 관리를 하게 되는 구조이다.

트랜잭션이 종료가 되면 영속성 컨텍스트도 사라지며 데이터를 가져온 엔티티 객체는 더 이상 JPA에서 관리를 하게 되지 않게 되고 그로 인해 지연 로딩 옵션을 연관관계가 있는 참조형 필드의 데이터에 접근을 할 때 데이터를 가져올 수 없게 되는것이다.

 

위 코드를 보면 Member의 참조형 필드인 team의 어노테이션이 @ManyToOne(fetch = FetchType.LAZY) 으로 설정이 되어있기 때문에 방금 설명을 했듯이 영속성 컨텍스트의 관리를 받지 않은 엔티티의 상황에서 지연 로딩을 할 수 없게 되면서 에러가 발생을 했다.

 

 

해결 방안

해당 이슈는 JPA의 기본 동작 원리를 이해하면 해결 방안은 생각보다 쉽게 떠오를 수 있다.

'영속성 컨텍스트가 관리를 해주지 않는다?'  그렇다면 트랜잭션을 하나로 묶어서 하나의 영속성 컨텍스트 안에서 조회를 하면 된다.

다시 말해 TestService의 testMethod() 위에 @Transactional 어노테이션을 적용시켜 testMethod가 시작하자마자 트랜잭션을 생성하면 된다.

 

@Transactional의 전파 옵션은 여러가지가 있는데 그중에 가장 기본값은 하위 메소드에 새로운 트랜잭션이 있더라도 하나의 트랜잭션으로 융합하는 옵션이 기본값으로 testMethod에서 트랜잭션이 시작을 했고 findMember() 메소드에서 @Transactional 어노테이션이 있더라도 새로운 트랜잭션이 발생하지 않고 기존 트랜잭션 그대로 진행이 됨으로 영속성 컨텍스트가 유지가 되어 지연 로딩의 team 필드의 데이터도 잘 조회가 될것이다.