![[인프런 워밍업 클럽 3기 - 백엔드 프로젝트] 3주차 발자국](https://cdn.inflearn.com/public/files/blogs/392a9001-7965-4d5a-9367-090283e77bd5/인프런워밍업.png)
[인프런 워밍업 클럽 3기 - 백엔드 프로젝트] 3주차 발자국
워밍업 클럽도 벌써 3주차가 되었다.
✅ 강의
3주차에는 어드민 개발을 마무리 짓는 내용이었다.
공통 Exception과 Advice를 개발하고 적용했다.
각 CRUD 기능을 개발하였다.
나머지 화면단은 템플릿 라이센스 이슈로 조금의 문제가 있어서 이번에는 넘어가게되었다.
✅ 미션
3주차는 [미션5] 삽입, 수정, 삭제 REST API 만들기가 메인이었다.
미션의 조건은 플레이리스트 삽입, 수정, 삭제로 잡았지만,
초기에 계획했던 API는 아래와 같아서 미션3에서 완성했던 음원 목록 조회쪽 제외 나머지 기능을 완성하는 것을 목표로 했다.
구현하면서 조금 헤맸던 포인트는 두가지 정도가 있었다. 그 포인트들에 대해 기록하면서 진행했는데 발자국에도 조금 소개해보려고 한다. (강의에서 나왔던 내용이기도 하다..😅)
플레이리스트 생성 시 연관관계 생기지 않는 문제
fun createPlaylist(request: CreatePlaylistRequest): Playlist {
val host = memberRepository.findById(request.hostId)
.orElseThrow { IllegalArgumentException("해당 멤버가 존재하지 않습니다.") }
val playlist = Playlist(
member = host,
title = request.title,
playlistImage = request.playlistImage
)
return playlistRepository.save(playlist)
}
처음에 만든 플레이리스트 생성 코드이다. Playlist
를 생성할 때, TrackPlaylist
데이터를 생성해주는 로직 자체가 없다.
JPA의 연관관계 설정은 엔티티 간 관계만 정의할 뿐, 연관된 데이터를 자동으로 생성해 주진 않는다. 반드시 직접 TrackPlaylist
객체를 생성하고 저장해 줘야 한다.
@Transactional
fun createPlaylist(request: CreatePlaylistRequest): Playlist {
val host = memberRepository.findById(request.hostId)
.orElseThrow { IllegalArgumentException("해당 멤버가 존재하지 않습니다.") }
val playlist = Playlist(
member = host,
title = request.title,
playlistImage = request.playlistImage
)
playlistRepository.save(playlist)
request.trackIds.forEach { trackId ->
val track = trackRepository.findById(trackId)
.orElseThrow { throw BadRequestCustomException("해당 음원 트랙이 존재하지 않습니다.") }
val trackPlaylist = TrackPlaylist(
track = track,
playlist = playlist
)
playlist.addTrackPlaylist(trackPlaylist)
}
return playlist
}
수정된 코드이다. 추가로 Playlist 엔티티에도 설정이 필요하다.
CascadeType.ALL
과orphanRemoval=true
옵션을 추가해줬으므로, 편의 메서드를 호출하고 Playlist를 저장할 때 TrackPlaylist까지 자동으로 영속화하도록 했다.orphanRemoval
default는 false이다.부모 엔티티에서 자식 엔티티가 제거된 경우, 이 자식 엔티티를 더 이상 참조하는 곳이 없어지면 JPA가 자동으로 자식 엔티티를 DB에서 삭제해주는 옵션이다.
즉, 부모와의 연결이 끊어지면 자식 데이터가 자동으로 삭제되는 것이다.
플레이리스트 상세 조회시 오류
playlist를 id기반으로 가져오려고 했는데 오류가 발생했다. JPA lazy 로딩 이슈였다.
현재
Playlist
엔티티에서host(Member)
와trackPlaylists
필드가 모두 FetchType.LAZY로 설정되어 있다.이로 인해, 영속성 컨텍스트가 종료된 이후에 프록시 객체를 접근하려고 하면 오류가 발생한다.
@ManyToOne(targetEntity = Member::class, fetch = FetchType.LAZY) var host: Member = member @OneToMany(mappedBy = "playlist", fetch = FetchType.LAZY) var trackPlaylists: MutableList<TrackPlaylist> = mutableListOf()
Fetch Join을 사용해야한다.
PlaylistRepository
에 다음과 같은 메서드를 추가하여 Fetch Join으로 데이터를 즉시 로딩하게 한다.interface PlaylistRepository : JpaRepository<Playlist, Long> { @Query("select p from Playlist p join fetch p.host left join fetch p.trackPlaylists tp left join fetch tp.track where p.id = :id") fun findByIdWithDetails(@Param("id") id: Long): Playlist? }
fun getPlaylist(playlistId: Long): Playlist { return playlistRepository.findByIdWithDetails(playlistId) ?: throw BadRequestCustomException("해당 플레이리스트가 존재하지 않습니다.") }
🍀 마무리
미니 프로젝트도 거의 마무리가 되었다. 사실 기능 구현보다 테스트 코드 작성이 생각보다 훨씬 어렵게 느껴진다. 익숙치 않아서 그런가....뭔가 따로 테스트 코드에 대해서 학습이 필요할 것 같다. 그래도 미니 프로젝트를 통해 어떤 방식으로 구현하고 테스트를 작성해야할지 아주 조금...감이 오기 시작했다. 이제 다음주면 마지막 주차이다. 마지막 주도 잘 마무리해서 유종의 미를 거뒀으면 좋겠다. 화이팅!!!
댓글을 작성해보세요.