diff --git a/src/main/java/com/example/solidconnection/application/domain/Application.java b/src/main/java/com/example/solidconnection/application/domain/Application.java index 98342ca88..5f982410e 100644 --- a/src/main/java/com/example/solidconnection/application/domain/Application.java +++ b/src/main/java/com/example/solidconnection/application/domain/Application.java @@ -1,7 +1,6 @@ package com.example.solidconnection.application.domain; import com.example.solidconnection.siteuser.domain.SiteUser; -import com.example.solidconnection.university.domain.UniversityInfoForApply; import jakarta.persistence.Column; import jakarta.persistence.Embedded; import jakarta.persistence.Entity; @@ -11,7 +10,9 @@ import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; +import jakarta.persistence.Index; import jakarta.persistence.ManyToOne; +import jakarta.persistence.Table; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; @@ -25,6 +26,16 @@ @DynamicUpdate @DynamicInsert @Entity +@Table(indexes = { + @Index(name = "idx_app_user_term_delete", + columnList = "site_user_id, term, is_delete"), + @Index(name = "idx_app_first_choice_search", + columnList = "verify_status, term, is_delete, first_choice_university_info_for_apply_id"), + @Index(name = "idx_app_second_choice_search", + columnList = "verify_status, term, is_delete, second_choice_university_info_for_apply_id"), + @Index(name = "idx_app_third_choice_search", + columnList = "verify_status, term, is_delete, third_choice_university_info_for_apply_id") +}) public class Application { @Id @@ -38,30 +49,30 @@ public class Application { private LanguageTest languageTest; @Setter - @Column(columnDefinition = "varchar(50) not null default 'PENDING'") + @Column(columnDefinition = "varchar(50) not null default 'PENDING'", name="verify_status") @Enumerated(EnumType.STRING) private VerifyStatus verifyStatus; - @Column(length = 100) + @Column(length = 100, name="nickname_for_apply") private String nicknameForApply; - @Column(columnDefinition = "int not null default 1") + @Column(columnDefinition = "int not null default 1", name="update_count") private Integer updateCount; - @Column(length = 50, nullable = false) + @Column(length = 50, nullable = false, name="term") private String term; - @Column + @Column(name="is_delete") private boolean isDelete = false; - @ManyToOne(fetch = FetchType.LAZY) - private UniversityInfoForApply firstChoiceUniversity; + @Column(nullable = false , name = "first_choice_university_info_for_apply_id") + private long firstChoiceUnivApplyInfoId; - @ManyToOne(fetch = FetchType.LAZY) - private UniversityInfoForApply secondChoiceUniversity; + @Column(name = "second_choice_university_info_for_apply_id") + private Long secondChoiceUnivApplyInfoId; - @ManyToOne(fetch = FetchType.LAZY) - private UniversityInfoForApply thirdChoiceUniversity; + @Column(name = "third_choice_university_info_for_apply_id") + private Long thirdChoiceUnivApplyInfoId; @ManyToOne(fetch = FetchType.LAZY) private SiteUser siteUser; @@ -85,18 +96,18 @@ public Application( LanguageTest languageTest, String term, Integer updateCount, - UniversityInfoForApply firstChoiceUniversity, - UniversityInfoForApply secondChoiceUniversity, - UniversityInfoForApply thirdChoiceUniversity, + long firstChoiceUnivApplyInfoId, + Long secondChoiceUnivApplyInfoId, + Long thirdChoiceUnivApplyInfoId, String nicknameForApply) { this.siteUser = siteUser; this.gpa = gpa; this.languageTest = languageTest; this.term = term; this.updateCount = updateCount; - this.firstChoiceUniversity = firstChoiceUniversity; - this.secondChoiceUniversity = secondChoiceUniversity; - this.thirdChoiceUniversity = thirdChoiceUniversity; + this.firstChoiceUnivApplyInfoId = firstChoiceUnivApplyInfoId; + this.secondChoiceUnivApplyInfoId = secondChoiceUnivApplyInfoId; + this.thirdChoiceUnivApplyInfoId = thirdChoiceUnivApplyInfoId; this.nicknameForApply = nicknameForApply; this.verifyStatus = PENDING; } @@ -106,18 +117,18 @@ public Application( Gpa gpa, LanguageTest languageTest, String term, - UniversityInfoForApply firstChoiceUniversity, - UniversityInfoForApply secondChoiceUniversity, - UniversityInfoForApply thirdChoiceUniversity, + long firstChoiceUnivApplyInfoId, + Long secondChoiceUnivApplyInfoId, + Long thirdChoiceUnivApplyInfoId, String nicknameForApply) { this.siteUser = siteUser; this.gpa = gpa; this.languageTest = languageTest; this.term = term; this.updateCount = 1; - this.firstChoiceUniversity = firstChoiceUniversity; - this.secondChoiceUniversity = secondChoiceUniversity; - this.thirdChoiceUniversity = thirdChoiceUniversity; + this.firstChoiceUnivApplyInfoId = firstChoiceUnivApplyInfoId; + this.secondChoiceUnivApplyInfoId = secondChoiceUnivApplyInfoId; + this.thirdChoiceUnivApplyInfoId = thirdChoiceUnivApplyInfoId; this.nicknameForApply = nicknameForApply; this.verifyStatus = PENDING; } @@ -125,18 +136,4 @@ public Application( public void setIsDeleteTrue() { this.isDelete = true; } - - public void updateUniversityChoice( - UniversityInfoForApply firstChoiceUniversity, - UniversityInfoForApply secondChoiceUniversity, - UniversityInfoForApply thirdChoiceUniversity, - String nicknameForApply) { - if (this.firstChoiceUniversity != null) { - this.updateCount++; - } - this.firstChoiceUniversity = firstChoiceUniversity; - this.secondChoiceUniversity = secondChoiceUniversity; - this.thirdChoiceUniversity = thirdChoiceUniversity; - this.nicknameForApply = nicknameForApply; - } } diff --git a/src/main/java/com/example/solidconnection/application/dto/UniversityApplicantsResponse.java b/src/main/java/com/example/solidconnection/application/dto/UniversityApplicantsResponse.java index 1d3415003..2e43ab851 100644 --- a/src/main/java/com/example/solidconnection/application/dto/UniversityApplicantsResponse.java +++ b/src/main/java/com/example/solidconnection/application/dto/UniversityApplicantsResponse.java @@ -1,5 +1,7 @@ package com.example.solidconnection.application.dto; +import com.example.solidconnection.application.domain.Application; +import com.example.solidconnection.siteuser.domain.SiteUser; import com.example.solidconnection.university.domain.UniversityInfoForApply; import java.util.List; @@ -10,13 +12,18 @@ public record UniversityApplicantsResponse( String region, String country, List applicants) { - - public static UniversityApplicantsResponse of(UniversityInfoForApply universityInfoForApply, List applicant) { + public static UniversityApplicantsResponse of(UniversityInfoForApply universityInfoForApply, List applications, SiteUser siteUser) { return new UniversityApplicantsResponse( universityInfoForApply.getKoreanName(), universityInfoForApply.getStudentCapacity(), universityInfoForApply.getUniversity().getRegion().getKoreanName(), universityInfoForApply.getUniversity().getCountry().getKoreanName(), - applicant); + applications.stream() + .map(application -> ApplicantResponse.of(application, isUsers(application, siteUser))) + .toList()); + } + + private static boolean isUsers(Application application, SiteUser siteUser) { + return application.getSiteUser().getId().equals(siteUser.getId()); } } diff --git a/src/main/java/com/example/solidconnection/application/repository/ApplicationRepository.java b/src/main/java/com/example/solidconnection/application/repository/ApplicationRepository.java index 7df53eae6..badbc8254 100644 --- a/src/main/java/com/example/solidconnection/application/repository/ApplicationRepository.java +++ b/src/main/java/com/example/solidconnection/application/repository/ApplicationRepository.java @@ -20,21 +20,26 @@ public interface ApplicationRepository extends JpaRepository boolean existsByNicknameForApply(String nicknameForApply); - List findAllByFirstChoiceUniversityAndVerifyStatusAndTermAndIsDeleteFalse( - UniversityInfoForApply firstChoiceUniversity, VerifyStatus verifyStatus, String term); - - List findAllBySecondChoiceUniversityAndVerifyStatusAndTermAndIsDeleteFalse( - UniversityInfoForApply secondChoiceUniversity, VerifyStatus verifyStatus, String term); - - List findAllByThirdChoiceUniversityAndVerifyStatusAndTermAndIsDeleteFalse( - UniversityInfoForApply thirdChoiceUniversity, VerifyStatus verifyStatus, String term); + @Query(""" + SELECT a + FROM Application a + JOIN FETCH a.siteUser + WHERE (a.firstChoiceUnivApplyInfoId IN :universityIds + OR a.secondChoiceUnivApplyInfoId IN :universityIds + OR a.thirdChoiceUnivApplyInfoId IN :universityIds) + AND a.verifyStatus = :status + AND a.term = :term + AND a.isDelete = false + """) + List findAllByUnivApplyInfoIds(@Param("universityIds") List universityIds, @Param("status") VerifyStatus status, @Param("term") String term); @Query(""" - SELECT a FROM Application a - WHERE a.siteUser = :siteUser - AND a.term = :term - AND a.isDelete = false - """) + SELECT a + FROM Application a + WHERE a.siteUser = :siteUser + AND a.term = :term + AND a.isDelete = false + """) Optional findBySiteUserAndTerm(@Param("siteUser") SiteUser siteUser, @Param("term") String term); default Application getApplicationBySiteUserAndTerm(SiteUser siteUser, String term) { diff --git a/src/main/java/com/example/solidconnection/application/service/ApplicationQueryService.java b/src/main/java/com/example/solidconnection/application/service/ApplicationQueryService.java index e13ca7e3f..8e06ad79f 100644 --- a/src/main/java/com/example/solidconnection/application/service/ApplicationQueryService.java +++ b/src/main/java/com/example/solidconnection/application/service/ApplicationQueryService.java @@ -6,10 +6,8 @@ import com.example.solidconnection.application.dto.ApplicationsResponse; import com.example.solidconnection.application.dto.UniversityApplicantsResponse; import com.example.solidconnection.application.repository.ApplicationRepository; -import com.example.solidconnection.cache.annotation.ThunderingHerdCaching; import com.example.solidconnection.common.exception.CustomException; import com.example.solidconnection.siteuser.domain.SiteUser; -import com.example.solidconnection.university.domain.University; import com.example.solidconnection.university.domain.UniversityInfoForApply; import com.example.solidconnection.university.repository.UniversityInfoForApplyRepository; import com.example.solidconnection.university.repository.custom.UniversityFilterRepositoryImpl; @@ -18,12 +16,14 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import java.util.Arrays; -import java.util.List; -import java.util.Objects; -import java.util.Optional; import java.util.function.Function; import java.util.stream.Collectors; +import java.util.stream.Stream; +import java.util.List; +import java.util.Objects; +import java.util.Map; +import java.util.HashMap; +import java.util.ArrayList; import static com.example.solidconnection.common.exception.ErrorCode.APPLICATION_NOT_APPROVED; @@ -38,97 +38,92 @@ public class ApplicationQueryService { @Value("${university.term}") public String term; - /* - * 다른 지원자들의 성적을 조회한다. - * - 유저가 다른 지원자들을 볼 수 있는지 검증한다. - * - 지역과 키워드를 통해 대학을 필터링한다. - * - 지역은 영어 대문자로 받는다 e.g. ASIA - * - 1지망, 2지망 지원자들을 조회한다. - * */ + // todo: 캐싱 정책 변경 시 수정 필요 @Transactional(readOnly = true) - // todo: 임시로 단일 키로 캐시 적용. 추후 캐싱 전략 재검토 필요. - @ThunderingHerdCaching(key = "applications:all", cacheManager = "customCacheManager", ttlSec = 86400) public ApplicationsResponse getApplicants(SiteUser siteUser, String regionCode, String keyword) { - // 국가와 키워드와 지역을 통해 대학을 필터링한다. - List universities - = universityFilterRepository.findByRegionCodeAndKeywords(regionCode, List.of(keyword)); - - // 1지망, 2지망, 3지망 지원자들을 조회한다. - List firstChoiceApplicants = getFirstChoiceApplicants(universities, siteUser, term); - List secondChoiceApplicants = getSecondChoiceApplicants(universities, siteUser, term); - List thirdChoiceApplicants = getThirdChoiceApplicants(universities, siteUser, term); - return new ApplicationsResponse(firstChoiceApplicants, secondChoiceApplicants, thirdChoiceApplicants); + // 1. 대학 지원 정보 필터링 (regionCode, keyword) + List univApplyInfos = universityFilterRepository.findByRegionCodeAndKeywords(regionCode, List.of(keyword)); + if (univApplyInfos.isEmpty()) { + return new ApplicationsResponse(List.of(), List.of(), List.of()); + } + // 2. 조건에 맞는 모든 Application 한 번에 조회 + List univApplyInfoIds = univApplyInfos.stream() + .map(UniversityInfoForApply::getId) + .toList(); + List applications = applicationRepository.findAllByUnivApplyInfoIds(univApplyInfoIds, VerifyStatus.APPROVED, term); + // 3. 지원서 분류 및 DTO 변환 + return classifyApplicationsByChoice(univApplyInfos, applications, siteUser); } @Transactional(readOnly = true) public ApplicationsResponse getApplicantsByUserApplications(SiteUser siteUser) { Application userLatestApplication = applicationRepository.getApplicationBySiteUserAndTerm(siteUser, term); - List userAppliedUniversities = Arrays.asList( - Optional.ofNullable(userLatestApplication.getFirstChoiceUniversity()) - .map(UniversityInfoForApply::getUniversity) - .orElse(null), - Optional.ofNullable(userLatestApplication.getSecondChoiceUniversity()) - .map(UniversityInfoForApply::getUniversity) - .orElse(null), - Optional.ofNullable(userLatestApplication.getThirdChoiceUniversity()) - .map(UniversityInfoForApply::getUniversity) - .orElse(null) - ).stream() + + List universityInfoForApplyIds = Stream.of( + userLatestApplication.getFirstChoiceUnivApplyInfoId(), + userLatestApplication.getSecondChoiceUnivApplyInfoId(), + userLatestApplication.getThirdChoiceUnivApplyInfoId() + ) .filter(Objects::nonNull) .collect(Collectors.toList()); - List firstChoiceApplicants = getFirstChoiceApplicants(userAppliedUniversities, siteUser, term); - List secondChoiceApplicants = getSecondChoiceApplicants(userAppliedUniversities, siteUser, term); - List thirdChoiceApplicants = getThirdChoiceApplicants(userAppliedUniversities, siteUser, term); - return new ApplicationsResponse(firstChoiceApplicants, secondChoiceApplicants, thirdChoiceApplicants); - } - - // 학기별로 상태가 관리된다. - // 금학기에 지원이력이 있는 사용자만 지원정보를 확인할 수 있도록 한다. - @Transactional(readOnly = true) - public void validateSiteUserCanViewApplicants(SiteUser siteUser) { - VerifyStatus verifyStatus = applicationRepository.getApplicationBySiteUserAndTerm(siteUser, term).getVerifyStatus(); - if (verifyStatus != VerifyStatus.APPROVED) { - throw new CustomException(APPLICATION_NOT_APPROVED); + if (universityInfoForApplyIds.isEmpty()) { + return new ApplicationsResponse(List.of(), List.of(), List.of()); } - } - private List getFirstChoiceApplicants(List universities, SiteUser siteUser, String term) { - return getApplicantsByChoice( - universities, - siteUser, - uia -> applicationRepository.findAllByFirstChoiceUniversityAndVerifyStatusAndTermAndIsDeleteFalse(uia, VerifyStatus.APPROVED, term) - ); + List applications = applicationRepository.findAllByUnivApplyInfoIds(universityInfoForApplyIds, VerifyStatus.APPROVED, term); + List universityInfosForApply = universityInfoForApplyRepository.findAllByUniversityIds(universityInfoForApplyIds); + + return classifyApplicationsByChoice(universityInfosForApply, applications, siteUser); } - private List getSecondChoiceApplicants(List universities, SiteUser siteUser, String term) { - return getApplicantsByChoice( - universities, - siteUser, - uia -> applicationRepository.findAllBySecondChoiceUniversityAndVerifyStatusAndTermAndIsDeleteFalse(uia, VerifyStatus.APPROVED, term) - ); + private ApplicationsResponse classifyApplicationsByChoice( + List universityInfosForApply, + List applications, + SiteUser siteUser) { + Map> firstChoiceMap = createChoiceMap(applications, Application::getFirstChoiceUnivApplyInfoId); + Map> secondChoiceMap = createChoiceMap(applications, Application::getSecondChoiceUnivApplyInfoId); + Map> thirdChoiceMap = createChoiceMap(applications, Application::getThirdChoiceUnivApplyInfoId); + + List firstChoiceApplicants = + createUniversityApplicantsResponses(universityInfosForApply, firstChoiceMap, siteUser); + List secondChoiceApplicants = + createUniversityApplicantsResponses(universityInfosForApply, secondChoiceMap, siteUser); + List thirdChoiceApplicants = + createUniversityApplicantsResponses(universityInfosForApply, thirdChoiceMap, siteUser); + + return new ApplicationsResponse(firstChoiceApplicants, secondChoiceApplicants, thirdChoiceApplicants); } - private List getThirdChoiceApplicants(List universities, SiteUser siteUser, String term) { - return getApplicantsByChoice( - universities, - siteUser, - uia -> applicationRepository.findAllByThirdChoiceUniversityAndVerifyStatusAndTermAndIsDeleteFalse(uia, VerifyStatus.APPROVED, term) - ); + private Map> createChoiceMap( + List applications, + Function choiceIdExtractor) { + Map> choiceMap = new HashMap<>(); + + for (Application application : applications) { + Long choiceId = choiceIdExtractor.apply(application); + if (choiceId != null) { + choiceMap.computeIfAbsent(choiceId, k -> new ArrayList<>()).add(application); + } + } + + return choiceMap; } - private List getApplicantsByChoice( - List searchedUniversities, - SiteUser siteUser, - Function> findApplicationsByChoice) { - return universityInfoForApplyRepository.findByUniversitiesAndTerm(searchedUniversities, term).stream() - .map(universityInfoForApply -> UniversityApplicantsResponse.of( - universityInfoForApply, - findApplicationsByChoice.apply(universityInfoForApply).stream() - .map(ap -> ApplicantResponse.of( - ap, - Objects.equals(siteUser.getId(), ap.getSiteUser().getId()))) - .toList())) + private List createUniversityApplicantsResponses( + List universityInfosForApply, + Map> choiceMap, + SiteUser siteUser) { + return universityInfosForApply.stream() + .map(uia -> UniversityApplicantsResponse.of(uia, choiceMap.getOrDefault(uia.getId(), List.of()), siteUser)) .toList(); } + + @Transactional(readOnly = true) + public void validateSiteUserCanViewApplicants(SiteUser siteUser) { + VerifyStatus verifyStatus = applicationRepository.getApplicationBySiteUserAndTerm(siteUser, term).getVerifyStatus(); + if (verifyStatus != VerifyStatus.APPROVED) { + throw new CustomException(APPLICATION_NOT_APPROVED); + } + } } diff --git a/src/main/java/com/example/solidconnection/application/service/ApplicationSubmissionService.java b/src/main/java/com/example/solidconnection/application/service/ApplicationSubmissionService.java index af1678a16..721693f38 100644 --- a/src/main/java/com/example/solidconnection/application/service/ApplicationSubmissionService.java +++ b/src/main/java/com/example/solidconnection/application/service/ApplicationSubmissionService.java @@ -6,14 +6,12 @@ import com.example.solidconnection.application.dto.ApplyRequest; import com.example.solidconnection.application.dto.UniversityChoiceRequest; import com.example.solidconnection.application.repository.ApplicationRepository; -import com.example.solidconnection.cache.annotation.DefaultCacheOut; import com.example.solidconnection.common.exception.CustomException; import com.example.solidconnection.score.domain.GpaScore; import com.example.solidconnection.score.domain.LanguageTestScore; import com.example.solidconnection.score.repository.GpaScoreRepository; import com.example.solidconnection.score.repository.LanguageTestScoreRepository; import com.example.solidconnection.siteuser.domain.SiteUser; -import com.example.solidconnection.university.domain.UniversityInfoForApply; import com.example.solidconnection.university.repository.UniversityInfoForApplyRepository; import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Value; @@ -45,24 +43,14 @@ public class ApplicationSubmissionService { // 학점 및 어학성적이 모두 유효한 경우에만 지원서 등록이 가능하다. // 기존에 있던 status field 우선 APRROVED로 입력시킨다. @Transactional - // todo: 임시로 새로운 신청 생성 시 기존 캐싱 데이터를 삭제한다. 추후 수정 필요 - @DefaultCacheOut( - key = {"applications:all"}, - cacheManager = "customCacheManager" - ) public ApplicationSubmissionResponse apply(SiteUser siteUser, ApplyRequest applyRequest) { UniversityChoiceRequest universityChoiceRequest = applyRequest.universityChoiceRequest(); GpaScore gpaScore = getValidGpaScore(siteUser, applyRequest.gpaScoreId()); LanguageTestScore languageTestScore = getValidLanguageTestScore(siteUser, applyRequest.languageTestScoreId()); - UniversityInfoForApply firstChoiceUniversity = universityInfoForApplyRepository - .getUniversityInfoForApplyByIdAndTerm(universityChoiceRequest.firstChoiceUniversityId(), term); - UniversityInfoForApply secondChoiceUniversity = Optional.ofNullable(universityChoiceRequest.secondChoiceUniversityId()) - .map(id -> universityInfoForApplyRepository.getUniversityInfoForApplyByIdAndTerm(id, term)) - .orElse(null); - UniversityInfoForApply thirdChoiceUniversity = Optional.ofNullable(universityChoiceRequest.thirdChoiceUniversityId()) - .map(id -> universityInfoForApplyRepository.getUniversityInfoForApplyByIdAndTerm(id, term)) - .orElse(null); + long firstChoiceUniversityId = universityChoiceRequest.firstChoiceUniversityId(); + Long secondChoiceUniversityId = universityChoiceRequest.secondChoiceUniversityId(); + Long thirdChoiceUniversityId = universityChoiceRequest.thirdChoiceUniversityId(); Optional existingApplication = applicationRepository.findBySiteUserAndTerm(siteUser, term); int updateCount = existingApplication @@ -72,10 +60,22 @@ public ApplicationSubmissionResponse apply(SiteUser siteUser, ApplyRequest apply return application.getUpdateCount() + 1; }) .orElse(1); - Application newApplication = new Application(siteUser, gpaScore.getGpa(), languageTestScore.getLanguageTest(), - term, updateCount, firstChoiceUniversity, secondChoiceUniversity, thirdChoiceUniversity, getRandomNickname()); + + Application newApplication = new Application( + siteUser, + gpaScore.getGpa(), + languageTestScore.getLanguageTest(), + term, + updateCount, + firstChoiceUniversityId, + secondChoiceUniversityId, + thirdChoiceUniversityId, + getRandomNickname() + ); + newApplication.setVerifyStatus(VerifyStatus.APPROVED); applicationRepository.save(newApplication); + return ApplicationSubmissionResponse.from(newApplication); } diff --git a/src/main/java/com/example/solidconnection/university/repository/UniversityInfoForApplyRepository.java b/src/main/java/com/example/solidconnection/university/repository/UniversityInfoForApplyRepository.java index 58fb7aeec..73a5ee14b 100644 --- a/src/main/java/com/example/solidconnection/university/repository/UniversityInfoForApplyRepository.java +++ b/src/main/java/com/example/solidconnection/university/repository/UniversityInfoForApplyRepository.java @@ -22,7 +22,12 @@ public interface UniversityInfoForApplyRepository extends JpaRepository findFirstByKoreanNameAndTerm(String koreanName, String term); - @Query("SELECT c FROM UniversityInfoForApply c WHERE c.university IN :universities AND c.term = :term") + @Query(""" + SELECT uifa + FROM UniversityInfoForApply uifa + WHERE uifa.university IN :universities + AND uifa.term = :term + """) List findByUniversitiesAndTerm(@Param("universities") List universities, @Param("term") String term); @Query(""" @@ -49,7 +54,8 @@ OR u.region.code IN ( SELECT * FROM university_info_for_apply WHERE term = :term - ORDER BY RAND() LIMIT :limitNum + ORDER BY RAND() + LIMIT :limitNum """, nativeQuery = true) List findRandomByTerm(@Param("term") String term, @Param("limitNum") int limitNum); @@ -58,8 +64,23 @@ default UniversityInfoForApply getUniversityInfoForApplyById(Long id) { .orElseThrow(() -> new CustomException(UNIVERSITY_INFO_FOR_APPLY_NOT_FOUND)); } - default UniversityInfoForApply getUniversityInfoForApplyByIdAndTerm(Long id, String term) { - return findByIdAndTerm(id, term) - .orElseThrow(() -> new CustomException(UNIVERSITY_INFO_FOR_APPLY_NOT_FOUND_FOR_TERM)); - } -} + @Query(""" + SELECT DISTINCT uifa + FROM UniversityInfoForApply uifa + JOIN FETCH uifa.university u + JOIN FETCH u.country c + JOIN FETCH u.region r + WHERE uifa.id IN :ids + """) + List findAllByUniversityIds(@Param("ids") List ids); + + @Query(""" + SELECT DISTINCT uifa + FROM UniversityInfoForApply uifa + JOIN FETCH uifa.university u + JOIN FETCH u.country c + JOIN FETCH u.region r + WHERE u.id IN :universityIds + """) + List findByUniversityIdsWithUniversityAndLocation(@Param("universityIds") List universityIds); +} \ No newline at end of file diff --git a/src/main/java/com/example/solidconnection/university/repository/custom/UniversityFilterRepository.java b/src/main/java/com/example/solidconnection/university/repository/custom/UniversityFilterRepository.java index c35533877..93850a2f4 100644 --- a/src/main/java/com/example/solidconnection/university/repository/custom/UniversityFilterRepository.java +++ b/src/main/java/com/example/solidconnection/university/repository/custom/UniversityFilterRepository.java @@ -8,7 +8,7 @@ public interface UniversityFilterRepository { - List findByRegionCodeAndKeywords(String regionCode, List keywords); + List findByRegionCodeAndKeywords(String regionCode, List keywords); List findByRegionCodeAndKeywordsAndLanguageTestTypeAndTestScoreAndTerm( String regionCode, List keywords, LanguageTestType testType, String testScore, String term); diff --git a/src/main/java/com/example/solidconnection/university/repository/custom/UniversityFilterRepositoryImpl.java b/src/main/java/com/example/solidconnection/university/repository/custom/UniversityFilterRepositoryImpl.java index 25da850da..b01095996 100644 --- a/src/main/java/com/example/solidconnection/university/repository/custom/UniversityFilterRepositoryImpl.java +++ b/src/main/java/com/example/solidconnection/university/repository/custom/UniversityFilterRepositoryImpl.java @@ -5,8 +5,8 @@ import com.example.solidconnection.university.domain.LanguageTestType; import com.example.solidconnection.university.domain.QUniversity; import com.example.solidconnection.university.domain.QUniversityInfoForApply; -import com.example.solidconnection.university.domain.University; import com.example.solidconnection.university.domain.UniversityInfoForApply; +import com.example.solidconnection.university.domain.QLanguageRequirement; import com.querydsl.core.types.dsl.BooleanExpression; import com.querydsl.core.types.dsl.Expressions; import com.querydsl.core.types.dsl.StringPath; @@ -28,21 +28,28 @@ public UniversityFilterRepositoryImpl(EntityManager em) { } @Override - public List findByRegionCodeAndKeywords(String regionCode, List keywords) { + public List findByRegionCodeAndKeywords(String regionCode, List keywords) { + QUniversityInfoForApply universityInfoForApply = QUniversityInfoForApply.universityInfoForApply; QUniversity university = QUniversity.university; QCountry country = QCountry.country; QRegion region = QRegion.region; + QLanguageRequirement languageRequirement = QLanguageRequirement.languageRequirement; return queryFactory - .selectFrom(university) - .join(university.country, country) - .join(country.region, region) - .where(regionCodeEq(region, regionCode) - .and(countryOrUniversityContainsKeyword(country, university, keywords)) + .selectFrom(universityInfoForApply) + .join(universityInfoForApply.university, university).fetchJoin() + .join(university.country, country).fetchJoin() + .join(country.region, region).fetchJoin() + .leftJoin(universityInfoForApply.languageRequirements, languageRequirement).fetchJoin() + .where( + regionCodeEq(region, regionCode) + .and(countryOrUniversityContainsKeyword(country, university, keywords)) ) + .distinct() .fetch(); } + private BooleanExpression regionCodeEq(QRegion region, String regionCode) { if (regionCode == null || regionCode.isEmpty()) { return Expressions.asBoolean(true).isTrue(); @@ -69,7 +76,6 @@ private BooleanExpression createKeywordCondition(StringPath namePath, List findByRegionCodeAndKeywordsAndLanguageTestTypeAndTestScoreAndTerm( String regionCode, List keywords, LanguageTestType testType, String testScore, String term) { - QUniversity university = QUniversity.university; QCountry country = QCountry.country; QRegion region = QRegion.region; diff --git a/src/main/resources/db/migration/V13_add_application_index_and_delete_manny_to_one_mapping.sql b/src/main/resources/db/migration/V13_add_application_index_and_delete_manny_to_one_mapping.sql new file mode 100644 index 000000000..3f3018724 --- /dev/null +++ b/src/main/resources/db/migration/V13_add_application_index_and_delete_manny_to_one_mapping.sql @@ -0,0 +1,15 @@ +ALTER TABLE application RENAME COLUMN first_choice_university_id TO first_choice_university_info_for_apply_id; +ALTER TABLE application RENAME COLUMN second_choice_university_id TO second_choice_university_info_for_apply_id; +ALTER TABLE application RENAME COLUMN third_choice_university_id TO third_choice_university_info_for_apply_id; + +CREATE INDEX idx_app_user_term_delete + ON application(site_user_id, term, is_delete); + +CREATE INDEX idx_app_first_choice_search + ON application(verify_status, term, is_delete, first_choice_university_apply_info_id); + +CREATE INDEX idx_app_second_choice_search + ON application(verify_status, term, is_delete, second_choice_university_apply_info_id); + +CREATE INDEX idx_app_third_choice_search + ON application(verify_status, term, is_delete, third_choice_university_apply_info_id); diff --git a/src/test/java/com/example/solidconnection/application/fixture/ApplicationFixture.java b/src/test/java/com/example/solidconnection/application/fixture/ApplicationFixture.java index b2cbc6460..91dc29c59 100644 --- a/src/test/java/com/example/solidconnection/application/fixture/ApplicationFixture.java +++ b/src/test/java/com/example/solidconnection/application/fixture/ApplicationFixture.java @@ -20,9 +20,9 @@ public class ApplicationFixture { String term, Gpa gpa, LanguageTest languageTest, - UniversityInfoForApply firstChoiceUniversity, - UniversityInfoForApply secondChoiceUniversity, - UniversityInfoForApply thirdChoiceUniversity + Long firstChoiceUniversityApplyInfoId, + Long secondChoiceUniversityApplyInfoId, + Long thirdChoiceUniversityApplyInfoId ) { return applicationFixtureBuilder.application() .siteUser(siteUser) @@ -30,9 +30,9 @@ public class ApplicationFixture { .languageTest(languageTest) .nicknameForApply(nicknameForApply) .term(term) - .firstChoiceUniversity(firstChoiceUniversity) - .secondChoiceUniversity(secondChoiceUniversity) - .thirdChoiceUniversity(thirdChoiceUniversity) + .firstChoiceUniversityApplyInfoId(firstChoiceUniversityApplyInfoId) + .secondChoiceUniversityApplyInfoId(secondChoiceUniversityApplyInfoId) + .thirdChoiceUniversityApplyInfoId(thirdChoiceUniversityApplyInfoId) .create(); } } diff --git a/src/test/java/com/example/solidconnection/application/fixture/ApplicationFixtureBuilder.java b/src/test/java/com/example/solidconnection/application/fixture/ApplicationFixtureBuilder.java index 5f6c06741..c9bf33c3e 100644 --- a/src/test/java/com/example/solidconnection/application/fixture/ApplicationFixtureBuilder.java +++ b/src/test/java/com/example/solidconnection/application/fixture/ApplicationFixtureBuilder.java @@ -18,9 +18,9 @@ public class ApplicationFixtureBuilder { private Gpa gpa; private LanguageTest languageTest; - private UniversityInfoForApply firstChoiceUniversity; - private UniversityInfoForApply secondChoiceUniversity; - private UniversityInfoForApply thirdChoiceUniversity; + private Long firstChoiceUniversityApplyInfoId; + private Long secondChoiceUniversityApplyInfoId; + private Long thirdChoiceUniversityApplyInfoId; private SiteUser siteUser; private String nicknameForApply; private String term; @@ -39,18 +39,18 @@ public ApplicationFixtureBuilder languageTest(LanguageTest languageTest) { return this; } - public ApplicationFixtureBuilder firstChoiceUniversity(UniversityInfoForApply firstChoiceUniversity) { - this.firstChoiceUniversity = firstChoiceUniversity; + public ApplicationFixtureBuilder firstChoiceUniversityApplyInfoId(Long firstChoiceUniversityApplyInfoId) { + this.firstChoiceUniversityApplyInfoId = firstChoiceUniversityApplyInfoId; return this; } - public ApplicationFixtureBuilder secondChoiceUniversity(UniversityInfoForApply secondChoiceUniversity) { - this.secondChoiceUniversity = secondChoiceUniversity; + public ApplicationFixtureBuilder secondChoiceUniversityApplyInfoId(Long secondChoiceUniversityApplyInfoId) { + this.secondChoiceUniversityApplyInfoId = secondChoiceUniversityApplyInfoId; return this; } - public ApplicationFixtureBuilder thirdChoiceUniversity(UniversityInfoForApply thirdChoiceUniversity) { - this.thirdChoiceUniversity = thirdChoiceUniversity; + public ApplicationFixtureBuilder thirdChoiceUniversityApplyInfoId(Long thirdChoiceUniversityApplyInfoId) { + this.thirdChoiceUniversityApplyInfoId = thirdChoiceUniversityApplyInfoId; return this; } @@ -75,9 +75,9 @@ public Application create() { gpa, languageTest, term, - firstChoiceUniversity, - secondChoiceUniversity, - thirdChoiceUniversity, + firstChoiceUniversityApplyInfoId, + secondChoiceUniversityApplyInfoId, + thirdChoiceUniversityApplyInfoId, nicknameForApply ); application.setVerifyStatus(VerifyStatus.APPROVED); diff --git a/src/test/java/com/example/solidconnection/application/service/ApplicationQueryServiceTest.java b/src/test/java/com/example/solidconnection/application/service/ApplicationQueryServiceTest.java index ac0b83619..1a0f927a2 100644 --- a/src/test/java/com/example/solidconnection/application/service/ApplicationQueryServiceTest.java +++ b/src/test/java/com/example/solidconnection/application/service/ApplicationQueryServiceTest.java @@ -106,7 +106,7 @@ class 지원자_목록_조회_테스트 { term, gpaScore1.getGpa(), languageTestScore1.getLanguageTest(), - 괌대학_A_지원_정보, + 괌대학_A_지원_정보.getId(), null, null ); @@ -116,7 +116,7 @@ class 지원자_목록_조회_테스트 { term, gpaScore2.getGpa(), languageTestScore2.getLanguageTest(), - 괌대학_B_지원_정보, + 괌대학_B_지원_정보.getId(), null, null ); @@ -126,7 +126,7 @@ class 지원자_목록_조회_테스트 { term, gpaScore3.getGpa(), languageTestScore3.getLanguageTest(), - 서던덴마크대학교_지원_정보, + 서던덴마크대학교_지원_정보.getId(), null, null ); @@ -141,11 +141,11 @@ class 지원자_목록_조회_테스트 { // then assertThat(response.firstChoice()).containsAll(List.of( UniversityApplicantsResponse.of(괌대학_A_지원_정보, - List.of(ApplicantResponse.of(application1, true))), + List.of(application1), user1), UniversityApplicantsResponse.of(괌대학_B_지원_정보, - List.of(ApplicantResponse.of(application2, false))), + List.of(application2), user1), UniversityApplicantsResponse.of(서던덴마크대학교_지원_정보, - List.of(ApplicantResponse.of(application3, false))) + List.of(application3), user1) )); } @@ -158,7 +158,7 @@ class 지원자_목록_조회_테스트 { term, gpaScore1.getGpa(), languageTestScore1.getLanguageTest(), - 괌대학_A_지원_정보, + 괌대학_A_지원_정보.getId(), null, null ); @@ -168,7 +168,7 @@ class 지원자_목록_조회_테스트 { term, gpaScore2.getGpa(), languageTestScore2.getLanguageTest(), - 괌대학_B_지원_정보, + 괌대학_B_지원_정보.getId(), null, null ); @@ -178,7 +178,7 @@ class 지원자_목록_조회_테스트 { term, gpaScore3.getGpa(), languageTestScore3.getLanguageTest(), - 서던덴마크대학교_지원_정보, + 서던덴마크대학교_지원_정보.getId(), null, null ); @@ -193,9 +193,9 @@ class 지원자_목록_조회_테스트 { // then assertThat(response.firstChoice()).containsExactlyInAnyOrder( UniversityApplicantsResponse.of(괌대학_A_지원_정보, - List.of(ApplicantResponse.of(application1, true))), + List.of(application1), user1), UniversityApplicantsResponse.of(괌대학_B_지원_정보, - List.of(ApplicantResponse.of(application2, false))) + List.of(application2), user1) ); } @@ -208,7 +208,7 @@ class 지원자_목록_조회_테스트 { term, gpaScore1.getGpa(), languageTestScore1.getLanguageTest(), - 괌대학_A_지원_정보, + 괌대학_A_지원_정보.getId(), null, null ); @@ -218,7 +218,7 @@ class 지원자_목록_조회_테스트 { term, gpaScore2.getGpa(), languageTestScore2.getLanguageTest(), - 괌대학_B_지원_정보, + 괌대학_B_지원_정보.getId(), null, null ); @@ -228,7 +228,7 @@ class 지원자_목록_조회_테스트 { term, gpaScore3.getGpa(), languageTestScore3.getLanguageTest(), - 서던덴마크대학교_지원_정보, + 서던덴마크대학교_지원_정보.getId(), null, null ); @@ -243,9 +243,9 @@ class 지원자_목록_조회_테스트 { // then assertThat(response.firstChoice()).containsExactlyInAnyOrder( UniversityApplicantsResponse.of(괌대학_A_지원_정보, - List.of(ApplicantResponse.of(application1, true))), + List.of(application1), user1), UniversityApplicantsResponse.of(괌대학_B_지원_정보, - List.of(ApplicantResponse.of(application2, false))) + List.of(application2), user1) ); } @@ -258,7 +258,7 @@ class 지원자_목록_조회_테스트 { "1988-1", gpaScore1.getGpa(), languageTestScore1.getLanguageTest(), - 괌대학_A_지원_정보, + 괌대학_A_지원_정보.getId(), null, null ); @@ -273,7 +273,7 @@ class 지원자_목록_조회_테스트 { // then assertThat(response.firstChoice()).doesNotContainAnyElementsOf(List.of( UniversityApplicantsResponse.of(괌대학_A_지원_정보, - List.of(ApplicantResponse.of(application, true))) + List.of(application), user1) )); } @@ -286,7 +286,7 @@ class 지원자_목록_조회_테스트 { term, gpaScore1.getGpa(), languageTestScore1.getLanguageTest(), - 괌대학_A_지원_정보, + 괌대학_A_지원_정보.getId(), null, null ); @@ -298,7 +298,7 @@ class 지원자_목록_조회_테스트 { term, gpaScore1.getGpa(), languageTestScore1.getLanguageTest(), - 괌대학_B_지원_정보, + 괌대학_B_지원_정보.getId(), null, null ); @@ -320,7 +320,6 @@ class 지원자_목록_조회_테스트 { @Nested class 경쟁자_목록_조회_테스트 { - @Test void 이번_학기_지원한_대학의_경쟁자_목록을_조회한다() { // given @@ -330,7 +329,7 @@ class 경쟁자_목록_조회_테스트 { term, gpaScore1.getGpa(), languageTestScore1.getLanguageTest(), - 괌대학_A_지원_정보, + 괌대학_A_지원_정보.getId(), null, null ); @@ -340,7 +339,7 @@ class 경쟁자_목록_조회_테스트 { term, gpaScore2.getGpa(), languageTestScore2.getLanguageTest(), - 괌대학_A_지원_정보, + 괌대학_A_지원_정보.getId(), null, null ); @@ -350,7 +349,7 @@ class 경쟁자_목록_조회_테스트 { term, gpaScore3.getGpa(), languageTestScore3.getLanguageTest(), - 괌대학_B_지원_정보, + 서던덴마크대학교_지원_정보.getId(), null, null ); @@ -359,10 +358,8 @@ class 경쟁자_목록_조회_테스트 { // then assertThat(response.firstChoice()).containsExactlyInAnyOrder( - UniversityApplicantsResponse.of(괌대학_A_지원_정보, List.of( - ApplicantResponse.of(application1, true), - ApplicantResponse.of(application2, false) - )) + UniversityApplicantsResponse.of(괌대학_A_지원_정보, + List.of(application1, application2), user1) ); } @@ -375,47 +372,43 @@ class 경쟁자_목록_조회_테스트 { term, gpaScore1.getGpa(), languageTestScore1.getLanguageTest(), - 괌대학_A_지원_정보, + 괌대학_A_지원_정보.getId(), null, null ); - applicationFixture.지원서( + Application application2 = applicationFixture.지원서( user2, "nickname2", term, gpaScore2.getGpa(), languageTestScore2.getLanguageTest(), - null, - 괌대학_B_지원_정보, - null + 괌대학_A_지원_정보.getId(), + 괌대학_B_지원_정보.getId(), + 서던덴마크대학교_지원_정보.getId() ); - applicationFixture.지원서( + Application application3 = applicationFixture.지원서( user3, "nickname3", term, gpaScore3.getGpa(), languageTestScore3.getLanguageTest(), + 서던덴마크대학교_지원_정보.getId(), null, - null, - 서던덴마크대학교_지원_정보 + null ); // when ApplicationsResponse response = applicationQueryService.getApplicantsByUserApplications(user1); // then - assertThat(response.firstChoice()).containsExactlyInAnyOrder( - UniversityApplicantsResponse.of(괌대학_A_지원_정보, - List.of(ApplicantResponse.of(application1, true))) - ); - - assertThat(response.secondChoice()).containsExactlyInAnyOrder( - UniversityApplicantsResponse.of(괌대학_A_지원_정보, List.of()) - ); - - assertThat(response.thirdChoice()).containsExactlyInAnyOrder( - UniversityApplicantsResponse.of(괌대학_A_지원_정보, List.of()) - ); + assertThat(response.firstChoice()) + .hasSize(1) + .allSatisfy(uar -> { + assertThat(uar.koreanName()).isEqualTo(괌대학_A_지원_정보.getKoreanName()); + assertThat(uar.applicants()) + .extracting(ApplicantResponse::nicknameForApply) + .containsExactlyInAnyOrder("nickname1", "nickname2"); + }); } } } diff --git a/src/test/java/com/example/solidconnection/application/service/ApplicationSubmissionServiceTest.java b/src/test/java/com/example/solidconnection/application/service/ApplicationSubmissionServiceTest.java index f70cd9fc0..2718fb90b 100644 --- a/src/test/java/com/example/solidconnection/application/service/ApplicationSubmissionServiceTest.java +++ b/src/test/java/com/example/solidconnection/application/service/ApplicationSubmissionServiceTest.java @@ -86,12 +86,18 @@ void setUp() { // then Application savedApplication = applicationRepository.findBySiteUserAndTerm(user, term).orElseThrow(); assertAll( - () -> assertThat(response.applyCount()).isEqualTo(savedApplication.getUpdateCount()), - () -> assertThat(savedApplication.getVerifyStatus()).isEqualTo(VerifyStatus.APPROVED), - () -> assertThat(savedApplication.isDelete()).isFalse(), - () -> assertThat(savedApplication.getFirstChoiceUniversity().getId()).isEqualTo(괌대학_A_지원_정보.getId()), - () -> assertThat(savedApplication.getSecondChoiceUniversity().getId()).isEqualTo(괌대학_B_지원_정보.getId()), - () -> assertThat(savedApplication.getThirdChoiceUniversity().getId()).isEqualTo(서던덴마크대학교_지원_정보.getId()) + () -> assertThat(response.applyCount()) + .isEqualTo(savedApplication.getUpdateCount()), + () -> assertThat(savedApplication.getVerifyStatus()) + .isEqualTo(VerifyStatus.APPROVED), + () -> assertThat(savedApplication.isDelete()) + .isFalse(), + () -> assertThat(savedApplication.getFirstChoiceUnivApplyInfoId()) + .isEqualTo(괌대학_A_지원_정보.getId()), + () -> assertThat(savedApplication.getSecondChoiceUnivApplyInfoId()) + .isEqualTo(괌대학_B_지원_정보.getId()), + () -> assertThat(savedApplication.getThirdChoiceUnivApplyInfoId()) + .isEqualTo(서던덴마크대학교_지원_정보.getId()) ); }