개요
프로젝트 기간에 발생했던 오류다. 시간에 쫓겨 정확한 원인을 파악하지 못한 채로 해결했었는데, JPA 공부를 하다가 깨달은 부분이 있어서 되짚어보기로 했다
북마크를 조회할 때, LazyInitializationException이 발생했다.
개발하신 분 환경에서는 오류가 발생하지 않았지만, 운영 환경과 내 로컬 환경에서는 동일한 오류가 발생했다.
이로 인해 개발자 분께서 직접 디버깅이 어려운 상황이었고, 내가 수정해야 하는 상황이었다.
비슷한 맥락으로 이런 오류도 볼 수 있다.
지연 로딩과 프록시
지연 로딩이란, 연관관계를 가진 엔티티를 즉시 불러오지 않고 실제로 사용할 때 까지 로딩을 미루는 전략이다.
이렇게 FetchType.LAZY로 설정하면 Bookmark 객체를 조회할 때 user는 즉시 DB에서 불러오지 않고,
bookmark.getUser()이나 getJobOpening()이 호출되는 시점에 DB쿼리를 날려서 가져오게 된다.
Hibernate는 지연 로딩을 구현하기 위해 프록시 객체를 만든다.
즉, 실제 User 객체가 아니라 User를 흉내내는 가짜 객체가 들어와있다.
이 프록시는 실제 필드가 로드되기 전까진 내부에 데이터가 없다.
이런 프록시 객체를 영속성 컨텍스트가 닫힌 시점에, 또는 트랜잭션 외부에서 건드리거나,
직렬화하려 하면 LazyInitializationException같은 오류가 발생한다.
해결
해당 오류의 원인은 Bookmark 객체를 Redis에 저장할 때, Entity 객체를 그대로 저장하고 조회하려 했기 때문이었다.
User, JobOpening같은 연관 객체들이 지연로딩 상태였고, 직렬화 등의 과정에서 Hibernate 프록시가 그대로 남아 있어 예외가 발생했다.
fetch join이나 entitygraph로 연관 엔티티를 미리 로딩해서 문제를 피하는 방법도 있는데, 이 방법은 일시적인 회피책이고 구조적인 해결책이 아니다. 연관관계가 복잡해질수록 fetch join의 한계나 쿼리 복잡도도 함께 증가하게 된다.
그래서 Bookmark 엔티티를 DTO로 완전히 변환한 뒤 Redis에 저장하는 방향을 택했다. 이렇게 하면 직렬화 관련 오류를 방지할 수 있을 뿐 아니라, 캐시에는 필요한 데이터만 저장되기 때문에 구조적으로도 훨씬 깔끔하다.
'팀 프로젝트 > cheerha.project' 카테고리의 다른 글
AOP(트랜잭션)와 try-catch (0) | 2025.03.27 |
---|---|
트러블슈팅: 인증과 인가 (0) | 2025.03.19 |
Spring Security와 JWT (0) | 2025.03.18 |