🍃이 글은 개발하다가 만난 오류 중, JPA와 관련된 오류들을 정리한 글입니다.🍃
LazyInitializationException은 Hibernate와 같은 개체 관계형 매핑(ORM) 프레임워크에서 아직 초기화되지 않은 느리게 로드된 엔터티 또는 컬렉션에 액세스하려고 시도할 때 발생하는 예외이다.
데이터 조회 시, 데이터베이스 리소스를 절약하기 위해 다음과 같이 FetchType을 LAZY로 설정하여 실제 데이터를 사용할 때까지 초기화가 되지 않는다.
이런 상황에 트랜잭션을 지원하지 않는 Controller 계층에서 초기화되지 않은 컬렉션에 접근하려 해서 생긴 문제였다.
//엔티티 클래스의 코드
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "noticeBoard_id", foreignKey = @ForeignKey(name = "fk_notice_board_upload_file_to_notice_board"))
private NoticeBoard noticeBoard;
//서비스 계층 클래스의 코드
@Transactional
public NoticeBoard findDetailNoticeBoard(Long id) {
NoticeBoard noticeBoard = noticeBoardRepository.findById(id).get();
noticeBoard.updateViews();
initNoticeBoard(noticeBoard);
return noticeBoard;
}
private static void initNoticeBoard(NoticeBoard noticeBoard) {
List<NoticeBoardUploadFile> uploadFiles = noticeBoard.getUploadFiles();
if (uploadFiles.size() != 0) {
noticeBoard.getUploadFiles().get(0);
}
}
해당 오류는 컬렉션이 엔티티에 의해 참조되지 않는다. 즉, 하이버네이트가 컬렉션을 관리하지 않아서 발생한 오류이다.
우선 부모 엔티티와 자식 엔티티의 생명주기를 맞추기 위해서 @OneToMany(mappedBy = "noticeBoard", cascade = CascadeType.ALL, orphanRemoval = true) 설정해주었다.
이 상황에, 클라이언트에서 부모 엔티티의 수정 요청이 들어왔을 때 기존 컬렉션을 새롭게 만들어진 컬렉션으로 set()를 통해 변경한다.
이렇게 하이버네이트에 의해 관리되던 기존 컬렉션을 새로운 컬렉션으로 변경하려고 해서 생긴 문제였다.
//엔티티 클래스의 코드
@OneToMany(mappedBy = "noticeBoard", cascade = CascadeType.ALL, orphanRemoval = true)
private List<NoticeBoardUploadFile> uploadFiles = new ArrayList<>();
//기존 서비스 계층 클래스의 코드
@Transactional
public void updateNoticeBoard(Long id, NoticeBoardRequestDto noticeBoardRequestDto, List<NoticeBoardUploadFile> storeFiles) {
NoticeBoard noticeBoard = noticeBoardRepository.findById(id).get();
noticeBoard.updateTitle(noticeBoardRequestDto.getTitle());
noticeBoard.updateText(noticeBoardRequestDto.getText());
noticeBoard.setUploadFiles(storeFIles);
}
//엔티티 클래스의 코드
public void updateUploadFiles(List<NoticeBoardUploadFile> uploadFiles) {
this.uploadFiles.clear(); //기존 컬렉션 안에 있던 데이터만 삭제
this.uploadFiles.addAll(uploadFiles); //기존 컬렉션에 파라미터로 받은 컬렉션 안의 요소들을 추가함
uploadFiles.stream().forEach(e -> e.designateNoticeBoard(this));
}
//변경된 서비스 계층 클래스의 코드
@Transactional
public void updateNoticeBoard(Long id, NoticeBoardRequestDto noticeBoardRequestDto, List<NoticeBoardUploadFile> storeFiles) {
NoticeBoard noticeBoard = noticeBoardRepository.findById(id).get();
noticeBoard.updateTitle(noticeBoardRequestDto.getTitle());
noticeBoard.updateText(noticeBoardRequestDto.getText());
if (noticeBoardRequestDto.getFiles() != null) {
noticeBoard.updateUploadFiles(storeFiles);
}
}
@Component
@EnableScheduling
@RequiredArgsConstructor
public class ScheduledTask {
private final ScheduleBoardService scheduleBoardService;
@Scheduled(cron = "0 0 0 * * ?") // logic to be executed 'every day at 00:00'
public void updateSchedulesState() {
List<Schedules> schedules = sheduleBoardService.findScheduleList();
scheduleBoardService.autoUpdateSchedulesState(schedules);
}
}
@Service
@Transactional(readOnly = true)
@RequiredArgsConstructor
public class ScheduleBoardService {
@Transactional
public void autoUpdateSchedulesState(List<Schedules> findScheduleList) {
findScheduleList.stream().forEach(e->e.updateScheduleState());
}
}
@Component
@EnableScheduling
@RequiredArgsConstructor
public class ScheduledTask {
private final ScheduleBoardService scheduleBoardService;
@Scheduled(cron = "0 0 0 * * ?") // logic to be executed 'every day at 00:00'
public void updateSchedulesState() {
scheduleBoardService.autoUpdateSchedulesState();
}
}
@Service
@Transactional(readOnly = true)
@RequiredArgsConstructor
public class ScheduleBoardService {
@Transactional
public void autoUpdateSchedulesState() {
List<Schedules> findScheduleList = scheduleRepository.findAll();
findScheduleList.stream().forEach(e->e.updateScheduleState());
}
}