Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -22,38 +23,42 @@ public class SaveMusicService {
private final MusicAsyncService musicAsyncService;

public Music saveMusic(Music music) {
Optional<Music> 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());
}
}
);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ public enum ErrorCode {
// ========================
// 409 Conflict
// ========================
MUSIC_CONSISTENCY_ERROR(409_001, HttpStatus.CONFLICT, "음악 정보 처리 중 서버 오류가 발생했습니다."),


// ========================
Expand Down