이번에는 지난 시간에 만들었던 `아티스트 정보 검색 API`로 응답받은 데이터를 데이터베이스에 저장하는 기능을 구현해보려고 한다.
사전 설정
이번 프로젝트에서는 PostgreSQL을 사용해보기로 해서 설치해 두었다. PostgreSQL에 데이터를 저장하기 위해서는 다음과 같이 설정을 추가해야 한다.
build.gradle
dependencies {
...
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.postgresql:postgresql:42.7.3'
}
application.properties
# PostgreSQL
spring.datasource.url=${DB_URL}
spring.datasource.username=${DB_USERNAME}
spring.datasource.password=${DB_PASSWORD}
spring.datasource.driver-class-name=org.postgresql.Driver
# JPA
spring.jpa.hibernate.ddl-auto=none
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect
Domain
Artist
- `artistId`: Spotify API 내에서 사용하는 고유 ID (String)
- `artistName`: 아티스트 이름
- `profileImg`: 아티스트 대표 이미지
@Entity
@Getter
@NoArgsConstructor
@Table(name = "artist")
public class Artist {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "artist_id")
private String artistId;
@Column(name = "artist_name")
private String artistName;
@Column(name = "profile_img")
private String profileImg;
@Builder
public Artist(String artistId, String artistName, String profileImg) {
this.artistId = artistId;
this.artistName = artistName;
this.profileImg = profileImg;
}
}
Track
- `trackName`: 곡 제목
- `albumName`: 앨범 제목
- `previewUri`: 트랙 미리 듣기 URL
@Entity
@Getter
@AllArgsConstructor
@Table(name = "track")
public class Track {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "track_name")
private String trackName;
@Column(name = "album_name")
private String albumName;
@Column(name = "preview_uri")
private String previewUri;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "artist_id")
@JsonIgnore
private Artist artist;
}
Repository
ArtistRepository
public interface ArtistRepository extends JpaRepository<Artist, Long> {
Artist findByArtistId(String artistId);
Optional<Artist> findByArtistName(String artistName);
}
TrackRepository
public interface TrackRepository extends JpaRepository<Track, Long> {
List<Track> findByArtist_ArtistName(String artistName);
}
이 `findByArtist_ArtistName` 메서드는 `artistName`에 해당하는 모든 `Track` 객체를 반환하는 메서드로, 내부적으로 다음과 같은 SQL 쿼리를 실행한다.
SELECT * FROM track t
JOIN artist a ON t.artist_id = a.id
WHERE a.artist_name = :artistName
Service
ArtistService
@Transactional(readOnly = true)
@Service
@RequiredArgsConstructor
public class ArtistService {
private final ArtistRepository artistRepository;
private final TrackRepository trackRepository;
private final SpotifyService spotifyService;
@Transactional
public Artist saveArtistFromSpotify(String artistName) {
List<SearchResponseDto> searchResults = spotifyService.search(artistName);
// 첫 번째 결과가 없으면 예외 발생
SearchResponseDto artistInfo = searchResults.stream()
.findFirst()
.orElseThrow(() -> new IllegalArgumentException("No artist found with name: " + artistName));
// 아티스트 저장
Artist artist = artistRepository.findByArtistId(artistInfo.getArtistId());
if (artist == null) {
artist = Artist.builder()
.artistId(artistInfo.getArtistId())
.artistName(artistInfo.getArtistName())
.profileImg(artistInfo.getImageUrl())
.build();
artist = artistRepository.save(artist);
saveTopTracks(artistInfo.getTracks(), artist);
}
return artist;
}
private void saveTopTracks(List<TrackInfo> tracks, Artist artist) {
for (TrackInfo trackInfo : tracks) {
Track track = new Track(trackInfo.getTrackName(), trackInfo.getAlbumName(), trackInfo.getPreviewUri(), artist);
trackRepository.save(track);
}
}
public Artist getArtistById(Long id) {
return artistRepository.findById(id)
.orElseThrow(() -> new IllegalArgumentException("해당하는 가수가 없습니다."));
}
public Artist getArtistByArtistName(String artistName) {
return artistRepository.findByArtistName(artistName)
.orElseThrow(() -> new IllegalArgumentException("해당하는 가수가 없습니다."));
}
@Transactional
public void deleteArtist(Long id) {
artistRepository.deleteById(id);
}
}
TrackService
@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class TrackService {
private final TrackRepository trackRepository;
public List<Track> getTracksByArtist(String artistName) {
return trackRepository.findByArtist_ArtistName(artistName);
}
@Transactional
public void deleteTrack(Long trackId) {
trackRepository.deleteById(trackId);
}
}
Controller
ArtistController
@RestController
@RequiredArgsConstructor
@RequestMapping("/api/artists")
public class ArtistController {
private final ArtistService artistService;
@GetMapping("/spotify")
public ResponseEntity<Artist> saveArtistInfo(@RequestParam("artistName") String artistName) {
return ResponseEntity.ok(artistService.saveArtistFromSpotify(artistName));
}
@GetMapping("/search")
public ResponseEntity<Artist> getArtistByArtistName(@RequestParam("artistName") String artistName) {
return ResponseEntity.ok(artistService.getArtistByArtistName(artistName));
}
@GetMapping("/{id}")
public ResponseEntity<Artist> getArtistById(@PathVariable("id") Long id) {
return ResponseEntity.ok(artistService.getArtistById(id));
}
@DeleteMapping("/{id}")
public ResponseEntity<Void> deleteArtistById(@PathVariable("id") Long id) {
artistService.deleteArtist(id);
return ResponseEntity.noContent().build();
}
}
TrackController
@RestController
@RequiredArgsConstructor
@RequestMapping("/api/tracks")
public class TrackController {
private final TrackService trackService;
@GetMapping
public ResponseEntity<List<Track>> getTracksByArtist(@RequestParam("artistName") String artistName) {
List<Track> tracks = trackService.getTracksByArtist(artistName);
return ResponseEntity.ok(tracks);
}
@DeleteMapping("/{trackId}")
public ResponseEntity<Void> deleteTrack(@PathVariable Long trackId) {
trackService.deleteTrack(trackId);
return ResponseEntity.noContent().build();
}
}
정리 ✏️
지난 시간에 구현했던 API를 호출해서 데이터를 저장하는 코드와 `아티스트명`으로 데이터 조회 및 데이터 삭제 등 기본적인 기능을 구현해 보았다. 하지만 현재 `아티스트명`으로 데이터 조회 시, 데이터베이스에 저장된 아티스트 이름과 정확히 일치하는 이름만 검색이 가능하다는 문제가 있다.
즉, 현재 `artist` 테이블에 저장되어 있는 아티스트명이 `DAY6`일 경우, 다음과 같은 이름으로 검색했을 때, 모두 검색이 되지 않는다.
- day6
- Day6
- 데이식스
- 데식
즉, 대소문자 구분 없이 검색이 가능해야 하며, 한글명과 별칭으로도 검색이 가능해야 한다.
다음 시간에는 이러한 요구사항을 만족하고, 예외 처리도 추가해보려고 한다.
'Project > hilarious' 카테고리의 다른 글
[SpotifyAPI][1] 아티스트 정보 검색 API 만들기 (0) | 2024.11.01 |
---|---|
[SpotifyAPI][0] App 생성 및 간단한 API 테스트 (0) | 2024.10.31 |