주뇽's 저장소
[Troubleshooting] Spring Boot와 JPA에서 유저의 총 좋아요 개수를 조회할 때 발생한 LazyInitializationException 해결 본문
[Troubleshooting] Spring Boot와 JPA에서 유저의 총 좋아요 개수를 조회할 때 발생한 LazyInitializationException 해결
뎁쭌 2024. 5. 3. 21:00 최근에 Spring Boot와 JPA를 사용하여 게시글 프로젝트를 진행하던 중, 유저가 받은 총 좋아요 개수를 조회하는 과정에서 LazyInitializationException이 발생하는 문제를 겪었다. 이 글에서는 해당 문제의 원인과 해결 방법에 대해 자세히 설명한다.
문제 상황:
- UserAccount 엔티티와 Post 엔티티가 일대다 관계로 매핑되어 있다
- UserAccount 엔티티에서 posts 필드가 지연 로딩(Lazy Loading)으로 설정되어 있다
- 유저가 받은 총 좋아요 개수를 조회하기 위해 UserAccount의 getTotalLikeCount() 메서드에서 posts 컬렉션에 접근하려고 한다
- getTotalLikeCount() 메서드 내부에서 LazyInitializationException 발생한다
문제 원인:
LazyInitializationException은 지연 로딩으로 설정된 연관 관계를 불러오려고 할 때 세션이 이미 종료된 경우 발생한다. getTotalLikeCount() 메서드에서 posts 컬렉션에 접근하려고 할 때, 세션이 종료되어 있어 지연 로딩이 불가능하기 때문에 예외가 발생한 것이다.
해결 방법 1: @Transactional 어노테이션 사용한다
처음에는 @Transactional 어노테이션을 사용하여 문제를 해결하려고 했다. @Transactional 어노테이션을 getTotalLikeCount() 메서드가 호출되는 서비스 메서드에 추가하면 해당 메서드가 트랜잭션 내에서 실행되므로 지연 로딩이 가능해진다.
@Service
public class MyUserService {
@Transactional
public int getLike(Long userId) {
UserAccount userAccount = userRepository.findById(userId)
.orElseThrow(() -> new UserNotFoundException("User not found with id: " + userId));
return userAccount.getTotalLikeCount();
}
}
그러나 이 방법으로 문제가 완전히 해결되지 않았고, 여전히 LazyInitializationException이 발생했다.
해결 방법 2: @EntityGraph 어노테이션 사용한다
다음으로 시도한 방법은 @EntityGraph 어노테이션을 사용하는 것이었다. @EntityGraph 어노테이션을 사용하면 지연 로딩으로 설정된 연관 관계를 즉시 로딩(Eager Loading)으로 변경할 수 있다.
1. UserRepository에 @EntityGraph 어노테이션을 사용하여 posts 연관 관계를 즉시 로딩으로 변경한다
@Repository
public interface UserRepository extends JpaRepository<UserAccount, Long> {
@EntityGraph(attributePaths = {"posts"})
Optional<UserAccount> findWithPostsById(Long id);
}
2. 서비스 메서드에서 findWithPostsById 메서드를 사용하여 UserAccount를 조회한다
@Service
public class MyUserService {
public int getLike(Long userId) {
UserAccount userAccount = userRepository.findWithPostsById(userId)
.orElseThrow(() -> new UserNotFoundException("User not found with id: " + userId));
return userAccount.getTotalLikeCount();
}
}
이 방법을 사용하니 LazyInitializationException이 발생하지 않고 정상적으로 총 좋아요 개수를 조회할 수 있었다. @EntityGraph 어노테이션을 사용하여 posts 연관 관계를 즉시 로딩으로 변경하고, findWithPostsById 메서드를 통해 UserAccount를 조회했기 때문에 세션이 종료되어도 문제없이 posts 컬렉션에 접근할 수 있게 되었다.
해결 방법 3: DTO를 사용하여 필요한 데이터만 조회한다
마지막으로 고려한 방법은 DTO(Data Transfer Object)를 사용하여 필요한 데이터만 조회하는 것이다. 도메인 엔티티 대신 DTO를 사용하면 필요한 데이터만 선택적으로 가져올 수 있어 성능 최적화에 도움이 된다.
1. UserAccountDTO 클래스 생성한다
public class UserAccountDTO {
private Long id;
private String nickname;
private int totalLikeCount;
// Constructor, Getters, Setters
}
2. UserRepository에 JPQL 쿼리를 사용하여 필요한 데이터만 조회하고 UserAccountDTO로 매핑한다
@Repository
public interface UserRepository extends JpaRepository<UserAccount, Long> {
@Query("SELECT new cohttp://m.example.dto.UserAccountDTO(u.id, u.nickname, SUM(p.likeCount)) " +
"FROM UserAccount u LEFT JOIN u.posts p " +
"WHERE u.id = :userId " +
"GROUP BY u.id, u.nickname")
UserAccountDTO findUserAccountDTOById(@Param("userId") Long userId);
}
3. 서비스 메서드에서 UserAccountDTO를 사용하여 필요한 데이터 조회한다
@Service
public class MyUserService {
public UserAccountDTO getLike(Long userId) {
return userRepository.findUserAccountDTOById(userId);
}
}
DTO를 사용하는 방법은 지연 로딩 문제를 근본적으로 해결하진 않지만, 필요한 데이터만 선택적으로 조회할 수 있어 성능 최적화에 도움이 된다. 또한 도메인 엔티티와 프레젠테이션 계층을 분리하는 장점도 있다.
마무리:
Spring Boot와 JPA에서 유저의 총 좋아요 개수를 조회할 때 발생한 LazyInitializationException을 해결하는 방법에 대해 알아보았다. @Transactional 어노테이션을 사용하는 것만으로는 문제를 완전히 해결하기 어려울 수 있으며, @EntityGraph 어노테이션을 사용하거나 DTO를 활용하는 것이 효과적인 해결책이 될 수 있다.
'웹개발 > SpringBoot' 카테고리의 다른 글
[Troubleshooting] Spring Boot와 JPA에서 캐시로 인해 발생한 데이터 불일치 문제 해결하기 (2) | 2024.05.14 |
---|---|
[Spring Security] @AuthenticationPrincipal를 이용하여 로그인한 사용자 알아내기 (0) | 2024.04.23 |
[Spring Boot] 실시간 채팅 기능을 위한 WebSocket, SockJS, STOMP (2) | 2024.02.29 |