Skip to content

Commit 3b52e95

Browse files
committed
feat: Cheer 의 동적 쿼리 조회 구현
1 parent 02b8b59 commit 3b52e95

File tree

5 files changed

+206
-1
lines changed

5 files changed

+206
-1
lines changed

src/main/java/eatda/repository/cheer/CheerRepository.java

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,19 @@
22

33
import eatda.domain.ImageKey;
44
import eatda.domain.cheer.Cheer;
5+
import eatda.domain.cheer.CheerTagName;
56
import eatda.domain.member.Member;
7+
import eatda.domain.store.District;
68
import eatda.domain.store.Store;
9+
import eatda.domain.store.StoreCategory;
710
import java.util.List;
811
import java.util.Optional;
912
import org.springframework.data.domain.Pageable;
13+
import org.springframework.data.jpa.domain.Specification;
1014
import org.springframework.data.jpa.repository.EntityGraph;
1115
import org.springframework.data.jpa.repository.JpaRepository;
1216
import org.springframework.data.jpa.repository.Query;
17+
import org.springframework.lang.Nullable;
1318

1419
public interface CheerRepository extends JpaRepository<Cheer, Long> {
1520

@@ -19,6 +24,34 @@ public interface CheerRepository extends JpaRepository<Cheer, Long> {
1924
@EntityGraph(attributePaths = {"member", "cheerTags.values"})
2025
List<Cheer> findAllByStoreIdOrderByCreatedAtDesc(Long storeId, Pageable pageable);
2126

27+
default List<Cheer> findAllByConditions(@Nullable StoreCategory category,
28+
List<CheerTagName> cheerTagNames,
29+
List<District> districts,
30+
Pageable pageable) {
31+
Specification<Cheer> spec = createSpecification(category, cheerTagNames, districts);
32+
return findAll(spec, pageable);
33+
}
34+
35+
private Specification<Cheer> createSpecification(@Nullable StoreCategory category,
36+
List<CheerTagName> cheerTagNames,
37+
List<District> districts) {
38+
Specification<Cheer> spec = Specification.allOf();
39+
if (category != null) {
40+
spec = spec.and((root, query, cb) -> cb.equal(root.get("store").get("category"), category));
41+
}
42+
if (!cheerTagNames.isEmpty()) {
43+
spec = spec.and(((root, query, cb) ->
44+
root.get("cheerTags").get("values").get("name").in(cheerTagNames)));
45+
}
46+
if (!districts.isEmpty()) {
47+
spec = spec.and((root, query, cb) -> root.get("store").get("district").in(districts));
48+
}
49+
return spec;
50+
}
51+
52+
@EntityGraph(attributePaths = {"store", "member", "cheerTags.values"})
53+
List<Cheer> findAll(Specification<Cheer> specification, Pageable pageable);
54+
2255
@Query("""
2356
SELECT c.imageKey FROM Cheer c
2457
WHERE c.store = :store AND c.imageKey IS NOT NULL
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package eatda.fixture;
2+
3+
import eatda.domain.cheer.Cheer;
4+
import eatda.domain.cheer.CheerTag;
5+
import eatda.domain.cheer.CheerTagName;
6+
import eatda.repository.cheer.CheerTagRepository;
7+
import java.util.List;
8+
import org.springframework.stereotype.Component;
9+
10+
@Component
11+
public class CheerTagGenerator {
12+
13+
private final CheerTagRepository cheerTagRepository;
14+
15+
public CheerTagGenerator(CheerTagRepository cheerTagRepository) {
16+
this.cheerTagRepository = cheerTagRepository;
17+
}
18+
19+
public List<CheerTag> generate(Cheer cheer, List<CheerTagName> tagNames) {
20+
return tagNames.stream()
21+
.map(name -> cheerTagRepository.save(new CheerTag(cheer, name)))
22+
.toList();
23+
}
24+
}

src/test/java/eatda/fixture/StoreGenerator.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,11 @@ public Store generate(String kakaoId, String lotNumberAddress, StoreCategory cat
4949
return storeRepository.save(store);
5050
}
5151

52+
public Store generate(String kakaoId, String lotNumberAddress, District district, StoreCategory category) {
53+
Store store = create(kakaoId, lotNumberAddress, district, category);
54+
return storeRepository.save(store);
55+
}
56+
5257
private Store create(String kakaoId, String lotNumberAddress, District district, StoreCategory category) {
5358
return Store.builder()
5459
.kakaoId(kakaoId)

src/test/java/eatda/repository/BaseRepositoryTest.java

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,19 @@
11
package eatda.repository;
22

33
import eatda.fixture.CheerGenerator;
4+
import eatda.fixture.CheerTagGenerator;
45
import eatda.fixture.MemberGenerator;
56
import eatda.fixture.StoreGenerator;
67
import eatda.repository.cheer.CheerRepository;
8+
import eatda.repository.cheer.CheerTagRepository;
79
import eatda.repository.member.MemberRepository;
810
import eatda.repository.store.StoreRepository;
911
import eatda.repository.story.StoryRepository;
1012
import org.springframework.beans.factory.annotation.Autowired;
1113
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
1214
import org.springframework.context.annotation.Import;
1315

14-
@Import({MemberGenerator.class, StoreGenerator.class, CheerGenerator.class})
16+
@Import({MemberGenerator.class, StoreGenerator.class, CheerGenerator.class, CheerTagGenerator.class})
1517
@DataJpaTest
1618
public abstract class BaseRepositoryTest {
1719

@@ -24,6 +26,9 @@ public abstract class BaseRepositoryTest {
2426
@Autowired
2527
protected CheerGenerator cheerGenerator;
2628

29+
@Autowired
30+
protected CheerTagGenerator cheerTagGenerator;
31+
2732
@Autowired
2833
protected MemberRepository memberRepository;
2934

@@ -33,6 +38,9 @@ public abstract class BaseRepositoryTest {
3338
@Autowired
3439
protected CheerRepository cheerRepository;
3540

41+
@Autowired
42+
protected CheerTagRepository cheerTagRepository;
43+
3644
@Autowired
3745
protected StoryRepository storyRepository;
3846
}

src/test/java/eatda/repository/cheer/CheerRepositoryTest.java

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,151 @@
33
import static org.assertj.core.api.Assertions.assertThat;
44

55
import eatda.domain.ImageKey;
6+
import eatda.domain.cheer.Cheer;
7+
import eatda.domain.cheer.CheerTagName;
68
import eatda.domain.member.Member;
9+
import eatda.domain.store.District;
710
import eatda.domain.store.Store;
11+
import eatda.domain.store.StoreCategory;
812
import eatda.repository.BaseRepositoryTest;
13+
import java.time.LocalDateTime;
914
import java.util.List;
1015
import java.util.Optional;
1116
import org.junit.jupiter.api.Nested;
1217
import org.junit.jupiter.api.Test;
18+
import org.springframework.data.domain.Pageable;
1319

1420
class CheerRepositoryTest extends BaseRepositoryTest {
1521

22+
@Nested
23+
class FindAllByConditions {
24+
25+
@Test
26+
void 카테고리로_필터링하여_조회할_수_있다() {
27+
Member member1 = memberGenerator.generateRegisteredMember("커찬", "[email protected]", "123", "01012341235");
28+
Member member2 = memberGenerator.generateRegisteredMember("지민", "[email protected]", "124", "01012341236");
29+
LocalDateTime startAt = LocalDateTime.of(2023, 10, 1, 12, 0);
30+
Store store1 = storeGenerator.generate("1235", "서울시 강남구 역삼동 123-45", StoreCategory.KOREAN, startAt);
31+
Store store2 = storeGenerator.generate("1236", "서울시 강남구 역삼동 123-45", StoreCategory.WESTERN, startAt);
32+
Store store3 = storeGenerator.generate("1237", "서울시 강남구 역삼동 123-45", StoreCategory.KOREAN, startAt);
33+
Cheer cheer1_1 = cheerGenerator.generateCommon(member1, store1);
34+
Cheer cheer2_1 = cheerGenerator.generateCommon(member1, store2);
35+
Cheer cheer2_2 = cheerGenerator.generateCommon(member2, store2);
36+
Cheer cheer3_2 = cheerGenerator.generateCommon(member2, store3);
37+
38+
List<Cheer> actual = cheerRepository.findAllByConditions(
39+
StoreCategory.KOREAN, List.of(), List.of(), Pageable.unpaged());
40+
41+
assertThat(actual).map(Cheer::getId)
42+
.containsExactlyInAnyOrder(cheer1_1.getId(), cheer3_2.getId());
43+
}
44+
45+
@Test
46+
void 응원_태그를_필터링하여_조회할_수_있다() {
47+
Member member1 = memberGenerator.generateRegisteredMember("커찬", "[email protected]", "123", "01012341235");
48+
Member member2 = memberGenerator.generateRegisteredMember("지민", "[email protected]", "124", "01012341236");
49+
LocalDateTime startAt = LocalDateTime.of(2023, 10, 1, 12, 0);
50+
Store store1 = storeGenerator.generate("1235", "서울시 강남구 역삼동 123-45", StoreCategory.KOREAN, startAt);
51+
Store store2 = storeGenerator.generate("1236", "서울시 강남구 역삼동 123-45", StoreCategory.KOREAN, startAt);
52+
Store store3 = storeGenerator.generate("1237", "서울시 강남구 역삼동 123-45", StoreCategory.KOREAN, startAt);
53+
Store store4 = storeGenerator.generate("1238", "서울시 강남구 역삼동 123-45", StoreCategory.KOREAN, startAt);
54+
Cheer cheer1_1 = cheerGenerator.generate(member1, store1, startAt);
55+
Cheer cheer2_1 = cheerGenerator.generate(member1, store2, startAt);
56+
Cheer cheer2_2 = cheerGenerator.generate(member2, store2, startAt);
57+
Cheer cheer3_1 = cheerGenerator.generate(member1, store3, startAt);
58+
Cheer cheer4_2 = cheerGenerator.generate(member2, store4, startAt);
59+
cheerTagGenerator.generate(cheer1_1, List.of(CheerTagName.INSTAGRAMMABLE, CheerTagName.ENERGETIC));
60+
cheerTagGenerator.generate(cheer2_1, List.of(CheerTagName.CLEAN_RESTROOM));
61+
cheerTagGenerator.generate(cheer2_2, List.of(CheerTagName.CLEAN_RESTROOM));
62+
cheerTagGenerator.generate(cheer3_1, List.of(CheerTagName.ENERGETIC, CheerTagName.QUIET));
63+
64+
List<Cheer> actual = cheerRepository.findAllByConditions(null,
65+
List.of(CheerTagName.INSTAGRAMMABLE, CheerTagName.CLEAN_RESTROOM), List.of(), Pageable.unpaged());
66+
67+
assertThat(actual)
68+
.map(Cheer::getId)
69+
.containsExactlyInAnyOrder(cheer1_1.getId(), cheer2_1.getId(), cheer2_2.getId());
70+
}
71+
72+
@Test
73+
void 지역구를_필터링하여_조회할_수_있다() {
74+
Member member1 = memberGenerator.generateRegisteredMember("커찬", "[email protected]", "123", "01012341235");
75+
Member member2 = memberGenerator.generateRegisteredMember("지민", "[email protected]", "124", "01012341236");
76+
Store store1 = storeGenerator.generate("1235", "서울시 강남구 역삼동 123-45", District.GANGNAM);
77+
Store store2 = storeGenerator.generate("1236", "서울시 강남구 역삼동 123-45", District.GANGNAM);
78+
Store store3 = storeGenerator.generate("1237", "서울시 성북구 석관동 123-45", District.SEONGBUK);
79+
Cheer cheer1_1 = cheerGenerator.generateCommon(member1, store1);
80+
Cheer cheer2_1 = cheerGenerator.generateCommon(member1, store2);
81+
Cheer cheer2_2 = cheerGenerator.generateCommon(member2, store2);
82+
Cheer cheer3_2 = cheerGenerator.generateCommon(member2, store3);
83+
84+
List<Cheer> actual = cheerRepository.findAllByConditions(
85+
null, List.of(), List.of(District.GANGNAM), Pageable.unpaged());
86+
87+
assertThat(actual)
88+
.map(Cheer::getId)
89+
.containsExactlyInAnyOrder(cheer1_1.getId(), cheer2_1.getId(), cheer2_2.getId());
90+
}
91+
92+
@Test
93+
void 여러_조건을_조합하여_조회할_수_있다() {
94+
Member member1 = memberGenerator.generateRegisteredMember("커찬", "[email protected]", "123", "01012341235");
95+
Member member2 = memberGenerator.generateRegisteredMember("지민", "[email protected]", "124", "01012341236");
96+
Store store1 = storeGenerator.generate("1235", "서울시 성북구 석관동 123-41", District.SEONGBUK,
97+
StoreCategory.KOREAN);
98+
Store store2 = storeGenerator.generate("1236", "서울시 강남구 역삼동 123-42", District.GANGNAM,
99+
StoreCategory.WESTERN);
100+
Store store3 = storeGenerator.generate("1237", "서울시 강남구 역삼동 123-43", District.GANGNAM,
101+
StoreCategory.KOREAN);
102+
Store store4 = storeGenerator.generate("1238", "서울시 강남구 역삼동 123-44", District.GANGNAM,
103+
StoreCategory.KOREAN);
104+
Store store5 = storeGenerator.generate("1239", "서울시 강남구 역삼동 123-45", District.GANGNAM,
105+
StoreCategory.KOREAN);
106+
Cheer cheer1_1 = cheerGenerator.generateCommon(member1, store1);
107+
Cheer cheer2_1 = cheerGenerator.generateCommon(member1, store2);
108+
Cheer cheer3_1 = cheerGenerator.generateCommon(member1, store3);
109+
Cheer cheer3_2 = cheerGenerator.generateCommon(member2, store3);
110+
Cheer cheer4_2 = cheerGenerator.generateCommon(member2, store4);
111+
Cheer cheer5_2 = cheerGenerator.generateCommon(member2, store5);
112+
cheerTagGenerator.generate(cheer1_1, List.of(CheerTagName.INSTAGRAMMABLE, CheerTagName.ENERGETIC));
113+
cheerTagGenerator.generate(cheer2_1, List.of(CheerTagName.CLEAN_RESTROOM));
114+
cheerTagGenerator.generate(cheer3_1, List.of(CheerTagName.ENERGETIC, CheerTagName.QUIET));
115+
cheerTagGenerator.generate(cheer3_2, List.of(CheerTagName.CLEAN_RESTROOM));
116+
cheerTagGenerator.generate(cheer4_2, List.of(CheerTagName.INSTAGRAMMABLE));
117+
cheerTagGenerator.generate(cheer5_2, List.of(CheerTagName.CLEAN_RESTROOM, CheerTagName.ENERGETIC));
118+
119+
List<Cheer> actual = cheerRepository.findAllByConditions(StoreCategory.KOREAN,
120+
List.of(CheerTagName.CLEAN_RESTROOM), List.of(District.GANGNAM), Pageable.unpaged());
121+
122+
assertThat(actual)
123+
.map(Cheer::getId)
124+
.containsExactlyInAnyOrder(cheer3_2.getId(), cheer5_2.getId());
125+
}
126+
127+
@Test
128+
void 조건없이_모든_가게를_조회할_수_있다() {
129+
Member member1 = memberGenerator.generateRegisteredMember("커찬", "[email protected]", "123", "01012341235");
130+
Member member2 = memberGenerator.generateRegisteredMember("지민", "[email protected]", "124", "01012341236");
131+
LocalDateTime startAt = LocalDateTime.of(2023, 10, 1, 12, 0);
132+
Store store1 = storeGenerator.generate("1235", "서울시 강남구 역삼동 123-45", StoreCategory.KOREAN, startAt);
133+
Store store2 = storeGenerator.generate("1236", "서울시 강남구 역삼동 123-45", StoreCategory.KOREAN, startAt);
134+
Store store3 = storeGenerator.generate("1237", "서울시 강남구 역삼동 123-45", StoreCategory.KOREAN, startAt);
135+
Cheer cheer1_1 = cheerGenerator.generate(member1, store1, startAt);
136+
Cheer cheer2_1 = cheerGenerator.generate(member1, store2, startAt);
137+
Cheer cheer2_2 = cheerGenerator.generate(member2, store2, startAt);
138+
Cheer cheer3_1 = cheerGenerator.generate(member1, store3, startAt);
139+
cheerTagGenerator.generate(cheer1_1, List.of(CheerTagName.INSTAGRAMMABLE, CheerTagName.ENERGETIC));
140+
cheerTagGenerator.generate(cheer2_1, List.of(CheerTagName.CLEAN_RESTROOM));
141+
cheerTagGenerator.generate(cheer2_2, List.of(CheerTagName.CLEAN_RESTROOM));
142+
143+
List<Cheer> actual = cheerRepository.findAllByConditions(null, List.of(), List.of(), Pageable.unpaged());
144+
145+
assertThat(actual)
146+
.map(Cheer::getId)
147+
.containsExactlyInAnyOrder(cheer1_1.getId(), cheer2_1.getId(), cheer2_2.getId(), cheer3_1.getId());
148+
}
149+
}
150+
16151
@Nested
17152
class FindRecentImageKey {
18153

0 commit comments

Comments
 (0)