Skip to content

Commit 3605f0c

Browse files
committed
2 parents d64eb40 + e0f49df commit 3605f0c

36 files changed

Lines changed: 2219 additions & 1176 deletions

src/main/java/com/back/web7_9_codecrete_be/domain/artists/controller/ArtistsController.java

Lines changed: 1 addition & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -135,28 +135,7 @@ public RsData<LikeArtistResponse> isLiked(
135135
return RsData.success("해당 아티스트 찜 여부 조회 성공", artistService.findArtistLikeByUserId(artist, user));
136136
}
137137

138-
@Operation(summary = "아티스트 인기 순위(구현 전)", description = "Spotify 인기도를 바탕으로 아티스트 인기 순위 랭킹을 제공합니다.")
139-
@GetMapping("/ranking")
140-
public void artistRanking() {}
141-
142-
@Operation(summary = "장르 기반 아티스트 추천(구현 전)", description = "찜한 장르를 기반으로 아티스트 추천 리스트를 제공합니다.")
143-
@GetMapping("/recommendation/{genreId}")
144-
public void recommendArtist(
145-
@PathVariable Long genreId
146-
) {}
147-
148-
@Operation(summary = "공연 셋리스트 생성(구현 전)", description = "사용자가 공연 셋리스트를 생성합니다.")
149-
@PostMapping("/setlist/{concertId}/{artistId}")
150-
public void makeSetlist(
151-
@PathVariable Long concertId,
152-
@PathVariable Long artistId
153-
) {}
154138

155-
@Operation(summary = "공연 셋리스트 조회(구현 전)", description = "다른 사용자들이 생성한 셋리스트를 조회합니다.")
156-
@GetMapping("/setlist/{concertId}/{artistId}")
157-
public void getSetlist(
158-
@PathVariable Long concertId,
159-
@PathVariable Long artistId
160-
) {}
139+
161140
}
162141

src/main/java/com/back/web7_9_codecrete_be/domain/artists/controller/GenreController.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11
package com.back.web7_9_codecrete_be.domain.artists.controller;
22

3+
import com.back.web7_9_codecrete_be.domain.artists.dto.response.GenreArtistsResponse;
34
import com.back.web7_9_codecrete_be.domain.artists.dto.response.GenreResponse;
5+
import com.back.web7_9_codecrete_be.domain.artists.service.ArtistService;
46
import com.back.web7_9_codecrete_be.domain.artists.service.GenreService;
57
import com.back.web7_9_codecrete_be.global.rsData.RsData;
68
import io.swagger.v3.oas.annotations.Operation;
79
import io.swagger.v3.oas.annotations.tags.Tag;
810
import lombok.RequiredArgsConstructor;
911
import org.springframework.web.bind.annotation.GetMapping;
12+
import org.springframework.web.bind.annotation.PathVariable;
1013
import org.springframework.web.bind.annotation.RequestMapping;
1114
import org.springframework.web.bind.annotation.RestController;
1215

@@ -19,10 +22,19 @@
1922
public class GenreController {
2023

2124
private final GenreService genreService;
25+
private final ArtistService artistService;
2226

2327
@Operation(summary = "전체 장르 목록", description = "DB에 저장되어있는 전체 장르 목록을 반환합니다.")
2428
@GetMapping()
2529
public RsData<List<GenreResponse>> genreList() {
2630
return RsData.success("전체 장르 조회 성공", genreService.genreList());
2731
}
32+
33+
@Operation(summary = "장르별 아티스트 목록", description = "각 장르에 해당하는 아티스트 목록을 반환합니다.")
34+
@GetMapping("/{genreId}")
35+
public RsData<List<GenreArtistsResponse>> recommendArtist(
36+
@PathVariable Long genreId
37+
) {
38+
return RsData.success("해당 장르 아티스트 목록 조회 성공", artistService.findArtistsByGenreId(genreId));
39+
}
2840
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package com.back.web7_9_codecrete_be.domain.artists.dto.response;
2+
3+
import com.back.web7_9_codecrete_be.domain.artists.entity.Artist;
4+
import com.back.web7_9_codecrete_be.domain.artists.entity.Genre;
5+
import io.swagger.v3.oas.annotations.media.Schema;
6+
7+
public record GenreArtistsResponse(
8+
@Schema(description = "아티스트 id 입니다.")
9+
Long id,
10+
11+
@Schema(description = "아티스트 이름입니다.")
12+
String artistName,
13+
14+
@Schema(description = "한국어 기준 아티스트 이름입니다.")
15+
String nameKo,
16+
17+
@Schema(description = "아티스트 사진 URL 입니다.")
18+
String imageUrl,
19+
20+
@Schema(description = "아티스트의 Spotify id 입니다.")
21+
String spotifyArtistId
22+
) {
23+
public static GenreArtistsResponse from(Artist artist) {
24+
return new GenreArtistsResponse(
25+
artist.getId(),
26+
artist.getArtistName(),
27+
artist.getNameKo(),
28+
artist.getImageUrl(),
29+
artist.getSpotifyArtistId()
30+
);
31+
}
32+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package com.back.web7_9_codecrete_be.domain.artists.dto.response;
2+
3+
import java.util.List;
4+
5+
// Spotify 아티스트 상세 정보 캐시용 DTO - Redis에 저장하기 위한 데이터 구조
6+
7+
public record SpotifyArtistDetailCache(
8+
// 아티스트 기본 정보
9+
String artistName,
10+
String profileImageUrl,
11+
double popularity,
12+
13+
// Top Tracks (상위 10개)
14+
List<TopTrackResponse> topTracks,
15+
16+
// 앨범 목록 (최대 20개)
17+
List<AlbumResponse> albums,
18+
int totalAlbums
19+
) {
20+
}
21+
Lines changed: 32 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,27 @@
11
package com.back.web7_9_codecrete_be.domain.artists.repository;
22

33
import com.back.web7_9_codecrete_be.domain.artists.entity.Artist;
4+
import com.back.web7_9_codecrete_be.domain.artists.entity.ArtistType;
45
import org.springframework.data.domain.Pageable;
56
import org.springframework.data.domain.Slice;
67
import org.springframework.data.jpa.repository.JpaRepository;
78
import org.springframework.data.jpa.repository.Query;
9+
import org.springframework.data.repository.query.Param;
810
import org.springframework.stereotype.Repository;
911

1012
import java.util.List;
1113

1214
@Repository
1315
public interface ArtistRepository extends JpaRepository<Artist, Long> {
14-
boolean existsBySpotifyArtistId(String spotifyArtistId);
15-
java.util.Optional<Artist> findBySpotifyArtistId(String spotifyArtistId);
16+
17+
// 아티스트 상세 조회용 - artistGenres와 genre를 fetch join하여 N+1 문제 방지
18+
@Query("""
19+
SELECT DISTINCT a FROM Artist a
20+
LEFT JOIN FETCH a.artistGenres ag
21+
LEFT JOIN FETCH ag.genre
22+
WHERE a.id = :id
23+
""")
24+
java.util.Optional<Artist> findByIdWithArtistGenres(@Param("id") Long id);
1625

1726
@Query("SELECT a FROM Artist a WHERE a.nameKo IS NULL ORDER BY a.id ASC")
1827
List<Artist> findByNameKoIsNullOrderByIdAsc(Pageable pageable);
@@ -23,36 +32,40 @@ public interface ArtistRepository extends JpaRepository<Artist, Long> {
2332
boolean existsByArtistName(String artistName);
2433
boolean existsByNameKo(String nameKo);
2534

26-
/**
27-
* 같은 artistGroup인 아티스트들 조회 (관련 아티스트 추천용)
28-
* artistGenres를 fetch join하여 N+1 문제 방지
29-
*/
35+
// 같은 artistGroup인 아티스트들 조회 (관련 아티스트 추천용) - artistGenres를 fetch join하여 N+1 문제 방지
3036
@Query("""
3137
SELECT DISTINCT a FROM Artist a
3238
LEFT JOIN FETCH a.artistGenres ag
3339
LEFT JOIN FETCH ag.genre
3440
WHERE a.artistGroup = :artistGroup AND a.id != :excludeId
3541
ORDER BY a.likeCount DESC, a.id ASC
3642
""")
37-
List<Artist> findByArtistGroupAndIdNot(@org.springframework.data.repository.query.Param("artistGroup") String artistGroup,
38-
@org.springframework.data.repository.query.Param("excludeId") long excludeId,
43+
List<Artist> findByArtistGroupAndIdNot(@Param("artistGroup") String artistGroup,
44+
@Param("excludeId") long excludeId,
3945
Pageable pageable);
4046

41-
/**
42-
* 같은 genre인 아티스트들 조회 (관련 아티스트 추천용)
43-
* artistGenres와 genre를 fetch join하여 N+1 문제 방지
44-
*/
47+
// 같은 genre인 아티스트들 조회 (관련 아티스트 추천용) - artistGenres와 genre를 fetch join하여 N+1 문제 방지
4548
@Query("""
4649
SELECT DISTINCT a FROM Artist a
4750
JOIN FETCH a.artistGenres ag
4851
JOIN FETCH ag.genre
4952
WHERE ag.genre.id = :genreId AND a.id != :excludeId
5053
ORDER BY a.likeCount DESC, a.id ASC
5154
""")
52-
List<Artist> findByGenreIdAndIdNot(@org.springframework.data.repository.query.Param("genreId") Long genreId,
53-
@org.springframework.data.repository.query.Param("excludeId") long excludeId,
55+
List<Artist> findByGenreIdAndIdNot(@Param("genreId") Long genreId,
56+
@Param("excludeId") long excludeId,
5457
Pageable pageable);
5558

59+
// 장르별 아티스트 목록 조회 - artistGenres와 genre를 fetch join하여 N+1 문제 방지
60+
@Query("""
61+
SELECT DISTINCT a FROM Artist a
62+
JOIN FETCH a.artistGenres ag
63+
JOIN FETCH ag.genre g
64+
WHERE g.id = :genreId
65+
ORDER BY a.likeCount DESC, a.id ASC
66+
""")
67+
List<Artist> findArtistsByGenreId(@Param("genreId") Long genreId);
68+
5669
List<Artist> findAllByArtistNameContainingIgnoreCaseOrNameKoContainingIgnoreCase(String artistName1, String artistName2);
5770

5871
Slice<Artist> findAllBy(Pageable pageable);
@@ -74,24 +87,21 @@ List<Artist> findByGenreIdAndIdNot(@org.springframework.data.repository.query.Pa
7487

7588
// 배치 조회: spotifyId 리스트로 존재하는 아티스트의 spotifyId만 반환
7689
@Query("SELECT a.spotifyArtistId FROM Artist a WHERE a.spotifyArtistId IN :spotifyIds")
77-
List<String> findSpotifyIdsBySpotifyIdsIn(@org.springframework.data.repository.query.Param("spotifyIds") List<String> spotifyIds);
90+
List<String> findSpotifyIdsBySpotifyIdsIn(@Param("spotifyIds") List<String> spotifyIds);
7891

7992
// 배치 조회: spotifyId 리스트로 존재하는 아티스트 전체 엔티티 반환 (Bulk 저장용)
8093
@Query("SELECT a FROM Artist a WHERE a.spotifyArtistId IN :spotifyIds")
81-
List<Artist> findBySpotifyArtistIdIn(@org.springframework.data.repository.query.Param("spotifyIds") List<String> spotifyIds);
94+
List<Artist> findBySpotifyArtistIdIn(@Param("spotifyIds") List<String> spotifyIds);
8295

83-
/**
84-
* 같은 artistType인 아티스트들 조회 (관련 아티스트 추천용, fallback)
85-
* artistGenres를 fetch join하여 N+1 문제 방지
86-
*/
96+
// 같은 artistType인 아티스트들 조회 (관련 아티스트 추천용, fallback) - artistGenres를 fetch join하여 N+1 문제 방지
8797
@Query("""
8898
SELECT DISTINCT a FROM Artist a
8999
LEFT JOIN FETCH a.artistGenres ag
90100
LEFT JOIN FETCH ag.genre
91101
WHERE a.artistType = :artistType AND a.id != :excludeId
92102
ORDER BY a.likeCount DESC, a.id ASC
93103
""")
94-
List<Artist> findByArtistTypeAndIdNot(@org.springframework.data.repository.query.Param("artistType") com.back.web7_9_codecrete_be.domain.artists.entity.ArtistType artistType,
95-
@org.springframework.data.repository.query.Param("excludeId") long excludeId,
104+
List<Artist> findByArtistTypeAndIdNot(@Param("artistType") ArtistType artistType,
105+
@Param("excludeId") long excludeId,
96106
Pageable pageable);
97107
}

src/main/java/com/back/web7_9_codecrete_be/domain/artists/service/ArtistService.java

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,14 @@
77
import com.back.web7_9_codecrete_be.domain.artists.repository.ArtistRepository;
88
import com.back.web7_9_codecrete_be.domain.artists.repository.ArtistLikeRepository;
99
import com.back.web7_9_codecrete_be.domain.artists.repository.ConcertArtistRepository;
10-
import com.back.web7_9_codecrete_be.domain.artists.service.spotifyService.SpotifyService;
10+
import com.back.web7_9_codecrete_be.domain.artists.repository.GenreRepository;
11+
import com.back.web7_9_codecrete_be.domain.artists.service.GenreService;
1112
import com.back.web7_9_codecrete_be.domain.concerts.entity.Concert;
1213
import com.back.web7_9_codecrete_be.domain.concerts.repository.ConcertRepository;
1314
import com.back.web7_9_codecrete_be.domain.concerts.service.ConcertService;
1415
import com.back.web7_9_codecrete_be.domain.users.entity.User;
1516
import com.back.web7_9_codecrete_be.global.error.code.ArtistErrorCode;
17+
import com.back.web7_9_codecrete_be.global.error.code.GenreErrorCode;
1618
import com.back.web7_9_codecrete_be.global.error.exception.BusinessException;
1719
import lombok.AccessLevel;
1820
import org.springframework.data.domain.PageRequest;
@@ -32,6 +34,7 @@ public class ArtistService {
3234

3335
private final SpotifyService spotifyService;
3436
private final ArtistRepository artistRepository;
37+
private final GenreRepository genreRepository;
3538
private final GenreService genreService;
3639
private final ArtistLikeRepository artistLikeRepository;
3740
private final ConcertArtistRepository concertArtistRepository;
@@ -46,7 +49,7 @@ public Artist findArtist(Long artistId) {
4649

4750
@Transactional
4851
public int setArtist() {
49-
return spotifyService.seedKoreanArtists300();
52+
return spotifyService.seedKoreanArtists();
5053
}
5154

5255
@Transactional
@@ -97,7 +100,7 @@ public Slice<ArtistListResponse> listArtist(Pageable pageable, User user, Artist
97100

98101
@Transactional(readOnly = true)
99102
public ArtistDetailResponse getArtistDetail(Long artistId) {
100-
Artist artist = artistRepository.findById(artistId)
103+
Artist artist = artistRepository.findByIdWithArtistGenres(artistId)
101104
.orElseThrow(() -> new BusinessException(ArtistErrorCode.ARTIST_NOT_FOUND));
102105

103106
if (artist.getSpotifyArtistId() == null) {
@@ -198,7 +201,6 @@ public void deleteLikeArtist(Long artistId, User user) {
198201
@Transactional
199202
public void linkArtistConcert(Long artistId, Long concertId) {
200203
Artist artist = findArtist(artistId);
201-
// TODO: 추후 수정 예정
202204
Concert concert = concertRepository.findById(concertId)
203205
.orElseThrow();
204206
concertArtistRepository.save(new ConcertArtist(artist, concert));
@@ -232,4 +234,14 @@ public List<LikeArtistsResponse> findLikeArtistsByUserid(User user) {
232234
.toList();
233235
}
234236

237+
@Transactional(readOnly = true)
238+
public List<GenreArtistsResponse> findArtistsByGenreId(Long genreId) {
239+
if (!genreRepository.existsById(genreId)) {
240+
throw new BusinessException(GenreErrorCode.GENRE_NOT_FOUND);
241+
}
242+
List<Artist> artists = artistRepository.findArtistsByGenreId(genreId);
243+
return artists.stream()
244+
.map(GenreArtistsResponse::from)
245+
.toList();
246+
}
235247
}

src/main/java/com/back/web7_9_codecrete_be/domain/artists/service/GenreService.java

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
11
package com.back.web7_9_codecrete_be.domain.artists.service;
22

3-
import com.back.web7_9_codecrete_be.domain.artists.dto.response.ConcertListByArtistResponse;
43
import com.back.web7_9_codecrete_be.domain.artists.dto.response.GenreResponse;
54
import com.back.web7_9_codecrete_be.domain.artists.entity.Genre;
65
import com.back.web7_9_codecrete_be.domain.artists.repository.GenreRepository;
7-
import com.back.web7_9_codecrete_be.domain.concerts.entity.Concert;
86
import com.back.web7_9_codecrete_be.global.error.code.GenreErrorCode;
97
import com.back.web7_9_codecrete_be.global.error.exception.BusinessException;
108
import lombok.RequiredArgsConstructor;

0 commit comments

Comments
 (0)