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 @@ -13,6 +13,7 @@
import jakarta.validation.Valid;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
Expand All @@ -32,6 +33,9 @@ public class UnivApplyInfoController {
private final LikedUnivApplyInfoService likedUnivApplyInfoService;
private final UnivApplyInfoRecommendService univApplyInfoRecommendService;

@Value("${university.term}")
public String term;

@GetMapping("/recommend")
public ResponseEntity<UnivApplyInfoRecommendsResponse> getUnivApplyInfoRecommends(
@AuthorizedUser(required = false) Long siteUserId
Expand Down Expand Up @@ -91,15 +95,15 @@ public ResponseEntity<UnivApplyInfoDetailResponse> getUnivApplyInfoDetails(
public ResponseEntity<UnivApplyInfoPreviewResponses> searchUnivApplyInfoByFilter(
@Valid @ModelAttribute UnivApplyInfoFilterSearchRequest request
) {
UnivApplyInfoPreviewResponses response = univApplyInfoQueryService.searchUnivApplyInfoByFilter(request);
UnivApplyInfoPreviewResponses response = univApplyInfoQueryService.searchUnivApplyInfoByFilter(request, term);
return ResponseEntity.ok(response);
}

@GetMapping("/search/text")
public ResponseEntity<UnivApplyInfoPreviewResponses> searchUnivApplyInfoByText(
@RequestParam(required = false) String text
@RequestParam(required = false) String value
) {
UnivApplyInfoPreviewResponses response = univApplyInfoQueryService.searchUnivApplyInfoByText(text);
UnivApplyInfoPreviewResponses response = univApplyInfoQueryService.searchUnivApplyInfoByText(value, term);
return ResponseEntity.ok(response);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,6 @@ public interface UnivApplyInfoFilterRepository {
List<UnivApplyInfo> findAllByRegionCodeAndKeywords(String regionCode, List<String> keywords);

List<UnivApplyInfo> findAllByFilter(LanguageTestType testType, String testScore, String term, List<String> countryKoreanNames);

List<UnivApplyInfo> findAllByText(String text, String term);
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
package com.example.solidconnection.university.repository.custom;

import com.example.solidconnection.location.country.domain.QCountry;
import com.example.solidconnection.location.region.domain.QRegion;
import com.example.solidconnection.university.domain.LanguageTestType;
import com.example.solidconnection.university.domain.QLanguageRequirement;
import com.example.solidconnection.university.domain.QUnivApplyInfo;
import com.example.solidconnection.university.domain.QUniversity;
import com.example.solidconnection.university.domain.UnivApplyInfo;
import com.querydsl.core.BooleanBuilder;
import com.querydsl.core.types.dsl.BooleanExpression;
import com.querydsl.core.types.dsl.CaseBuilder;
import com.querydsl.core.types.dsl.Expressions;
import com.querydsl.core.types.dsl.NumberExpression;
import com.querydsl.core.types.dsl.StringPath;
import com.querydsl.jpa.impl.JPAQuery;
import com.querydsl.jpa.impl.JPAQueryFactory;
import jakarta.persistence.EntityManager;
import java.util.List;
Expand Down Expand Up @@ -136,4 +141,46 @@ private boolean isGivenScoreOverMinPassScore(
.map(requirement -> givenTestType.compare(givenTestScore, requirement.getMinScore()))
.orElse(-1) >= 0;
}

@Override
public List<UnivApplyInfo> findAllByText(String text, String term) {
QUnivApplyInfo univApplyInfo = QUnivApplyInfo.univApplyInfo;
QUniversity university = QUniversity.university;
QLanguageRequirement languageRequirement = QLanguageRequirement.languageRequirement;
QCountry country = QCountry.country;
QRegion region = QRegion.region;

JPAQuery<UnivApplyInfo> base = queryFactory.selectFrom(univApplyInfo)
.join(univApplyInfo.university, university).fetchJoin()
.join(university.country, country).fetchJoin()
.join(region).on(country.regionCode.eq(region.code))
.leftJoin(univApplyInfo.languageRequirements, languageRequirement).fetchJoin()
.where(termEq(univApplyInfo, term));

// text 가 비어있다면 모든 대학 지원 정보를 id 오름차순으로 정렬하여 반환
if (text == null || text.isBlank()) {
return base.orderBy(univApplyInfo.id.asc()).fetch();
}

// 매칭 조건 (대학 지원 정보명/국가명/지역명 중 하나라도 포함)
BooleanExpression univApplyInfoLike = univApplyInfo.koreanName.contains(text);
BooleanExpression countryLike = country.koreanName.contains(text);
BooleanExpression regionLike = region.koreanName.contains(text);
BooleanBuilder where = new BooleanBuilder()
.or(univApplyInfoLike)
.or(countryLike)
.or(regionLike);

// 우선순위 랭크: 대학 지원 정보명(0) > 국가명(1) > 지역명(2) > 그 외(3)
NumberExpression<Integer> rank = new CaseBuilder()
.when(univApplyInfoLike).then(0)
.when(countryLike).then(1)
.when(regionLike).then(2)
.otherwise(3);

// 정렬 조건: 랭크 오름차순 > 대학지원정보 id 오름차순
return base.where(where)
.orderBy(rank.asc(), univApplyInfo.id.asc())
.fetch();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
import com.example.solidconnection.university.repository.UnivApplyInfoRepository;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

Expand All @@ -20,15 +19,12 @@ public class UnivApplyInfoQueryService {

private final UnivApplyInfoRepository univApplyInfoRepository;

@Value("${university.term}")
public String term;

/*
* 대학교 상세 정보를 불러온다.
* - 대학교(University) 정보와 대학 지원 정보(UniversityInfoForApply) 정보를 조합하여 반환한다.
* */
@Transactional(readOnly = true)
@ThunderingHerdCaching(key = "univApplyInfo:{0}", cacheManager = "customCacheManager", ttlSec = 86400)
@ThunderingHerdCaching(key = "univApplyInfo:{0}:{1}", cacheManager = "customCacheManager", ttlSec = 86400)
public UnivApplyInfoDetailResponse getUnivApplyInfoDetail(Long univApplyInfoId) {
UnivApplyInfo univApplyInfo
= univApplyInfoRepository.getUnivApplyInfoById(univApplyInfoId);
Expand All @@ -38,7 +34,7 @@ public UnivApplyInfoDetailResponse getUnivApplyInfoDetail(Long univApplyInfoId)
}

@Transactional(readOnly = true)
public UnivApplyInfoPreviewResponses searchUnivApplyInfoByFilter(UnivApplyInfoFilterSearchRequest request) {
public UnivApplyInfoPreviewResponses searchUnivApplyInfoByFilter(UnivApplyInfoFilterSearchRequest request, String term) {
List<UnivApplyInfoPreviewResponse> responses = univApplyInfoRepository
.findAllByFilter(request.languageTestType(), request.testScore(), term, request.countryCode())
.stream()
Expand All @@ -48,8 +44,12 @@ public UnivApplyInfoPreviewResponses searchUnivApplyInfoByFilter(UnivApplyInfoFi
}

@Transactional(readOnly = true)
public UnivApplyInfoPreviewResponses searchUnivApplyInfoByText(String text) {
// todo: 구현
return null;
@ThunderingHerdCaching(key = "univApplyInfoTextSearch:{0}:{1}", cacheManager = "customCacheManager", ttlSec = 86400)
public UnivApplyInfoPreviewResponses searchUnivApplyInfoByText(String text, String term) {
List<UnivApplyInfoPreviewResponse> responses = univApplyInfoRepository.findAllByText(text, term)
.stream()
.map(UnivApplyInfoPreviewResponse::from)
.toList();
return new UnivApplyInfoPreviewResponses(responses);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,14 @@ public class UnivApplyInfoFixture {
.create();
}

public UnivApplyInfo 아칸소주립대학_지원_정보() {
return univApplyInfoFixtureBuilder.univApplyInfo()
.term(term)
.koreanName("아칸소 주립 대학")
.university(universityFixture.아칸소_주립_대학())
.create();
}

public UnivApplyInfo 메모리얼대학_세인트존스_A_지원_정보() {
return univApplyInfoFixtureBuilder.univApplyInfo()
.term(term)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,15 @@ public final class UniversityFixture {
.create();
}

public University 아칸소_주립_대학() {
return universityFixtureBuilder.university()
.koreanName("아칸소 주립 대학")
.englishName("Arkansas State University")
.country(countryFixture.미국())
.region(regionFixture.영미권())
.create();
}

public University 메모리얼_대학_세인트존스() {
return universityFixtureBuilder.university()
.koreanName("메모리얼 대학 세인트존스")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import static com.example.solidconnection.common.exception.ErrorCode.UNIV_APPLY_INFO_NOT_FOUND;
import static com.example.solidconnection.university.domain.LanguageTestType.TOEIC;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatCode;
import static org.assertj.core.api.AssertionsForClassTypes.assertThatExceptionOfType;
import static org.junit.jupiter.api.Assertions.assertAll;
import static org.mockito.BDDMockito.then;
Expand All @@ -23,6 +24,7 @@
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.test.mock.mockito.SpyBean;

@TestContainerSpringBootTest
Expand All @@ -41,6 +43,9 @@ class UnivApplyInfoQueryServiceTest {
@Autowired
private LanguageRequirementFixture languageRequirementFixture;

@Value("${university.term}")
public String term;

@Nested
class 대학_지원_정보_상세_조회 {

Expand Down Expand Up @@ -97,7 +102,7 @@ class 대학_지원_정보_필터링_검색 {
languageRequirementFixture.토플_70(괌대학_B_지원_정보);

// when
UnivApplyInfoPreviewResponses response = univApplyInfoQueryService.searchUnivApplyInfoByFilter(request);
UnivApplyInfoPreviewResponses response = univApplyInfoQueryService.searchUnivApplyInfoByFilter(request, term);

// then
assertThat(response.univApplyInfoPreviews())
Expand All @@ -114,7 +119,7 @@ class 대학_지원_정보_필터링_검색 {
languageRequirementFixture.토익_900(괌대학_B_지원_정보);

// when
UnivApplyInfoPreviewResponses response = univApplyInfoQueryService.searchUnivApplyInfoByFilter(request);
UnivApplyInfoPreviewResponses response = univApplyInfoQueryService.searchUnivApplyInfoByFilter(request, term);

// then
assertThat(response.univApplyInfoPreviews())
Expand All @@ -132,8 +137,8 @@ class 대학_지원_정보_필터링_검색 {
languageRequirementFixture.토익_800(메모리얼대학_세인트존스_A_지원_정보);

// when
UnivApplyInfoPreviewResponses response1 = univApplyInfoQueryService.searchUnivApplyInfoByFilter(request1);
UnivApplyInfoPreviewResponses response2 = univApplyInfoQueryService.searchUnivApplyInfoByFilter(request2);
UnivApplyInfoPreviewResponses response1 = univApplyInfoQueryService.searchUnivApplyInfoByFilter(request1, term);
UnivApplyInfoPreviewResponses response2 = univApplyInfoQueryService.searchUnivApplyInfoByFilter(request2, term);

// then
assertAll(
Expand All @@ -147,4 +152,132 @@ class 대학_지원_정보_필터링_검색 {
);
}
}

@Nested
class 대학_지원_정보_텍스트_검색 {

@Test
void 텍스트가_없으면_전체_대학을_id_순으로_정렬하여_반환한다() {
// given
UnivApplyInfo 괌대학_A_지원_정보 = univApplyInfoFixture.괌대학_A_지원_정보();
UnivApplyInfo 메이지대학_지원_정보 = univApplyInfoFixture.메이지대학_지원_정보();

// when
UnivApplyInfoPreviewResponses response = univApplyInfoQueryService.searchUnivApplyInfoByText(null, term);

// then
assertThat(response.univApplyInfoPreviews())
.containsExactly(
UnivApplyInfoPreviewResponse.from(괌대학_A_지원_정보),
UnivApplyInfoPreviewResponse.from(메이지대학_지원_정보)
);
}

@Nested
class 각각의_검색_대상에_대해_검색한다 {

@Test
void 국문_대학_지원_정보명() {
// given
String text = "메";
UnivApplyInfo 메이지대학_지원_정보 = univApplyInfoFixture.메이지대학_지원_정보();
UnivApplyInfo 메모리얼대학_세인트존스_A_지원_정보 = univApplyInfoFixture.메모리얼대학_세인트존스_A_지원_정보();
univApplyInfoFixture.괌대학_A_지원_정보();

// when
UnivApplyInfoPreviewResponses response = univApplyInfoQueryService.searchUnivApplyInfoByText(text, term);

// then
assertThat(response.univApplyInfoPreviews())
.containsExactly(
UnivApplyInfoPreviewResponse.from(메이지대학_지원_정보),
UnivApplyInfoPreviewResponse.from(메모리얼대학_세인트존스_A_지원_정보)
);
}

@Test
void 국문_국가명() {
// given
String text = "미국";
UnivApplyInfo 괌대학_A_지원_정보 = univApplyInfoFixture.괌대학_A_지원_정보();
UnivApplyInfo 괌대학_B_지원_정보 = univApplyInfoFixture.괌대학_B_지원_정보();
univApplyInfoFixture.메이지대학_지원_정보();

// when
UnivApplyInfoPreviewResponses response = univApplyInfoQueryService.searchUnivApplyInfoByText(text, term);

// then
assertThat(response.univApplyInfoPreviews())
.containsExactly(
UnivApplyInfoPreviewResponse.from(괌대학_A_지원_정보),
UnivApplyInfoPreviewResponse.from(괌대학_B_지원_정보)
);
}

@Test
void 국문_권역명() {
// given
String text = "유럽";
UnivApplyInfo 린츠_카톨릭대학_지원_정보 = univApplyInfoFixture.린츠_카톨릭대학_지원_정보();
UnivApplyInfo 서던덴마크대학교_지원_정보 = univApplyInfoFixture.서던덴마크대학교_지원_정보();
univApplyInfoFixture.메이지대학_지원_정보();

// when
UnivApplyInfoPreviewResponses response = univApplyInfoQueryService.searchUnivApplyInfoByText(text, term);

// then
assertThat(response.univApplyInfoPreviews())
.containsExactly(
UnivApplyInfoPreviewResponse.from(린츠_카톨릭대학_지원_정보),
UnivApplyInfoPreviewResponse.from(서던덴마크대학교_지원_정보)
);
}
}

@Test
void 대학_국가_권역_일치_순서로_정렬하여_응답한다() {
// given
String text = "아";
UnivApplyInfo 권역_아 = univApplyInfoFixture.메이지대학_지원_정보();
UnivApplyInfo 국가_아 = univApplyInfoFixture.그라츠대학_지원_정보();
UnivApplyInfo 대학지원정보_아 = univApplyInfoFixture.아칸소주립대학_지원_정보();
Comment on lines +237 to +243
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이 테스트코드를 위해서 "아칸소주립대학"을 fixture로 추가했습니다


// when
UnivApplyInfoPreviewResponses response = univApplyInfoQueryService.searchUnivApplyInfoByText(text, term);

// then
assertThat(response.univApplyInfoPreviews())
.containsExactly(
UnivApplyInfoPreviewResponse.from(대학지원정보_아),
UnivApplyInfoPreviewResponse.from(국가_아),
UnivApplyInfoPreviewResponse.from(권역_아)
);
}

@Test
void 캐시가_적용된다() {
// given
String text = "Guam";
UnivApplyInfo 괌대학_A_지원_정보 = univApplyInfoFixture.괌대학_A_지원_정보();

// when
UnivApplyInfoPreviewResponses firstResponse = univApplyInfoQueryService.searchUnivApplyInfoByText(text, term);
UnivApplyInfoPreviewResponses secondResponse = univApplyInfoQueryService.searchUnivApplyInfoByText(text, term);

// then
assertThatCode(() -> {
List<Long> firstResponseIds = extractIds(firstResponse);
List<Long> secondResponseIds = extractIds(secondResponse);
assertThat(firstResponseIds).isEqualTo(secondResponseIds);
}).doesNotThrowAnyException();
then(univApplyInfoRepository).should(times(1)).findAllByText(text, term);
}

private List<Long> extractIds(UnivApplyInfoPreviewResponses responses) {
return responses.univApplyInfoPreviews()
.stream()
.map(UnivApplyInfoPreviewResponse::id)
.toList();
}
}
}
Loading