[Spring] JPA 지연 로딩과 @Transactional

민스킴·2023년 9월 30일
0

Spring

목록 보기
5/12

JPA의 모든 변경은 트랜잭션 안에서 이루어져야 한다.

스프링 테이터 JPA가 제공하는 공통 인터페이스를 사용하면 데이터를 변경하는 메소드에 @Transactional로 트랜잭션 처리가 되어 있다.

이걸 굳이 따로 포스트 하는 이유는 해커톤 도중에 겪었던 문제의 원인을 알 수 있기 때문이다.

LazyInitializationException을 본적이 있는가? 아마 JPA를 사용해본 경험이 있다면 대부분 겪어봤을 것이다.
위 에러가 발생하는 이유는 준영속 상태의 엔티티에서 지연 로딩을 할 때 발생한다.

만약 준영속 상태와 영속성 컨텍스트를 잘 모른다면 이전 글을 보고오자!

예를 들어 다음 코드를 보자. 멤버 엔티티가 팀 엔티티를 지연 로딩 한다고 가정해보자.

// 멤버 엔티티
@Entity
public class Member {
	...  // 생략
    
    // 지연 로딩 설정
    @ManyToOne(fetch = FetchType.LAZY)  
	private Team team;
}

// memberService의 어느 메소드
public Team getTeamFromMember(int id) {
	Member member = memberRepository.find(id);
	Team team = member.getTeam(); // 여기서 LazyInitializationException 발생!

	return team;
}

"왜 에러가 발생할까?"

그 이유는 member가 준영속 상태이기 때문이다.
memberRepository.find() 메소드가 시작될 때 트랜잭션이 시작되고, 메소드가 종료될 때 트랜잭션이 커밋되기 때문이다.
트랜잭션이 커밋되면 영속성 컨텍스트가 종료되고 member는 준영속 상태이기에, 프록시를 사용한 지연 로딩에서 에러가 발생하는 것이다.

이를 해결하는 간단한 방법으로 @Transactional을 사용하면 된다. getTeamFromMember() 메소드에 @Transactional을 사용해서 메소드의 시작과 끝을 트랜잭션의 범위로 설정해주면 해결된다.

트랜잭션A(부모) 내부에 트랜잭션B(자식)가 실행되는 경우, 트랜잭션A에 트랜잭션 B가 합류한다. 다시말해 커밋이나 롤백의 기준이 트랜잭션 A만 존재하는 경우와 같아진다.

따라서 memberRepository.find() 메소드에 @Transactional 처리가 되어있지만 이미 트랜잭션이 있기 때문에 범위가 getTeamFromMember() 메소드에 트랜잭션으로 합쳐진다.

// 멤버 엔티티
@Entity
public class Member {
	...  // 생략
    
    // 지연 로딩 설정
    @ManyToOne(fetch = FetchType.LAZY)  
	private Team team;
}

// memberService의 어느 메소드
@Transactional
public Team getTeamFromMember(int id) {
	Member member = memberRepository.find(id);
	Team team = member.getTeam(); // 이제 OK!

	return team;
}

이제 getTeamFromMember() 메소드 안에서 member는 영속 상태가 유지되고 있기 때문에 프록시를 사용한 지연 로딩이 성공적으로 실행된다.

profile
Boys, be ambitious!

0개의 댓글