OneToMany로 연결된 User과 Word 엔티티를 Fetch.LAZY(지연 로딩)으로 설정하였는데, Oauth를 통하여 받은 User객체로 Word List를 불러와 사용하는 과정에서 다음의 에러가 발생했다.
failed to lazily initialize a collection of role
User엔티티
public class Users implements UserDetails {
@Builder.Default
@OneToMany(mappedBy = "users")
private List<Word> words = new ArrayList<>();
...
}
Word 엔티티
public class Word {
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "user_id")
@Setter
private Users users;
다음과 같은 상황에서 에러가 난 코드는 서비스단에서 다음과 같다.
@Transactional(readOnly = true)
public List<WordInfoDto> getWordDtoList(Users users) {
List<Word> wordList=users.getWords();
List<WordInfoDto> wordInfoDtoList=new ArrayList<>();
for(int i=0;i<wordList.size();i++){
wordInfoDtoList.add(WordInfoDto.from(wordList.get(i)));
}
return wordInfoDtoList;
}
여기서 문제가 난 부분은 wordList가 프록시인 상태에서 wordList.size()를 호출하였기 때문인데, 이는 아래의 블로그에서 JPA Lazy Loading Select문 실행시점을 보며 알아낼 수 있었다.
https://velog.io/@byeongju/JPA-Lazy-Loading-%EB%82%B4-%EB%88%88%EC%9C%BC%EB%A1%9C-%ED%99%95%EC%9D%B8%ED%95%98%EA%B8%B0
이를 그림으로 표현하면 다음과 같다.
이제 정확하게 어느 부분에서 오류가 났는지 다시 확인해 보기 위하여 다음과 같이 디버깅을 하였다.
@Transactional(readOnly = true)
public List<WordInfoDto> getWordDtoList(Users users) {
System.out.println("before getWords()-----------------------------------");
List<Word> wordList=users.getWords();
List<WordInfoDto> wordInfoDtoList=new ArrayList<>();
System.out.println("before wordList.size()-----------------------------------");
for(int i=0;i<wordList.size();i++){
System.out.println("before wordList.get(i)");
wordInfoDtoList.add(WordInfoDto.from(wordList.get(i)));
}
return wordInfoDtoList;
}
실행 결과는 다음과 같다.

이를 통하여 user.getWords()는 프록시 객체를 반환하기 때문에 쿼리가 발생하지 않았으며, 프록시 객체.size()를 실행하였기에 오류가 났음을 알 수 있었다!
wordList가 프록시 객체인 상태라 문제가 생기는 것이므로, 쿼리를 발생시켜 초기화 해주면 문제가 해결된다.
따라서 wordList=wordRepositry.findAllBy(user) 로 초기화 작업을 진행하여 주니 해결되었다.
@Transactional(readOnly = true)
public List<WordInfoDto> getWordDtoList(Users users) {
List<Word> wordList=wordRepository.findAllByUsers(users);
List<WordInfoDto> wordInfoDtoList=new ArrayList<>();
for(int i=0;i<wordList.size();i++){
wordInfoDtoList.add(WordInfoDto.from(wordList.get(i)));
}
return wordInfoDtoList;
}
이제 유저 확인(Oauth), 유저를 통한 word들 검색으로 2번의 쿼리로 정상 작동된다.

단어 저장 로직 오류처리
트랜잭션과 영속성 컨텍스트