diff --git a/src/main/java/com/goormthon/backend/firstsori/domain/music/domain/entity/Music.java b/src/main/java/com/goormthon/backend/firstsori/domain/music/domain/entity/Music.java index ed0a0a3..6a2d2e5 100644 --- a/src/main/java/com/goormthon/backend/firstsori/domain/music/domain/entity/Music.java +++ b/src/main/java/com/goormthon/backend/firstsori/domain/music/domain/entity/Music.java @@ -8,7 +8,15 @@ @Entity @Getter @NoArgsConstructor(access = AccessLevel.PROTECTED) -@Table(name = "music") +@Table( + name = "music", + uniqueConstraints = { + @UniqueConstraint( + name = "uk_music_song_artist", + columnNames = {"song_name", "artist"} + ) + } +) public class Music { @Id diff --git a/src/main/java/com/goormthon/backend/firstsori/domain/music/domain/service/SaveMusicService.java b/src/main/java/com/goormthon/backend/firstsori/domain/music/domain/service/SaveMusicService.java index 4eaf53f..b5ec7a4 100644 --- a/src/main/java/com/goormthon/backend/firstsori/domain/music/domain/service/SaveMusicService.java +++ b/src/main/java/com/goormthon/backend/firstsori/domain/music/domain/service/SaveMusicService.java @@ -2,15 +2,16 @@ import com.goormthon.backend.firstsori.domain.music.domain.entity.Music; import com.goormthon.backend.firstsori.domain.music.domain.repository.MusicRepository; +import com.goormthon.backend.firstsori.global.common.exception.ErrorCode; +import com.goormthon.backend.firstsori.global.common.response.CustomException; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.dao.DataIntegrityViolationException; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.support.TransactionSynchronizationAdapter; import org.springframework.transaction.support.TransactionSynchronizationManager; -import java.util.Optional; - @Slf4j @Service @RequiredArgsConstructor @@ -22,38 +23,42 @@ public class SaveMusicService { private final MusicAsyncService musicAsyncService; public Music saveMusic(Music music) { - Optional existsMusic = musicRepository.findBySongNameAndArtist(music.getSongName(), music.getArtist()); Music targetMusic; - if (existsMusic.isEmpty()) { + try { targetMusic = musicRepository.save(music); log.info("DB에 새로운 음악 저장 완료: 곡='{}'", targetMusic.getSongName()); - } else { - targetMusic = existsMusic.get(); - log.info("음악이 이미 존재함: 곡='{}'", targetMusic.getSongName()); + } catch (DataIntegrityViolationException e) { + targetMusic = musicRepository + .findBySongNameAndArtist(music.getSongName(), music.getArtist()) + .orElseThrow(() -> new CustomException(ErrorCode.MUSIC_CONSISTENCY_ERROR)); + ; + log.info("이미 존재하는 음악 사용: 곡='{}'", targetMusic.getSongName()); } - // 2. 트랜잭션 커밋 성공 시 비동기 작업 등록 - // DB 변경사항이 Redis 및 Stream에 반영되기 전에 실패하는 것을 방지 - TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() { - @Override - public void afterCommit() { - log.info("✅ 트랜잭션 커밋 성공. 비동기 이벤트 및 캐시 TTL 연장 작업 시작."); - - // 2.1. 검색 캐시 TTL 연장 (비동기) - musicAsyncService.extendSearchCacheTtl(targetMusic.getSongName()); + registerAfterCommit(targetMusic); - // 2.2. 인기 차트 반영 이벤트 발행 (Stream) - musicEventProducer.publishMusicEvent(targetMusic.getSongName()); - } - }); - - // 3. 결과 반환 (트랜잭션 커밋은 이 메서드 종료 후 Spring에 의해 실행) return targetMusic; } + // 2. 트랜잭션 커밋 성공 시 비동기 작업 등록 + // DB 변경사항이 Redis 및 Stream에 반영되기 전에 실패하는 것을 방지 + private void registerAfterCommit(Music music) { + TransactionSynchronizationManager.registerSynchronization( + new TransactionSynchronizationAdapter() { + @Override + public void afterCommit() { + log.info("✅ 트랜잭션 커밋 성공. 비동기 이벤트 및 캐시 TTL 연장 작업 시작."); + // 2.1. 검색 캐시 TTL 연장 (비동기) + musicAsyncService.extendSearchCacheTtl(music.getSongName()); + // 2.2. 인기 차트 반영 이벤트 발행 (Stream) + musicEventProducer.publishMusicEvent(music.getSongName()); + } + } + ); + } } diff --git a/src/main/java/com/goormthon/backend/firstsori/global/common/exception/ErrorCode.java b/src/main/java/com/goormthon/backend/firstsori/global/common/exception/ErrorCode.java index 3e4736c..2b6a8dd 100644 --- a/src/main/java/com/goormthon/backend/firstsori/global/common/exception/ErrorCode.java +++ b/src/main/java/com/goormthon/backend/firstsori/global/common/exception/ErrorCode.java @@ -67,6 +67,7 @@ public enum ErrorCode { // ======================== // 409 Conflict // ======================== + MUSIC_CONSISTENCY_ERROR(409_001, HttpStatus.CONFLICT, "음악 정보 처리 중 서버 오류가 발생했습니다."), // ========================