티스토리 뷰
프로젝트 하다가 만났던 LazyInitializationException
JPA 공부 후, 발생 원인과 해결법을 한번 정리해보고자 합니다😃
발생 원인
- 서비스 단에 @Transactional을 붙여둔 상태에서 메서드가 종료되면 Hibernate의 Session도 함께 종료되어 영속성 컨텍스트에서 사라집니다.
- 엔티티 객체의 속성 중, FetchType이 LAZY로 설정된 필드가 있었다면, 해당 트랜잭션 내에서, 프록시 객체로 채워진 상태일 것입니다. 이때, get을 통해 데이터를 가져오게 되면 쿼리를 통해 해당 객체를 실제 데이터로 채우게 됩니다.
- 하지만 서비스 단에서 컨트롤러 단으로 코드 흐름이 넘어간 경우, 트랜잭션이 종료되어 영속성 컨텍스트에서 사라진 상태이므로 프록시 객체를 실제 객체로 채우지 못해 LazyInitializationException이 발생하게 되는 것입니다.
코드로 살펴보기!
일반적인 REST API의 실행 흐름은 @Controller, @Service, @Repository 순서가 됩니다.
User
- OneToMany는 FetchType이 LAZY가 디폴트 값!
@Entity(name = "USERS")
public class User extends Auditable {
// ...
@OneToMany(mappedBy = "user", cascade = {CascadeType.PERSIST, CascadeType.REMOVE})
private List<UserHomepage> userHomepages = new ArrayList<>();
@OneToMany(mappedBy = "user", cascade = {CascadeType.PERSIST, CascadeType.REMOVE})
private List<UserKeyword> userKeywords = new ArrayList<>();
}
UserController
@RestController
public class UserController {
// ...
@GetMapping
public ResponseEntity getUser() {
User user = userService.findUser();
UserResponseDto.Response response = userMapper.userToUserResponseDtoResponse(user);
return new ResponseEntity<>(response, HttpStatus.CREATED);
}
}
UserService
@Service
public class UserService {
// ...
@Transactional
public User findUser() {
return findUserByJWT.getLoginUser();
}
}
🚨유저 정보를 가져오는 API 호출 시, LazyInitializationException 발생 확인
org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: com.example.dingle.user.entity.User.userHomepages, could not initialize proxy - no Session
UserService에서 유저 정보를 가져왔으나, Service 단에서 빠져나오면서 트랜잭션이 종료되어 영속성 상태가 끝나게 됩니다. Lazy로 설정된 필드는 프록시 객체가 get 요청 시에 쿼리문을 날려 가져오게 되는데 이 과정을 영속성 상태가 끝난, 컨트롤러에서 수행하고 있어 LazyInitializationException이 발생한 것입니다!
해결법💊
세션을 응답 리턴시점까지 유지시키도록 설정 변경이 가능하지만, 세션의 수명이 길어지기 때문에 권장되지 않는다고 합니다. 그 대신, 서비스 레벨에서 트랜잭션이 종료되는 시점에 리턴 타입으로 엔티티를 DTO로 변환할 수 있습니다.
🤔이렇게 되면 FetchType.EAGER를 사용하는 것과 다를 바 없지 않을까??? 싶지만
API의 응답을 세분화하여 필요한 상황에 맞는 필요한 필드만 각 DTO로 맵핑하여 리턴할 수 있습니다.
추가로 학습해보면 좋을 것
OSIV 패턴
- 엔티티를 트랜잭션이 묶여있지 않은 밖까지 끌고 나가는건 좋지 않다.
- 엔티티는 RDB와 묶여있는 통신하는 객체이므로 트랜잭션 밖 영역으로 빠져나가면 예상치 못한 곳에서 쿼리가 나갈 수 있으므로 트랜잭션을 나올때는 Entity를 dto로 바꾸자!
'백엔드 > SpringBoot' 카테고리의 다른 글
Spring에서 AOP를 구현하는 방법(JDK/CGLib Proxy) (2) | 2024.01.21 |
---|---|
Fetch Join과 비동기로 283배 시간 단축하기! (0) | 2024.01.21 |
@Transactional 어노테이션을 뜯어보자. (+ 테스트 시 주의할 점) (0) | 2023.12.11 |
[SpringBoot] @Repository와 같은 빈이 생성되는 클래스에서 @Value로 주입받은 변수를 생성자에서 써도 될까? (1) | 2023.10.19 |
프레임워크를 사용하는 이유는 무엇일까? (0) | 2023.10.13 |