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
6 changes: 6 additions & 0 deletions src/main/java/eatda/domain/cheer/Cheer.java
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,12 @@ public class Cheer extends AuditingEntity {
@OneToMany(mappedBy = "cheer", cascade = CascadeType.ALL, orphanRemoval = true)
private Set<CheerImage> images = new HashSet<>();

/*
CheerTags가 Embedded이기 때문에 BatchSize를 그대로 적용하지 못함.
성능을 위해서는 Embedded 제거 후 직접 @OneToMany로 매핑 필요함.
현재 데이터가 많지 않음으로 현상 유지하며 모니터링.
추후 재설계 필요
*/
@Embedded
private CheerTags cheerTags;

Expand Down
6 changes: 6 additions & 0 deletions src/main/java/eatda/domain/store/Store.java
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,12 @@ public class Store extends AuditingEntity {
@Embedded
private Coordinates coordinates;

/*
현재는 가게당 평균 응원 수가 5개 이하이므로 BatchSize=10이 적절함.
데이터 증가를 고려하여 IN 쿼리 한 번당 최대 30개 Store의 Cheer를 로딩하도록 설정.
향후 응원 수가 증가하거나 Store 리스트 조회 규모가 커질 경우
성능 모니터링 후 BatchSize 조정 및 Fetch 전략 재검토 필요.
*/
@OneToMany(mappedBy = "store")
private List<Cheer> cheers = new ArrayList<>();

Expand Down
14 changes: 7 additions & 7 deletions src/main/java/eatda/repository/cheer/CheerRepository.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,18 @@
import eatda.domain.store.StoreCategory;
import jakarta.persistence.criteria.JoinType;
import java.util.List;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.data.jpa.repository.EntityGraph;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.lang.Nullable;

public interface CheerRepository extends JpaRepository<Cheer, Long> {

@EntityGraph(attributePaths = {"member", "cheerTags.values"})
List<Cheer> findAllByStoreOrderByCreatedAtDesc(Store store, PageRequest pageRequest);
Page<Cheer> findAllByStoreOrderByCreatedAtDesc(Store store, PageRequest pageRequest);

default List<Cheer> findAllByConditions(@Nullable StoreCategory category,
default Page<Cheer> findAllByConditions(@Nullable StoreCategory category,
List<CheerTagName> cheerTagNames,
List<District> districts, Pageable pageable) {
Specification<Cheer> spec = createSpecification(category, cheerTagNames, districts);
Expand All @@ -36,7 +35,9 @@ private Specification<Cheer> createSpecification(@Nullable StoreCategory categor
}
if (!cheerTagNames.isEmpty()) {
spec = spec.and(((root, query, cb) -> {
query.distinct(true);
if (query != null) {
query.distinct(true);
}
return root.join("cheerTags").join("values", JoinType.LEFT)
.get("name").in(cheerTagNames);
}));
Expand All @@ -47,8 +48,7 @@ private Specification<Cheer> createSpecification(@Nullable StoreCategory categor
return spec;
}

@EntityGraph(attributePaths = {"store", "member", "cheerTags.values"})
List<Cheer> findAll(Specification<Cheer> specification, Pageable pageable);
Page<Cheer> findAll(Specification<Cheer> specification, Pageable pageable);

int countByMember(Member member);

Expand Down
15 changes: 9 additions & 6 deletions src/main/java/eatda/repository/store/StoreRepository.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@
import eatda.exception.BusinessException;
import java.util.List;
import java.util.Optional;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.data.jpa.repository.EntityGraph;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.lang.Nullable;
Expand All @@ -33,16 +33,15 @@ default Store getById(Long id) {
""")
List<Store> findAllByCheeredMemberId(long memberId);

default List<Store> findAllByConditions(@Nullable StoreCategory category,
default Page<Store> findAllByConditions(@Nullable StoreCategory category,
List<CheerTagName> cheerTagNames,
List<District> districts,
Pageable pageable) {
Specification<Store> spec = createSpecification(category, cheerTagNames, districts);
return findAll(spec, pageable);
}

@EntityGraph(attributePaths = {"cheers"})
List<Store> findAll(Specification<Store> spec, Pageable pageable);
Page<Store> findAll(Specification<Store> spec, Pageable pageable);

private Specification<Store> createSpecification(@Nullable StoreCategory category,
List<CheerTagName> cheerTagNames,
Expand All @@ -52,8 +51,12 @@ private Specification<Store> createSpecification(@Nullable StoreCategory categor
spec = spec.and((root, query, cb) -> cb.equal(root.get("category"), category));
}
if (!cheerTagNames.isEmpty()) {
spec = spec.and(((root, query, cb) ->
root.join("cheers").join("cheerTags").join("values").get("name").in(cheerTagNames)));
spec = spec.and(((root, query, cb) -> {
if (query != null) {
query.distinct(true);
}
return root.join("cheers").join("cheerTags").join("values").get("name").in(cheerTagNames);
}));
}
if (!districts.isEmpty()) {
spec = spec.and((root, query, cb) -> root.get("district").in(districts));
Expand Down
3 changes: 0 additions & 3 deletions src/main/java/eatda/repository/story/StoryRepository.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,12 @@
import eatda.domain.story.Story;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.EntityGraph;
import org.springframework.data.jpa.repository.JpaRepository;

public interface StoryRepository extends JpaRepository<Story, Long> {
@EntityGraph(attributePaths = "images")
Page<Story> findAllByOrderByCreatedAtDesc(Pageable pageable);

Page<Story> findAllByMemberIdOrderByCreatedAtDesc(Long memberId, Pageable pageable);

@EntityGraph(attributePaths = {"member", "images"})
Page<Story> findAllByStoreKakaoIdOrderByCreatedAtDesc(String storeKakaoId, Pageable pageable);
}
15 changes: 10 additions & 5 deletions src/main/java/eatda/service/cheer/CheerService.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import java.util.stream.IntStream;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.data.domain.Sort.Direction;
Expand Down Expand Up @@ -115,12 +116,15 @@ private void saveCheerImages(Cheer cheer,

@Transactional(readOnly = true)
public CheersResponse getCheers(CheerSearchParameters parameters) {
List<Cheer> cheers = cheerRepository.findAllByConditions(
Page<Cheer> cheerPage = cheerRepository.findAllByConditions(
parameters.getCategory(),
parameters.getCheerTagNames(),
parameters.getDistricts(),
PageRequest.of(parameters.getPage(), parameters.getSize(), Sort.by(Direction.DESC, "createdAt"))
PageRequest.of(parameters.getPage(), parameters.getSize(),
Sort.by(Direction.DESC, "createdAt"))
);

List<Cheer> cheers = cheerPage.getContent();
return toCheersResponse(cheers);
}

Expand All @@ -140,11 +144,12 @@ private CheersResponse toCheersResponse(List<Cheer> cheers) {
@Transactional(readOnly = true)
public CheersInStoreResponse getCheersByStoreId(Long storeId, int page, int size) {
Store store = storeRepository.getById(storeId);
List<Cheer> cheers = cheerRepository.findAllByStoreOrderByCreatedAtDesc(store, PageRequest.of(page, size));
Page<Cheer> cheersPage = cheerRepository.findAllByStoreOrderByCreatedAtDesc(store, PageRequest.of(page, size));

List<CheerInStoreResponse> cheersResponse = cheers.stream()
List<CheerInStoreResponse> cheersResponse = cheersPage.getContent().stream()
.map(CheerInStoreResponse::new)
.toList(); // TODO N+1 문제 해결
.toList();

return new CheersInStoreResponse(cheersResponse);
}
}
3 changes: 2 additions & 1 deletion src/main/java/eatda/service/store/StoreService.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import java.util.Optional;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.data.domain.Sort.Direction;
Expand All @@ -45,7 +46,7 @@ public StoreResponse getStore(long storeId) {
// TODO : N+1 문제 해결
@Transactional(readOnly = true)
public StoresResponse getStores(StoreSearchParameters parameters) {
List<Store> stores = storeRepository.findAllByConditions(
Page<Store> stores = storeRepository.findAllByConditions(
parameters.getCategory(),
parameters.getCheerTagNames(),
parameters.getDistricts(),
Expand Down
3 changes: 3 additions & 0 deletions src/main/resources/application-dev.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ spring:
jpa:
hibernate:
ddl-auto: validate
properties:
hibernate:
default_batch_fetch_size: 30

flyway:
enabled: true
Expand Down
1 change: 1 addition & 0 deletions src/main/resources/application-local.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ spring:
max-file-size: 5MB
max-request-size: 20MB

# BatchSize 미적용시를 비교하기 위해 local에는 BatchSize를 추가하지 않음
jpa:
hibernate:
ddl-auto: validate
Expand Down
3 changes: 3 additions & 0 deletions src/main/resources/application-prod.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ spring:
jpa:
hibernate:
ddl-auto: validate
properties:
hibernate:
default_batch_fetch_size: 30

flyway:
enabled: true
Expand Down
11 changes: 6 additions & 5 deletions src/test/java/eatda/repository/cheer/CheerRepositoryTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import java.util.List;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;

class CheerRepositoryTest extends BaseRepositoryTest {
Expand Down Expand Up @@ -64,7 +65,7 @@ class FindAllByConditions {
Cheer cheer2_2 = cheerGenerator.generateCommon(member2, store2);
Cheer cheer3_2 = cheerGenerator.generateCommon(member2, store3);

List<Cheer> actual = cheerRepository.findAllByConditions(
Page<Cheer> actual = cheerRepository.findAllByConditions(
StoreCategory.KOREAN, List.of(), List.of(), Pageable.unpaged());

assertThat(actual).map(Cheer::getId)
Expand All @@ -90,7 +91,7 @@ class FindAllByConditions {
cheerTagGenerator.generate(cheer2_2, List.of(CheerTagName.CLEAN_RESTROOM));
cheerTagGenerator.generate(cheer3_1, List.of(CheerTagName.ENERGETIC, CheerTagName.QUIET));

List<Cheer> actual = cheerRepository.findAllByConditions(null,
Page<Cheer> actual = cheerRepository.findAllByConditions(null,
List.of(CheerTagName.INSTAGRAMMABLE, CheerTagName.CLEAN_RESTROOM), List.of(), Pageable.unpaged());

assertThat(actual)
Expand All @@ -110,7 +111,7 @@ class FindAllByConditions {
Cheer cheer2_2 = cheerGenerator.generateCommon(member2, store2);
Cheer cheer3_2 = cheerGenerator.generateCommon(member2, store3);

List<Cheer> actual = cheerRepository.findAllByConditions(
Page<Cheer> actual = cheerRepository.findAllByConditions(
null, List.of(), List.of(District.GANGNAM), Pageable.unpaged());

assertThat(actual)
Expand Down Expand Up @@ -145,7 +146,7 @@ class FindAllByConditions {
cheerTagGenerator.generate(cheer4_2, List.of(CheerTagName.INSTAGRAMMABLE));
cheerTagGenerator.generate(cheer5_2, List.of(CheerTagName.CLEAN_RESTROOM, CheerTagName.ENERGETIC));

List<Cheer> actual = cheerRepository.findAllByConditions(StoreCategory.KOREAN,
Page<Cheer> actual = cheerRepository.findAllByConditions(StoreCategory.KOREAN,
List.of(CheerTagName.CLEAN_RESTROOM), List.of(District.GANGNAM), Pageable.unpaged());

assertThat(actual)
Expand All @@ -169,7 +170,7 @@ class FindAllByConditions {
cheerTagGenerator.generate(cheer2_1, List.of(CheerTagName.CLEAN_RESTROOM));
cheerTagGenerator.generate(cheer2_2, List.of(CheerTagName.CLEAN_RESTROOM));

List<Cheer> actual = cheerRepository.findAllByConditions(null, List.of(), List.of(), Pageable.unpaged());
Page<Cheer> actual = cheerRepository.findAllByConditions(null, List.of(), List.of(), Pageable.unpaged());

assertThat(actual)
.map(Cheer::getId)
Expand Down
11 changes: 6 additions & 5 deletions src/test/java/eatda/repository/store/StoreRepositoryTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import java.util.List;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;

class StoreRepositoryTest extends BaseRepositoryTest {
Expand Down Expand Up @@ -61,7 +62,7 @@ class FindAllByConditions {
Store store2 = storeGenerator.generate("1236", "서울시 강남구 역삼동 123-45", StoreCategory.WESTERN, startAt);
Store store3 = storeGenerator.generate("1237", "서울시 강남구 역삼동 123-45", StoreCategory.KOREAN, startAt);

List<Store> actual = storeRepository.findAllByConditions(
Page<Store> actual = storeRepository.findAllByConditions(
StoreCategory.KOREAN, List.of(), List.of(), Pageable.unpaged());

assertThat(actual).map(Store::getId)
Expand All @@ -87,7 +88,7 @@ class FindAllByConditions {
cheerTagGenerator.generate(cheer2_2, List.of(CheerTagName.CLEAN_RESTROOM));
cheerTagGenerator.generate(cheer3_1, List.of(CheerTagName.ENERGETIC, CheerTagName.QUIET));

List<Store> actual = storeRepository.findAllByConditions(null,
Page<Store> actual = storeRepository.findAllByConditions(null,
List.of(CheerTagName.INSTAGRAMMABLE, CheerTagName.CLEAN_RESTROOM), List.of(), Pageable.unpaged());

assertThat(actual).map(Store::getId)
Expand All @@ -100,7 +101,7 @@ class FindAllByConditions {
Store store2 = storeGenerator.generate("1236", "서울시 강남구 역삼동 123-45", District.GANGNAM);
Store store3 = storeGenerator.generate("1237", "서울시 성북구 석관동 123-45", District.SEONGBUK);

List<Store> actual = storeRepository.findAllByConditions(
Page<Store> actual = storeRepository.findAllByConditions(
null, List.of(), List.of(District.GANGNAM), Pageable.unpaged());

assertThat(actual).map(Store::getId)
Expand Down Expand Up @@ -134,7 +135,7 @@ class FindAllByConditions {
cheerTagGenerator.generate(cheer4_2, List.of(CheerTagName.INSTAGRAMMABLE));
cheerTagGenerator.generate(cheer5_2, List.of(CheerTagName.CLEAN_RESTROOM, CheerTagName.ENERGETIC));

List<Store> actual = storeRepository.findAllByConditions(StoreCategory.KOREAN,
Page<Store> actual = storeRepository.findAllByConditions(StoreCategory.KOREAN,
List.of(CheerTagName.CLEAN_RESTROOM), List.of(District.GANGNAM), Pageable.unpaged());

assertThat(actual).map(Store::getId)
Expand All @@ -157,7 +158,7 @@ class FindAllByConditions {
cheerTagGenerator.generate(cheer2_1, List.of(CheerTagName.CLEAN_RESTROOM));
cheerTagGenerator.generate(cheer2_2, List.of(CheerTagName.CLEAN_RESTROOM));

List<Store> actual = storeRepository.findAllByConditions(null, List.of(), List.of(), Pageable.unpaged());
Page<Store> actual = storeRepository.findAllByConditions(null, List.of(), List.of(), Pageable.unpaged());

assertThat(actual).map(Store::getId)
.containsExactlyInAnyOrder(store1.getId(), store2.getId(), store3.getId());
Expand Down
Loading