From 313002b1fc6e4997db9f41504ff933b9d460494d Mon Sep 17 00:00:00 2001 From: whqtker Date: Tue, 9 Jun 2026 15:13:24 +0900 Subject: [PATCH 01/22] =?UTF-8?q?chore:=20application=5Fchoice=20=ED=85=8C?= =?UTF-8?q?=EC=9D=B4=EB=B8=94=20=EC=B6=94=EA=B0=80=20=EB=B0=8F=20=EC=A7=80?= =?UTF-8?q?=EB=A7=9D=20=EC=88=98=20=EC=BB=AC=EB=9F=BC=20=EC=B6=94=EA=B0=80?= =?UTF-8?q?=ED=95=98=EB=8A=94=20=EC=8A=A4=ED=81=AC=EB=A6=BD=ED=8A=B8=20?= =?UTF-8?q?=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../db/migration/V50__dynamic_choice_count.sql | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 src/main/resources/db/migration/V50__dynamic_choice_count.sql diff --git a/src/main/resources/db/migration/V50__dynamic_choice_count.sql b/src/main/resources/db/migration/V50__dynamic_choice_count.sql new file mode 100644 index 00000000..22f09075 --- /dev/null +++ b/src/main/resources/db/migration/V50__dynamic_choice_count.sql @@ -0,0 +1,12 @@ +ALTER TABLE home_university + ADD COLUMN max_choice_count INT NOT NULL DEFAULT 3; + +CREATE TABLE application_choice +( + application_id BIGINT NOT NULL, + choice_order INT NOT NULL, + univ_apply_info_id BIGINT NOT NULL, + PRIMARY KEY (application_id, choice_order), + CONSTRAINT fk_app_choice_application + FOREIGN KEY (application_id) REFERENCES application (id) +); From e88da7654d79c3ae9c920b4812394b1bfbda9a44 Mon Sep 17 00:00:00 2001 From: whqtker Date: Tue, 9 Jun 2026 15:33:44 +0900 Subject: [PATCH 02/22] =?UTF-8?q?feat:=20ApplicationChoice=20=ED=81=B4?= =?UTF-8?q?=EB=9E=98=EC=8A=A4=20=EC=B6=94=EA=B0=80,=20=EC=B5=9C=EB=8C=80?= =?UTF-8?q?=20=EC=A7=80=EB=A7=9D=20=EC=88=98=20=ED=95=84=EB=93=9C=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/domain/Application.java | 61 ++++++------------- .../application/domain/ApplicationChoice.java | 24 ++++++++ .../university/domain/HomeUniversity.java | 6 +- 3 files changed, 47 insertions(+), 44 deletions(-) create mode 100644 src/main/java/com/example/solidconnection/application/domain/ApplicationChoice.java 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 14c13886..9e8628e7 100644 --- a/src/main/java/com/example/solidconnection/application/domain/Application.java +++ b/src/main/java/com/example/solidconnection/application/domain/Application.java @@ -5,16 +5,23 @@ import com.example.solidconnection.common.BaseEntity; import com.example.solidconnection.common.VerifyStatus; import com.example.solidconnection.siteuser.domain.SiteUser; +import jakarta.persistence.CollectionTable; import jakarta.persistence.Column; +import jakarta.persistence.ElementCollection; import jakarta.persistence.Embedded; import jakarta.persistence.Entity; import jakarta.persistence.EnumType; import jakarta.persistence.Enumerated; +import jakarta.persistence.FetchType; import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; import jakarta.persistence.Index; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.OrderBy; import jakarta.persistence.Table; +import java.util.ArrayList; +import java.util.List; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; @@ -29,13 +36,7 @@ @Entity @Table(indexes = { @Index(name = "idx_app_user_term_delete", - columnList = "site_user_id, term_id, is_delete"), - @Index(name = "idx_app_first_choice_search", - columnList = "verify_status, term_id, is_delete, first_choice_university_info_for_apply_id"), - @Index(name = "idx_app_second_choice_search", - columnList = "verify_status, term_id, is_delete, second_choice_university_info_for_apply_id"), - @Index(name = "idx_app_third_choice_search", - columnList = "verify_status, term_id, is_delete, third_choice_university_info_for_apply_id") + columnList = "site_user_id, term_id, is_delete") }) public class Application extends BaseEntity { @@ -70,18 +71,17 @@ public class Application extends BaseEntity { @Column(name = "is_delete", nullable = false) private boolean isDelete = false; - @Column(nullable = false, name = "first_choice_university_info_for_apply_id") - private long firstChoiceUnivApplyInfoId; - - @Column(name = "second_choice_university_info_for_apply_id") - private Long secondChoiceUnivApplyInfoId; - - @Column(name = "third_choice_university_info_for_apply_id") - private Long thirdChoiceUnivApplyInfoId; - @Column(name = "site_user_id", nullable = false) private long siteUserId; + @ElementCollection(fetch = FetchType.EAGER) + @CollectionTable( + name = "application_choice", + joinColumns = @JoinColumn(name = "application_id") + ) + @OrderBy("choiceOrder ASC") + private List choices = new ArrayList<>(); + public Application( SiteUser siteUser, Gpa gpa, @@ -101,39 +101,14 @@ public Application( LanguageTest languageTest, long termId, Integer updateCount, - long firstChoiceUnivApplyInfoId, - Long secondChoiceUnivApplyInfoId, - Long thirdChoiceUnivApplyInfoId, + List choices, String nicknameForApply) { this.siteUserId = siteUser.getId(); this.gpa = gpa; this.languageTest = languageTest; this.termId = termId; this.updateCount = updateCount; - this.firstChoiceUnivApplyInfoId = firstChoiceUnivApplyInfoId; - this.secondChoiceUnivApplyInfoId = secondChoiceUnivApplyInfoId; - this.thirdChoiceUnivApplyInfoId = thirdChoiceUnivApplyInfoId; - this.nicknameForApply = nicknameForApply; - this.verifyStatus = PENDING; - } - - public Application( - SiteUser siteUser, - Gpa gpa, - LanguageTest languageTest, - long termId, - long firstChoiceUnivApplyInfoId, - Long secondChoiceUnivApplyInfoId, - Long thirdChoiceUnivApplyInfoId, - String nicknameForApply) { - this.siteUserId = siteUser.getId(); - this.gpa = gpa; - this.languageTest = languageTest; - this.termId = termId; - this.updateCount = 1; - this.firstChoiceUnivApplyInfoId = firstChoiceUnivApplyInfoId; - this.secondChoiceUnivApplyInfoId = secondChoiceUnivApplyInfoId; - this.thirdChoiceUnivApplyInfoId = thirdChoiceUnivApplyInfoId; + this.choices = new ArrayList<>(choices); this.nicknameForApply = nicknameForApply; this.verifyStatus = PENDING; } diff --git a/src/main/java/com/example/solidconnection/application/domain/ApplicationChoice.java b/src/main/java/com/example/solidconnection/application/domain/ApplicationChoice.java new file mode 100644 index 00000000..dfab460c --- /dev/null +++ b/src/main/java/com/example/solidconnection/application/domain/ApplicationChoice.java @@ -0,0 +1,24 @@ +package com.example.solidconnection.application.domain; + +import jakarta.persistence.Column; +import jakarta.persistence.Embeddable; +import lombok.Getter; + +@Embeddable +@Getter +public class ApplicationChoice { + + @Column(name = "choice_order", nullable = false) + private int choiceOrder; + + @Column(name = "univ_apply_info_id", nullable = false) + private long univApplyInfoId; + + protected ApplicationChoice() { + } + + public ApplicationChoice(int choiceOrder, long univApplyInfoId) { + this.choiceOrder = choiceOrder; + this.univApplyInfoId = univApplyInfoId; + } +} diff --git a/src/main/java/com/example/solidconnection/university/domain/HomeUniversity.java b/src/main/java/com/example/solidconnection/university/domain/HomeUniversity.java index 506491c0..aea0cb9d 100644 --- a/src/main/java/com/example/solidconnection/university/domain/HomeUniversity.java +++ b/src/main/java/com/example/solidconnection/university/domain/HomeUniversity.java @@ -25,7 +25,11 @@ public class HomeUniversity extends BaseEntity { @Column(name = "name", nullable = false, unique = true, length = 100) private String name; - public void update(String name) { + @Column(name = "max_choice_count", nullable = false) + private int maxChoiceCount; + + public void update(String name, int maxChoiceCount) { this.name = name; + this.maxChoiceCount = maxChoiceCount; } } From 4a1d2922cfa2a6227f7499aa49d4816ef0d39536 Mon Sep 17 00:00:00 2001 From: whqtker Date: Tue, 9 Jun 2026 15:40:14 +0900 Subject: [PATCH 03/22] =?UTF-8?q?chore:=20n=EC=A7=80=EB=A7=9D=20=EC=BB=AC?= =?UTF-8?q?=EB=9F=BC=20=EC=A0=9C=EC=95=BD=20=EC=A1=B0=EA=B1=B4=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 컬럼 복수 유지에 대한 임시 조치 --- src/main/resources/db/migration/V50__dynamic_choice_count.sql | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/resources/db/migration/V50__dynamic_choice_count.sql b/src/main/resources/db/migration/V50__dynamic_choice_count.sql index 22f09075..aee657b2 100644 --- a/src/main/resources/db/migration/V50__dynamic_choice_count.sql +++ b/src/main/resources/db/migration/V50__dynamic_choice_count.sql @@ -10,3 +10,6 @@ CREATE TABLE application_choice CONSTRAINT fk_app_choice_application FOREIGN KEY (application_id) REFERENCES application (id) ); + +ALTER TABLE application + MODIFY COLUMN first_choice_university_info_for_apply_id BIGINT NULL; From 011d40be88d4116de4d0b21ca4c8d8b2bbb72479 Mon Sep 17 00:00:00 2001 From: whqtker Date: Tue, 9 Jun 2026 15:42:49 +0900 Subject: [PATCH 04/22] =?UTF-8?q?feat:=20DTO=20=EB=B0=8F=20=EA=B2=80?= =?UTF-8?q?=EC=A6=9D=20=EB=A1=9C=EC=A7=81=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/dto/ApplicationsResponse.java | 5 +-- .../dto/UnivApplyInfoChoiceRequest.java | 11 ++---- .../dto/UnivApplyInfoResponse.java | 39 ++++++------------- .../common/exception/ErrorCode.java | 1 + .../ValidUnivApplyInfoChoiceValidator.java | 39 +++++-------------- 5 files changed, 26 insertions(+), 69 deletions(-) diff --git a/src/main/java/com/example/solidconnection/application/dto/ApplicationsResponse.java b/src/main/java/com/example/solidconnection/application/dto/ApplicationsResponse.java index 657c7c2c..793a26d6 100644 --- a/src/main/java/com/example/solidconnection/application/dto/ApplicationsResponse.java +++ b/src/main/java/com/example/solidconnection/application/dto/ApplicationsResponse.java @@ -2,9 +2,6 @@ import java.util.List; -public record ApplicationsResponse( - List firstChoice, - List secondChoice, - List thirdChoice) { +public record ApplicationsResponse(List> choices) { } diff --git a/src/main/java/com/example/solidconnection/application/dto/UnivApplyInfoChoiceRequest.java b/src/main/java/com/example/solidconnection/application/dto/UnivApplyInfoChoiceRequest.java index 449b1ca2..0de9eb85 100644 --- a/src/main/java/com/example/solidconnection/application/dto/UnivApplyInfoChoiceRequest.java +++ b/src/main/java/com/example/solidconnection/application/dto/UnivApplyInfoChoiceRequest.java @@ -2,17 +2,12 @@ import com.example.solidconnection.university.dto.validation.ValidUnivApplyInfoChoice; import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.List; @ValidUnivApplyInfoChoice public record UnivApplyInfoChoiceRequest( - @JsonProperty("firstChoiceUniversityId") - Long firstChoiceUnivApplyInfoId, - - @JsonProperty("secondChoiceUniversityId") - Long secondChoiceUnivApplyInfoId, - - @JsonProperty("thirdChoiceUniversityId") - Long thirdChoiceUnivApplyInfoId) { + @JsonProperty("choices") + List univApplyInfoIds) { } diff --git a/src/main/java/com/example/solidconnection/application/dto/UnivApplyInfoResponse.java b/src/main/java/com/example/solidconnection/application/dto/UnivApplyInfoResponse.java index 7755eea5..d7c6a1b1 100644 --- a/src/main/java/com/example/solidconnection/application/dto/UnivApplyInfoResponse.java +++ b/src/main/java/com/example/solidconnection/application/dto/UnivApplyInfoResponse.java @@ -1,41 +1,24 @@ package com.example.solidconnection.application.dto; -import static com.fasterxml.jackson.annotation.JsonInclude.Include.NON_NULL; - import com.example.solidconnection.application.domain.Application; +import com.example.solidconnection.application.domain.ApplicationChoice; import com.example.solidconnection.university.domain.UnivApplyInfo; -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.Comparator; import java.util.List; import java.util.Map; import java.util.stream.Collectors; -public record UnivApplyInfoResponse( - - @JsonProperty("firstChoiceUniversity") - String firstChoiceUnivApplyInfo, - - @JsonProperty("secondChoiceUniversity") - @JsonInclude(NON_NULL) - String secondChoiceUnivApplyInfo, - - @JsonProperty("thirdChoiceUniversity") - @JsonInclude(NON_NULL) - String thirdChoiceUnivApplyInfo) { +public record UnivApplyInfoResponse(List choices) { public static UnivApplyInfoResponse of(Application application, List univApplyInfos) { - Map univApplyInfoMap = univApplyInfos.stream() - .collect(Collectors.toMap( - UnivApplyInfo::getId, - UnivApplyInfo::getKoreanName - )); + Map nameById = univApplyInfos.stream() + .collect(Collectors.toMap(UnivApplyInfo::getId, UnivApplyInfo::getKoreanName)); + + List choiceNames = application.getChoices().stream() + .sorted(Comparator.comparingInt(ApplicationChoice::getChoiceOrder)) + .map(choice -> nameById.get(choice.getUnivApplyInfoId())) + .toList(); - return new UnivApplyInfoResponse( - univApplyInfoMap.get(application.getFirstChoiceUnivApplyInfoId()), - application.getSecondChoiceUnivApplyInfoId() != null - ? univApplyInfoMap.get(application.getSecondChoiceUnivApplyInfoId()) : null, - application.getThirdChoiceUnivApplyInfoId() != null - ? univApplyInfoMap.get(application.getThirdChoiceUnivApplyInfoId()) : null - ); + return new UnivApplyInfoResponse(choiceNames); } } diff --git a/src/main/java/com/example/solidconnection/common/exception/ErrorCode.java b/src/main/java/com/example/solidconnection/common/exception/ErrorCode.java index 8dc4ea70..bb33cf62 100644 --- a/src/main/java/com/example/solidconnection/common/exception/ErrorCode.java +++ b/src/main/java/com/example/solidconnection/common/exception/ErrorCode.java @@ -98,6 +98,7 @@ public enum ErrorCode { FIRST_CHOICE_REQUIRED(HttpStatus.BAD_REQUEST.value(), "1지망 대학교를 입력해주세요."), THIRD_CHOICE_REQUIRES_SECOND(HttpStatus.BAD_REQUEST.value(), "2지망 없이 3지망을 선택할 수 없습니다."), DUPLICATE_UNIV_APPLY_INFO_CHOICE(HttpStatus.BAD_REQUEST.value(), "지망 선택이 중복되었습니다."), + CHOICE_COUNT_EXCEEDS_LIMIT(HttpStatus.BAD_REQUEST.value(), "지망 수가 최대 지망 수를 초과했습니다."), // community INVALID_POST_CATEGORY(HttpStatus.BAD_REQUEST.value(), "잘못된 카테고리명입니다."), diff --git a/src/main/java/com/example/solidconnection/university/dto/validation/ValidUnivApplyInfoChoiceValidator.java b/src/main/java/com/example/solidconnection/university/dto/validation/ValidUnivApplyInfoChoiceValidator.java index 50069564..ff45b704 100644 --- a/src/main/java/com/example/solidconnection/university/dto/validation/ValidUnivApplyInfoChoiceValidator.java +++ b/src/main/java/com/example/solidconnection/university/dto/validation/ValidUnivApplyInfoChoiceValidator.java @@ -2,35 +2,30 @@ import static com.example.solidconnection.common.exception.ErrorCode.DUPLICATE_UNIV_APPLY_INFO_CHOICE; import static com.example.solidconnection.common.exception.ErrorCode.FIRST_CHOICE_REQUIRED; -import static com.example.solidconnection.common.exception.ErrorCode.THIRD_CHOICE_REQUIRES_SECOND; import com.example.solidconnection.application.dto.UnivApplyInfoChoiceRequest; import jakarta.validation.ConstraintValidator; import jakarta.validation.ConstraintValidatorContext; import java.util.HashSet; -import java.util.Objects; +import java.util.List; import java.util.Set; -import java.util.stream.Stream; -public class ValidUnivApplyInfoChoiceValidator implements ConstraintValidator { +public class ValidUnivApplyInfoChoiceValidator + implements ConstraintValidator { @Override public boolean isValid(UnivApplyInfoChoiceRequest request, ConstraintValidatorContext context) { context.disableDefaultConstraintViolation(); - if (isFirstChoiceNotSelected(request)) { - context.buildConstraintViolationWithTemplate(FIRST_CHOICE_REQUIRED.getMessage()) - .addConstraintViolation(); - return false; - } + List ids = request.univApplyInfoIds(); - if (isThirdChoiceWithoutSecond(request)) { - context.buildConstraintViolationWithTemplate(THIRD_CHOICE_REQUIRES_SECOND.getMessage()) + if (ids == null || ids.isEmpty()) { + context.buildConstraintViolationWithTemplate(FIRST_CHOICE_REQUIRED.getMessage()) .addConstraintViolation(); return false; } - if (isDuplicate(request)) { + if (hasDuplicate(ids)) { context.buildConstraintViolationWithTemplate(DUPLICATE_UNIV_APPLY_INFO_CHOICE.getMessage()) .addConstraintViolation(); return false; @@ -39,22 +34,8 @@ public boolean isValid(UnivApplyInfoChoiceRequest request, ConstraintValidatorCo return true; } - private boolean isFirstChoiceNotSelected(UnivApplyInfoChoiceRequest request) { - return request.firstChoiceUnivApplyInfoId() == null; - } - - private boolean isThirdChoiceWithoutSecond(UnivApplyInfoChoiceRequest request) { - return request.thirdChoiceUnivApplyInfoId() != null && request.secondChoiceUnivApplyInfoId() == null; - } - - private boolean isDuplicate(UnivApplyInfoChoiceRequest request) { - Set uniqueIds = new HashSet<>(); - return Stream.of( - request.firstChoiceUnivApplyInfoId(), - request.secondChoiceUnivApplyInfoId(), - request.thirdChoiceUnivApplyInfoId() - ) - .filter(Objects::nonNull) - .anyMatch(id -> !uniqueIds.add(id)); + private boolean hasDuplicate(List ids) { + Set unique = new HashSet<>(); + return ids.stream().anyMatch(id -> !unique.add(id)); } } From f6ac86c0a1b8cbd1709f336af47c328f1b27dfc8 Mon Sep 17 00:00:00 2001 From: whqtker Date: Tue, 9 Jun 2026 15:45:14 +0900 Subject: [PATCH 05/22] =?UTF-8?q?feat:=20JPQL=20=EC=BF=BC=EB=A6=AC=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../repository/ApplicationRepository.java | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) 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 f9dda309..0324cb46 100644 --- a/src/main/java/com/example/solidconnection/application/repository/ApplicationRepository.java +++ b/src/main/java/com/example/solidconnection/application/repository/ApplicationRepository.java @@ -16,20 +16,19 @@ public interface ApplicationRepository extends JpaRepository boolean existsByNicknameForApply(String nicknameForApply); @Query(""" - SELECT a + SELECT DISTINCT a FROM Application a - WHERE (a.firstChoiceUnivApplyInfoId IN :univApplyInfoIds - OR a.secondChoiceUnivApplyInfoId IN :univApplyInfoIds - OR a.thirdChoiceUnivApplyInfoId IN :univApplyInfoIds) + JOIN a.choices c + WHERE c.univApplyInfoId IN :univApplyInfoIds AND a.verifyStatus = :status AND a.termId = :termId AND a.isDelete = false """) - List findAllByUnivApplyInfoIds(@Param("univApplyInfoIds") List univApplyInfoIds, @Param("status") VerifyStatus status, @Param("termId") long termId); + List findAllByUnivApplyInfoIds( + @Param("univApplyInfoIds") List univApplyInfoIds, + @Param("status") VerifyStatus status, + @Param("termId") long termId); - // TODO: 근본 해결 필요 - // 지원서 유일성은 DB 제약으로 강제하고 - // 이 조회는 임시 회피 로직을 제거하는 방향으로 수정 필요. Optional findTopBySiteUserIdAndTermIdAndIsDeleteFalseOrderByIdDesc(long siteUserId, long termId); default Application getApplicationBySiteUserIdAndTermId(long siteUserId, long termId) { From fd83a348545df12d792c9282395d7e86a9c686c0 Mon Sep 17 00:00:00 2001 From: whqtker Date: Tue, 9 Jun 2026 15:51:45 +0900 Subject: [PATCH 06/22] =?UTF-8?q?feat:=20=EC=84=9C=EB=B9=84=EC=8A=A4=20?= =?UTF-8?q?=EB=A9=94=EC=84=9C=EB=93=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/ApplicationQueryService.java | 82 +++++++++---------- .../service/ApplicationSubmissionService.java | 54 +++++++----- 2 files changed, 73 insertions(+), 63 deletions(-) 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 60a49571..9d0f33ad 100644 --- a/src/main/java/com/example/solidconnection/application/service/ApplicationQueryService.java +++ b/src/main/java/com/example/solidconnection/application/service/ApplicationQueryService.java @@ -5,6 +5,7 @@ import static com.example.solidconnection.common.exception.ErrorCode.USER_NOT_FOUND; import com.example.solidconnection.application.domain.Application; +import com.example.solidconnection.application.domain.ApplicationChoice; import com.example.solidconnection.application.dto.ApplicantsResponse; import com.example.solidconnection.application.dto.ApplicationsResponse; import com.example.solidconnection.application.repository.ApplicationRepository; @@ -22,10 +23,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.Objects; -import java.util.function.Function; import java.util.stream.Collectors; -import java.util.stream.Stream; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -40,10 +38,8 @@ public class ApplicationQueryService { private final SiteUserRepository siteUserRepository; private final TermRepository termRepository; - // todo: 캐싱 정책 변경 시 수정 필요 @Transactional(readOnly = true) public ApplicationsResponse getApplicants(long siteUserId, String regionCode, String keyword) { - // 1. 대학 지원 정보 필터링 (regionCode, keyword) SiteUser siteUser = siteUserRepository.findById(siteUserId) .orElseThrow(() -> new CustomException(USER_NOT_FOUND)); List keywords = StringUtils.isNotBlank(keyword) ? List.of(keyword) : List.of(); @@ -51,17 +47,18 @@ public ApplicationsResponse getApplicants(long siteUserId, String regionCode, St Term term = termRepository.findByIsCurrentTrue() .orElseThrow(() -> new CustomException(CURRENT_TERM_NOT_FOUND)); - List univApplyInfos = universityFilterRepository.findAllByRegionCodeAndKeywordsAndTermId(regionCode, keywords, term.getId()); + List univApplyInfos = universityFilterRepository + .findAllByRegionCodeAndKeywordsAndTermId(regionCode, keywords, term.getId()); if (univApplyInfos.isEmpty()) { - return new ApplicationsResponse(List.of(), List.of(), List.of()); + return new ApplicationsResponse(List.of()); } - // 2. 조건에 맞는 모든 Application 한 번에 조회 List univApplyInfoIds = univApplyInfos.stream() .map(UnivApplyInfo::getId) .toList(); - List applications = applicationRepository.findAllByUnivApplyInfoIds(univApplyInfoIds, VerifyStatus.APPROVED, term.getId()); - // 3. 지원서 분류 및 DTO 변환 + List applications = applicationRepository + .findAllByUnivApplyInfoIds(univApplyInfoIds, VerifyStatus.APPROVED, term.getId()); + return classifyApplicationsByChoice(univApplyInfos, applications, siteUser); } @@ -73,21 +70,20 @@ public ApplicationsResponse getApplicantsByUserApplications(long siteUserId) { Term term = termRepository.findByIsCurrentTrue() .orElseThrow(() -> new CustomException(CURRENT_TERM_NOT_FOUND)); - Application userLatestApplication = applicationRepository.getApplicationBySiteUserIdAndTermId(siteUser.getId(), term.getId()); + Application userLatestApplication = applicationRepository + .getApplicationBySiteUserIdAndTermId(siteUser.getId(), term.getId()); - List univApplyInfoIds = Stream.of( - userLatestApplication.getFirstChoiceUnivApplyInfoId(), - userLatestApplication.getSecondChoiceUnivApplyInfoId(), - userLatestApplication.getThirdChoiceUnivApplyInfoId() - ) - .filter(Objects::nonNull) + List univApplyInfoIds = userLatestApplication.getChoices().stream() + .map(ApplicationChoice::getUnivApplyInfoId) + .distinct() .collect(Collectors.toList()); if (univApplyInfoIds.isEmpty()) { - return new ApplicationsResponse(List.of(), List.of(), List.of()); + return new ApplicationsResponse(List.of()); } - List applications = applicationRepository.findAllByUnivApplyInfoIds(univApplyInfoIds, VerifyStatus.APPROVED, term.getId()); + List applications = applicationRepository + .findAllByUnivApplyInfoIds(univApplyInfoIds, VerifyStatus.APPROVED, term.getId()); List univApplyInfos = univApplyInfoRepository.findAllByIds(univApplyInfoIds); return classifyApplicationsByChoice(univApplyInfos, applications, siteUser); @@ -97,33 +93,32 @@ private ApplicationsResponse classifyApplicationsByChoice( List univApplyInfos, 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(univApplyInfos, firstChoiceMap, siteUser); - List secondChoiceApplicants = - createUniversityApplicantsResponses(univApplyInfos, secondChoiceMap, siteUser); - List thirdChoiceApplicants = - createUniversityApplicantsResponses(univApplyInfos, thirdChoiceMap, siteUser); - - return new ApplicationsResponse(firstChoiceApplicants, secondChoiceApplicants, thirdChoiceApplicants); + int maxOrder = applications.stream() + .flatMap(a -> a.getChoices().stream()) + .mapToInt(ApplicationChoice::getChoiceOrder) + .max() + .orElse(0); + + List> allChoices = new ArrayList<>(); + for (int order = 1; order <= maxOrder; order++) { + final int choiceOrder = order; + Map> choiceMap = buildChoiceMapForOrder(applications, choiceOrder); + allChoices.add(createUniversityApplicantsResponses(univApplyInfos, choiceMap, siteUser)); + } + return new ApplicationsResponse(allChoices); } - private Map> createChoiceMap( - List applications, - Function choiceIdExtractor) { - Map> choiceMap = new HashMap<>(); - + private Map> buildChoiceMapForOrder(List applications, int order) { + Map> map = new HashMap<>(); for (Application application : applications) { - Long choiceId = choiceIdExtractor.apply(application); - if (choiceId != null) { - choiceMap.computeIfAbsent(choiceId, k -> new ArrayList<>()).add(application); - } + application.getChoices().stream() + .filter(c -> c.getChoiceOrder() == order) + .findFirst() + .ifPresent(choice -> map + .computeIfAbsent(choice.getUnivApplyInfoId(), k -> new ArrayList<>()) + .add(application)); } - - return choiceMap; + return map; } private List createUniversityApplicantsResponses( @@ -143,7 +138,8 @@ public void validateSiteUserCanViewApplicants(long siteUserId) { Term term = termRepository.findByIsCurrentTrue() .orElseThrow(() -> new CustomException(CURRENT_TERM_NOT_FOUND)); - VerifyStatus verifyStatus = applicationRepository.getApplicationBySiteUserIdAndTermId(siteUser.getId(), term.getId()).getVerifyStatus(); + VerifyStatus verifyStatus = applicationRepository + .getApplicationBySiteUserIdAndTermId(siteUser.getId(), term.getId()).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 e8c1144b..25cae958 100644 --- a/src/main/java/com/example/solidconnection/application/service/ApplicationSubmissionService.java +++ b/src/main/java/com/example/solidconnection/application/service/ApplicationSubmissionService.java @@ -1,6 +1,7 @@ package com.example.solidconnection.application.service; import static com.example.solidconnection.common.exception.ErrorCode.APPLY_UPDATE_LIMIT_EXCEED; +import static com.example.solidconnection.common.exception.ErrorCode.CHOICE_COUNT_EXCEEDS_LIMIT; import static com.example.solidconnection.common.exception.ErrorCode.CURRENT_TERM_NOT_FOUND; import static com.example.solidconnection.common.exception.ErrorCode.GPA_SCORE_NOT_FOUND; import static com.example.solidconnection.common.exception.ErrorCode.INVALID_GPA_SCORE_STATUS; @@ -9,6 +10,7 @@ import static com.example.solidconnection.common.exception.ErrorCode.USER_NOT_FOUND; import com.example.solidconnection.application.domain.Application; +import com.example.solidconnection.application.domain.ApplicationChoice; import com.example.solidconnection.application.dto.ApplicationSubmissionResponse; import com.example.solidconnection.application.dto.ApplyRequest; import com.example.solidconnection.application.dto.UnivApplyInfoChoiceRequest; @@ -24,11 +26,11 @@ import com.example.solidconnection.term.domain.Term; import com.example.solidconnection.term.repository.TermRepository; import com.example.solidconnection.university.domain.UnivApplyInfo; +import com.example.solidconnection.university.repository.HomeUniversityRepository; import com.example.solidconnection.university.repository.UnivApplyInfoRepository; import java.util.List; -import java.util.Objects; import java.util.Optional; -import java.util.stream.Stream; +import java.util.stream.IntStream; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -38,6 +40,7 @@ public class ApplicationSubmissionService { public static final int APPLICATION_UPDATE_COUNT_LIMIT = 3; + private static final int DEFAULT_MAX_CHOICE_COUNT = 3; private final ApplicationRepository applicationRepository; private final GpaScoreRepository gpaScoreRepository; @@ -45,22 +48,22 @@ public class ApplicationSubmissionService { private final SiteUserRepository siteUserRepository; private final TermRepository termRepository; private final UnivApplyInfoRepository univApplyInfoRepository; + private final HomeUniversityRepository homeUniversityRepository; - // 학점 및 어학성적이 모두 유효한 경우에만 지원서 등록이 가능하다. - // 기존에 있던 status field 우선 APRROVED로 입력시킨다. @Transactional public ApplicationSubmissionResponse apply(long siteUserId, ApplyRequest applyRequest) { SiteUser siteUser = siteUserRepository.findById(siteUserId) .orElseThrow(() -> new CustomException(USER_NOT_FOUND)); - UnivApplyInfoChoiceRequest univApplyInfoChoiceRequest = applyRequest.univApplyInfoChoiceRequest(); + UnivApplyInfoChoiceRequest choiceRequest = applyRequest.univApplyInfoChoiceRequest(); GpaScore gpaScore = getValidGpaScore(siteUser, applyRequest.gpaScoreId()); LanguageTestScore languageTestScore = getValidLanguageTestScore(siteUser, applyRequest.languageTestScoreId()); Term term = termRepository.findByIsCurrentTrue() .orElseThrow(() -> new CustomException(CURRENT_TERM_NOT_FOUND)); - Long firstChoiceUnivApplyInfoId = univApplyInfoChoiceRequest.firstChoiceUnivApplyInfoId(); - Long secondChoiceUnivApplyInfoId = univApplyInfoChoiceRequest.secondChoiceUnivApplyInfoId(); - Long thirdChoiceUnivApplyInfoId = univApplyInfoChoiceRequest.thirdChoiceUnivApplyInfoId(); + int maxChoiceCount = resolveMaxChoiceCount(siteUser); + validateChoiceCount(choiceRequest, maxChoiceCount); + + List choices = buildChoices(choiceRequest.univApplyInfoIds()); Optional existingApplication = applicationRepository.findTopBySiteUserIdAndTermIdAndIsDeleteFalseOrderByIdDesc(siteUser.getId(), term.getId()); @@ -78,28 +81,39 @@ public ApplicationSubmissionResponse apply(long siteUserId, ApplyRequest applyRe languageTestScore.getLanguageTest(), term.getId(), updateCount, - firstChoiceUnivApplyInfoId, - secondChoiceUnivApplyInfoId, - thirdChoiceUnivApplyInfoId, + choices, getRandomNickname() ); newApplication.setVerifyStatus(VerifyStatus.APPROVED); applicationRepository.save(newApplication); - List univApplyInfoIds = Stream.of( - firstChoiceUnivApplyInfoId, - secondChoiceUnivApplyInfoId, - thirdChoiceUnivApplyInfoId - ) - .filter(Objects::nonNull) - .toList(); - - List uniApplyInfos = univApplyInfoRepository.findAllByIds(univApplyInfoIds); + List uniApplyInfos = univApplyInfoRepository.findAllByIds(choiceRequest.univApplyInfoIds()); return ApplicationSubmissionResponse.of(APPLICATION_UPDATE_COUNT_LIMIT, newApplication, uniApplyInfos); } + private int resolveMaxChoiceCount(SiteUser siteUser) { + if (siteUser.getHomeUniversityId() == null) { + return DEFAULT_MAX_CHOICE_COUNT; + } + return homeUniversityRepository.findById(siteUser.getHomeUniversityId()) + .map(hu -> hu.getMaxChoiceCount()) + .orElse(DEFAULT_MAX_CHOICE_COUNT); + } + + private void validateChoiceCount(UnivApplyInfoChoiceRequest request, int maxChoiceCount) { + if (request.univApplyInfoIds().size() > maxChoiceCount) { + throw new CustomException(CHOICE_COUNT_EXCEEDS_LIMIT); + } + } + + private List buildChoices(List univApplyInfoIds) { + return IntStream.range(0, univApplyInfoIds.size()) + .mapToObj(i -> new ApplicationChoice(i + 1, univApplyInfoIds.get(i))) + .toList(); + } + private GpaScore getValidGpaScore(SiteUser siteUser, Long gpaScoreId) { GpaScore gpaScore = gpaScoreRepository.findGpaScoreBySiteUserIdAndId(siteUser.getId(), gpaScoreId) .orElseThrow(() -> new CustomException(GPA_SCORE_NOT_FOUND)); From 7fc96b2537f73cd3fcb2049d8aa1f8db4e017e16 Mon Sep 17 00:00:00 2001 From: whqtker Date: Tue, 9 Jun 2026 15:55:23 +0900 Subject: [PATCH 07/22] =?UTF-8?q?feat:=20=EC=96=B4=EB=93=9C=EB=AF=BC=20?= =?UTF-8?q?=EA=B5=AD=EB=82=B4=20=EB=8C=80=ED=95=99=20=EC=82=BD=EC=9E=85=20?= =?UTF-8?q?=EC=8B=9C=20=EC=B5=9C=EB=8C=80=20=EC=A7=80=EB=A7=9D=20=EC=88=98?= =?UTF-8?q?=20=EA=B4=80=EB=A0=A8=20DTO=EC=97=90=20=EC=A0=9C=EC=95=BD?= =?UTF-8?q?=EC=A1=B0=EA=B1=B4=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../university/dto/AdminHomeUniversityCreateRequest.java | 8 +++++++- .../admin/university/dto/AdminHomeUniversityResponse.java | 6 ++++-- .../university/dto/AdminHomeUniversityUpdateRequest.java | 8 +++++++- 3 files changed, 18 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/example/solidconnection/admin/university/dto/AdminHomeUniversityCreateRequest.java b/src/main/java/com/example/solidconnection/admin/university/dto/AdminHomeUniversityCreateRequest.java index 14df833e..0e706509 100644 --- a/src/main/java/com/example/solidconnection/admin/university/dto/AdminHomeUniversityCreateRequest.java +++ b/src/main/java/com/example/solidconnection/admin/university/dto/AdminHomeUniversityCreateRequest.java @@ -1,12 +1,18 @@ package com.example.solidconnection.admin.university.dto; +import jakarta.validation.constraints.Min; import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Size; public record AdminHomeUniversityCreateRequest( @NotBlank(message = "협정 대학명은 필수입니다") @Size(max = 100, message = "협정 대학명은 100자 이하여야 합니다") - String name + String name, + + @NotNull(message = "최대 지망 수는 필수입니다") + @Min(value = 1, message = "최대 지망 수는 1 이상이어야 합니다") + Integer maxChoiceCount ) { } diff --git a/src/main/java/com/example/solidconnection/admin/university/dto/AdminHomeUniversityResponse.java b/src/main/java/com/example/solidconnection/admin/university/dto/AdminHomeUniversityResponse.java index 71918520..6842b490 100644 --- a/src/main/java/com/example/solidconnection/admin/university/dto/AdminHomeUniversityResponse.java +++ b/src/main/java/com/example/solidconnection/admin/university/dto/AdminHomeUniversityResponse.java @@ -4,13 +4,15 @@ public record AdminHomeUniversityResponse( long id, - String name + String name, + int maxChoiceCount ) { public static AdminHomeUniversityResponse from(HomeUniversity homeUniversity) { return new AdminHomeUniversityResponse( homeUniversity.getId(), - homeUniversity.getName() + homeUniversity.getName(), + homeUniversity.getMaxChoiceCount() ); } } diff --git a/src/main/java/com/example/solidconnection/admin/university/dto/AdminHomeUniversityUpdateRequest.java b/src/main/java/com/example/solidconnection/admin/university/dto/AdminHomeUniversityUpdateRequest.java index e2247309..5f5df8af 100644 --- a/src/main/java/com/example/solidconnection/admin/university/dto/AdminHomeUniversityUpdateRequest.java +++ b/src/main/java/com/example/solidconnection/admin/university/dto/AdminHomeUniversityUpdateRequest.java @@ -1,12 +1,18 @@ package com.example.solidconnection.admin.university.dto; +import jakarta.validation.constraints.Min; import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Size; public record AdminHomeUniversityUpdateRequest( @NotBlank(message = "협정 대학명은 필수입니다") @Size(max = 100, message = "협정 대학명은 100자 이하여야 합니다") - String name + String name, + + @NotNull(message = "최대 지망 수는 필수입니다") + @Min(value = 1, message = "최대 지망 수는 1 이상이어야 합니다") + Integer maxChoiceCount ) { } From 27b148e1526943310d4edf33ab1b7a77001e5688 Mon Sep 17 00:00:00 2001 From: whqtker Date: Tue, 9 Jun 2026 16:08:17 +0900 Subject: [PATCH 08/22] =?UTF-8?q?feat:=20=EB=8C=80=ED=95=99=20=EA=B2=80?= =?UTF-8?q?=EC=83=89=20=EC=9D=91=EB=8B=B5=20=ED=95=84=EB=93=9C=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../solidconnection/admin/dto/UnivApplyInfoResponse.java | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/example/solidconnection/admin/dto/UnivApplyInfoResponse.java b/src/main/java/com/example/solidconnection/admin/dto/UnivApplyInfoResponse.java index b56b49f5..d451ae05 100644 --- a/src/main/java/com/example/solidconnection/admin/dto/UnivApplyInfoResponse.java +++ b/src/main/java/com/example/solidconnection/admin/dto/UnivApplyInfoResponse.java @@ -1,9 +1,7 @@ package com.example.solidconnection.admin.dto; -public record UnivApplyInfoResponse( - String firstChoiceUnivName, - String secondChoiceUnivName, - String thirdChoiceUnivName -) { +import java.util.List; + +public record UnivApplyInfoResponse(List choices) { } From 2575ea2d127f0cd1ff9ebe16b74d62b068f38334 Mon Sep 17 00:00:00 2001 From: whqtker Date: Tue, 9 Jun 2026 16:09:33 +0900 Subject: [PATCH 09/22] =?UTF-8?q?feat:=20=EC=B5=9C=EB=8C=80=20=EC=A7=80?= =?UTF-8?q?=EB=A7=9D=20=EC=88=98=20=ED=8F=AC=ED=95=A8=ED=95=98=EB=8F=84?= =?UTF-8?q?=EB=A1=9D=20=EC=84=9C=EB=B9=84=EC=8A=A4=20=EB=A9=94=EC=84=9C?= =?UTF-8?q?=EB=93=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../admin/university/service/AdminHomeUniversityService.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/example/solidconnection/admin/university/service/AdminHomeUniversityService.java b/src/main/java/com/example/solidconnection/admin/university/service/AdminHomeUniversityService.java index bcf1a8ed..af3a10f3 100644 --- a/src/main/java/com/example/solidconnection/admin/university/service/AdminHomeUniversityService.java +++ b/src/main/java/com/example/solidconnection/admin/university/service/AdminHomeUniversityService.java @@ -48,7 +48,7 @@ public AdminHomeUniversityResponse getHomeUniversity(Long id) { ) public AdminHomeUniversityResponse createHomeUniversity(AdminHomeUniversityCreateRequest request) { validateNameNotExists(request.name()); - HomeUniversity homeUniversity = new HomeUniversity(null, request.name()); + HomeUniversity homeUniversity = new HomeUniversity(null, request.name(), request.maxChoiceCount()); return AdminHomeUniversityResponse.from(homeUniversityRepository.save(homeUniversity)); } @@ -69,7 +69,7 @@ public AdminHomeUniversityResponse updateHomeUniversity(Long id, AdminHomeUniver HomeUniversity homeUniversity = homeUniversityRepository.findById(id) .orElseThrow(() -> new CustomException(HOME_UNIVERSITY_NOT_FOUND)); validateNameNotDuplicated(request.name(), id); - homeUniversity.update(request.name()); + homeUniversity.update(request.name(), request.maxChoiceCount()); return AdminHomeUniversityResponse.from(homeUniversity); } From 2e954aa9ad227df4441f720693865e4785d07299 Mon Sep 17 00:00:00 2001 From: whqtker Date: Tue, 9 Jun 2026 16:10:48 +0900 Subject: [PATCH 10/22] =?UTF-8?q?feat:=20QueryDSL=20=EC=A1=B0=EC=9D=B8=20-?= =?UTF-8?q?>=20=EB=8F=99=EC=A0=81=20=EC=84=A0=ED=83=9D=EC=9C=BC=EB=A1=9C?= =?UTF-8?q?=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../custom/SiteUserFilterRepositoryImpl.java | 60 ++++++++++++------- 1 file changed, 38 insertions(+), 22 deletions(-) diff --git a/src/main/java/com/example/solidconnection/siteuser/repository/custom/SiteUserFilterRepositoryImpl.java b/src/main/java/com/example/solidconnection/siteuser/repository/custom/SiteUserFilterRepositoryImpl.java index caca1f01..dd840b0a 100644 --- a/src/main/java/com/example/solidconnection/siteuser/repository/custom/SiteUserFilterRepositoryImpl.java +++ b/src/main/java/com/example/solidconnection/siteuser/repository/custom/SiteUserFilterRepositoryImpl.java @@ -1,6 +1,7 @@ package com.example.solidconnection.siteuser.repository.custom; import static com.example.solidconnection.application.domain.QApplication.application; +import static com.example.solidconnection.university.domain.QUnivApplyInfo.univApplyInfo; import static com.example.solidconnection.mentor.domain.QMentor.mentor; import static com.example.solidconnection.mentor.domain.QMentorApplication.mentorApplication; import static com.example.solidconnection.mentor.domain.QMentoring.mentoring; @@ -26,9 +27,11 @@ import com.example.solidconnection.admin.dto.UserSearchCondition; import com.example.solidconnection.admin.dto.UserSearchResponse; import com.example.solidconnection.siteuser.domain.Role; +import com.example.solidconnection.application.domain.Application; +import com.example.solidconnection.application.domain.ApplicationChoice; import com.example.solidconnection.siteuser.domain.SiteUser; import com.example.solidconnection.siteuser.domain.UserStatus; -import com.example.solidconnection.university.domain.QUnivApplyInfo; +import com.querydsl.core.Tuple; import com.querydsl.core.types.ConstructorExpression; import com.querydsl.core.types.Projections; import com.querydsl.core.types.dsl.BooleanExpression; @@ -38,7 +41,10 @@ import jakarta.persistence.EntityManager; import java.time.ZonedDateTime; import java.util.ArrayList; +import java.util.Comparator; import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageImpl; @@ -112,17 +118,6 @@ public class SiteUserFilterRepositoryImpl implements SiteUserFilterRepository { userBan.createdAt ); - private static final QUnivApplyInfo firstChoiceUnivApplyInfo = new QUnivApplyInfo("firstChoiceUnivApplyInfo"); - private static final QUnivApplyInfo secondChoiceUnivApplyInfo = new QUnivApplyInfo("secondChoiceUnivApplyInfo"); - private static final QUnivApplyInfo thirdChoiceUnivApplyInfo = new QUnivApplyInfo("thirdChoiceUnivApplyInfo"); - - private static final ConstructorExpression UNIV_APPLY_INFO_RESPONSE_PROJECTION = - Projections.constructor( - UnivApplyInfoResponse.class, - firstChoiceUnivApplyInfo.koreanName, - secondChoiceUnivApplyInfo.koreanName, - thirdChoiceUnivApplyInfo.koreanName - ); private final JPAQueryFactory queryFactory; @@ -327,21 +322,42 @@ private MenteeInfoResponse fetchMenteeInfo(long userId) { } private UnivApplyInfoResponse fetchUnivApplyInfo(long userId) { - UnivApplyInfoResponse result = queryFactory - .select(UNIV_APPLY_INFO_RESPONSE_PROJECTION) - .from(application) - .leftJoin(firstChoiceUnivApplyInfo).on(firstChoiceUnivApplyInfo.id.eq(application.firstChoiceUnivApplyInfoId)) - .leftJoin(secondChoiceUnivApplyInfo).on(secondChoiceUnivApplyInfo.id.eq(application.secondChoiceUnivApplyInfoId)) - .leftJoin(thirdChoiceUnivApplyInfo).on(thirdChoiceUnivApplyInfo.id.eq(application.thirdChoiceUnivApplyInfoId)) - .where(application.siteUserId.eq(userId)) + Application latestApplication = queryFactory + .selectFrom(application) + .where(application.siteUserId.eq(userId), application.isDelete.isFalse()) .orderBy(application.createdAt.desc()) .fetchFirst(); - if (result == null) { - return new UnivApplyInfoResponse(null, null, null); + if (latestApplication == null) { + return new UnivApplyInfoResponse(List.of()); + } + + List univApplyInfoIds = latestApplication.getChoices().stream() + .sorted(Comparator.comparingInt(ApplicationChoice::getChoiceOrder)) + .map(ApplicationChoice::getUnivApplyInfoId) + .toList(); + + if (univApplyInfoIds.isEmpty()) { + return new UnivApplyInfoResponse(List.of()); } - return result; + List tuples = queryFactory + .select(univApplyInfo.id, univApplyInfo.koreanName) + .from(univApplyInfo) + .where(univApplyInfo.id.in(univApplyInfoIds)) + .fetch(); + + Map nameById = tuples.stream() + .collect(Collectors.toMap( + t -> t.get(univApplyInfo.id), + t -> t.get(univApplyInfo.koreanName) + )); + + List choiceNames = univApplyInfoIds.stream() + .map(nameById::get) + .toList(); + + return new UnivApplyInfoResponse(choiceNames); } From fcd574e8d136b705090f9829f526eafc4e33fa69 Mon Sep 17 00:00:00 2001 From: whqtker Date: Tue, 9 Jun 2026 16:30:31 +0900 Subject: [PATCH 11/22] =?UTF-8?q?test:=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=ED=94=BD=EC=8A=A4=EC=B2=98=20=EB=B0=8F=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../admin/service/AdminUserServiceTest.java | 5 +- .../AdminHomeUniversityServiceTest.java | 25 +- .../fixture/ApplicationFixture.java | 9 +- .../fixture/ApplicationFixtureBuilder.java | 29 +- .../service/ApplicationQueryServiceTest.java | 286 +++++------------- .../ApplicationSubmissionServiceTest.java | 110 ++++--- ...ValidUnivApplyInfoChoiceValidatorTest.java | 26 +- .../fixture/HomeUniversityFixture.java | 9 + .../fixture/HomeUniversityFixtureBuilder.java | 8 +- 9 files changed, 202 insertions(+), 305 deletions(-) diff --git a/src/test/java/com/example/solidconnection/admin/service/AdminUserServiceTest.java b/src/test/java/com/example/solidconnection/admin/service/AdminUserServiceTest.java index 8c1d5a5f..63231c5b 100644 --- a/src/test/java/com/example/solidconnection/admin/service/AdminUserServiceTest.java +++ b/src/test/java/com/example/solidconnection/admin/service/AdminUserServiceTest.java @@ -38,6 +38,7 @@ import com.example.solidconnection.university.domain.UnivApplyInfo; import com.example.solidconnection.university.fixture.UnivApplyInfoFixture; import com.example.solidconnection.university.fixture.UniversityFixture; +import java.util.List; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; @@ -191,9 +192,7 @@ class 유저_상세_정보_조회 { termId, new Gpa(4.0, 4.5, "http://gpa-report.com/test.pdf"), new LanguageTest(LanguageTestType.TOEIC, "900", "http://language-test.com/test.pdf"), - firstChoice.getId(), - secondChoice.getId(), - null + List.of(firstChoice.getId(), secondChoice.getId()) ); // when diff --git a/src/test/java/com/example/solidconnection/admin/university/service/AdminHomeUniversityServiceTest.java b/src/test/java/com/example/solidconnection/admin/university/service/AdminHomeUniversityServiceTest.java index 425475b0..3977f2f1 100644 --- a/src/test/java/com/example/solidconnection/admin/university/service/AdminHomeUniversityServiceTest.java +++ b/src/test/java/com/example/solidconnection/admin/university/service/AdminHomeUniversityServiceTest.java @@ -84,7 +84,8 @@ class 협정대학_단건_조회 { // then assertAll( () -> assertThat(response.id()).isEqualTo(homeUniversity.getId()), - () -> assertThat(response.name()).isEqualTo(homeUniversity.getName()) + () -> assertThat(response.name()).isEqualTo(homeUniversity.getName()), + () -> assertThat(response.maxChoiceCount()).isEqualTo(homeUniversity.getMaxChoiceCount()) ); } @@ -103,7 +104,7 @@ class 협정대학_생성 { @Test void 유효한_요청으로_협정대학을_생성하면_성공한다() { // given - AdminHomeUniversityCreateRequest request = new AdminHomeUniversityCreateRequest("인하대학교"); + AdminHomeUniversityCreateRequest request = new AdminHomeUniversityCreateRequest("인하대학교", 3); // when AdminHomeUniversityResponse response = adminHomeUniversityService.createHomeUniversity(request); @@ -112,7 +113,9 @@ class 협정대학_생성 { HomeUniversity saved = homeUniversityRepository.findById(response.id()).orElseThrow(); assertAll( () -> assertThat(response.name()).isEqualTo("인하대학교"), - () -> assertThat(saved.getName()).isEqualTo("인하대학교") + () -> assertThat(response.maxChoiceCount()).isEqualTo(3), + () -> assertThat(saved.getName()).isEqualTo("인하대학교"), + () -> assertThat(saved.getMaxChoiceCount()).isEqualTo(3) ); } @@ -120,7 +123,7 @@ class 협정대학_생성 { void 이미_존재하는_이름으로_생성하면_예외가_발생한다() { // given homeUniversityFixture.인하대학교(); - AdminHomeUniversityCreateRequest request = new AdminHomeUniversityCreateRequest("인하대학교"); + AdminHomeUniversityCreateRequest request = new AdminHomeUniversityCreateRequest("인하대학교", 3); // when & then assertThatCode(() -> adminHomeUniversityService.createHomeUniversity(request)) @@ -136,7 +139,7 @@ class 협정대학_수정 { void 유효한_요청으로_협정대학을_수정하면_성공한다() { // given HomeUniversity homeUniversity = homeUniversityFixture.인하대학교(); - AdminHomeUniversityUpdateRequest request = new AdminHomeUniversityUpdateRequest("연세대학교"); + AdminHomeUniversityUpdateRequest request = new AdminHomeUniversityUpdateRequest("연세대학교", 5); // when AdminHomeUniversityResponse response = adminHomeUniversityService.updateHomeUniversity(homeUniversity.getId(), request); @@ -145,14 +148,16 @@ class 협정대학_수정 { HomeUniversity updated = homeUniversityRepository.findById(homeUniversity.getId()).orElseThrow(); assertAll( () -> assertThat(response.name()).isEqualTo("연세대학교"), - () -> assertThat(updated.getName()).isEqualTo("연세대학교") + () -> assertThat(response.maxChoiceCount()).isEqualTo(5), + () -> assertThat(updated.getName()).isEqualTo("연세대학교"), + () -> assertThat(updated.getMaxChoiceCount()).isEqualTo(5) ); } @Test void 존재하지_않는_협정대학을_수정하면_예외가_발생한다() { // given - AdminHomeUniversityUpdateRequest request = new AdminHomeUniversityUpdateRequest("연세대학교"); + AdminHomeUniversityUpdateRequest request = new AdminHomeUniversityUpdateRequest("연세대학교", 3); // when & then assertThatCode(() -> adminHomeUniversityService.updateHomeUniversity(999L, request)) @@ -164,8 +169,8 @@ class 협정대학_수정 { void 다른_협정대학의_이름으로_수정하면_예외가_발생한다() { // given homeUniversityFixture.인하대학교(); - HomeUniversity other = homeUniversityRepository.save(new HomeUniversity(null, "연세대학교")); - AdminHomeUniversityUpdateRequest request = new AdminHomeUniversityUpdateRequest("인하대학교"); + HomeUniversity other = homeUniversityRepository.save(new HomeUniversity(null, "연세대학교", 3)); + AdminHomeUniversityUpdateRequest request = new AdminHomeUniversityUpdateRequest("인하대학교", 3); // when & then assertThatCode(() -> adminHomeUniversityService.updateHomeUniversity(other.getId(), request)) @@ -177,7 +182,7 @@ class 협정대학_수정 { void 같은_이름으로_수정하면_성공한다() { // given HomeUniversity homeUniversity = homeUniversityFixture.인하대학교(); - AdminHomeUniversityUpdateRequest request = new AdminHomeUniversityUpdateRequest("인하대학교"); + AdminHomeUniversityUpdateRequest request = new AdminHomeUniversityUpdateRequest("인하대학교", 3); // when AdminHomeUniversityResponse response = adminHomeUniversityService.updateHomeUniversity(homeUniversity.getId(), request); 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 683f794a..850f7928 100644 --- a/src/test/java/com/example/solidconnection/application/fixture/ApplicationFixture.java +++ b/src/test/java/com/example/solidconnection/application/fixture/ApplicationFixture.java @@ -4,6 +4,7 @@ import com.example.solidconnection.application.domain.Gpa; import com.example.solidconnection.application.domain.LanguageTest; import com.example.solidconnection.siteuser.domain.SiteUser; +import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.boot.test.context.TestComponent; @@ -19,9 +20,7 @@ public class ApplicationFixture { long termId, Gpa gpa, LanguageTest languageTest, - Long firstChoiceUnivApplyInfoId, - Long secondChoiceUnivApplyInfoId, - Long thirdChoiceUnivApplyInfoId + List univApplyInfoIds ) { return applicationFixtureBuilder.application() .siteUser(siteUser) @@ -29,9 +28,7 @@ public class ApplicationFixture { .languageTest(languageTest) .nicknameForApply(nicknameForApply) .termId(termId) - .firstChoiceUnivApplyInfoId(firstChoiceUnivApplyInfoId) - .secondChoiceUnivApplyInfoId(secondChoiceUnivApplyInfoId) - .thirdChoiceUnivApplyInfoId(thirdChoiceUnivApplyInfoId) + .univApplyInfoIds(univApplyInfoIds) .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 d8c7fb7e..bd143172 100644 --- a/src/test/java/com/example/solidconnection/application/fixture/ApplicationFixtureBuilder.java +++ b/src/test/java/com/example/solidconnection/application/fixture/ApplicationFixtureBuilder.java @@ -1,11 +1,14 @@ package com.example.solidconnection.application.fixture; import com.example.solidconnection.application.domain.Application; +import com.example.solidconnection.application.domain.ApplicationChoice; import com.example.solidconnection.application.domain.Gpa; import com.example.solidconnection.application.domain.LanguageTest; import com.example.solidconnection.application.repository.ApplicationRepository; import com.example.solidconnection.common.VerifyStatus; import com.example.solidconnection.siteuser.domain.SiteUser; +import java.util.List; +import java.util.stream.IntStream; import lombok.RequiredArgsConstructor; import org.springframework.boot.test.context.TestComponent; @@ -17,9 +20,7 @@ public class ApplicationFixtureBuilder { private Gpa gpa; private LanguageTest languageTest; - private Long firstChoiceUnivApplyInfoId; - private Long secondChoiceUnivApplyInfoId; - private Long thirdChoiceUnivApplyInfoId; + private List univApplyInfoIds = List.of(); private SiteUser siteUser; private String nicknameForApply; private long termId; @@ -38,18 +39,8 @@ public ApplicationFixtureBuilder languageTest(LanguageTest languageTest) { return this; } - public ApplicationFixtureBuilder firstChoiceUnivApplyInfoId(Long firstChoiceUnivApplyInfoId) { - this.firstChoiceUnivApplyInfoId = firstChoiceUnivApplyInfoId; - return this; - } - - public ApplicationFixtureBuilder secondChoiceUnivApplyInfoId(Long secondChoiceUnivApplyInfoId) { - this.secondChoiceUnivApplyInfoId = secondChoiceUnivApplyInfoId; - return this; - } - - public ApplicationFixtureBuilder thirdChoiceUnivApplyInfoId(Long thirdChoiceUnivApplyInfoId) { - this.thirdChoiceUnivApplyInfoId = thirdChoiceUnivApplyInfoId; + public ApplicationFixtureBuilder univApplyInfoIds(List univApplyInfoIds) { + this.univApplyInfoIds = univApplyInfoIds; return this; } @@ -69,14 +60,16 @@ public ApplicationFixtureBuilder termId(long termId) { } public Application create() { + List choices = IntStream.range(0, univApplyInfoIds.size()) + .mapToObj(i -> new ApplicationChoice(i + 1, univApplyInfoIds.get(i))) + .toList(); Application application = new Application( siteUser, gpa, languageTest, termId, - firstChoiceUnivApplyInfoId, - secondChoiceUnivApplyInfoId, - thirdChoiceUnivApplyInfoId, + 1, + choices, 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 42b4d152..7e15148d 100644 --- a/src/test/java/com/example/solidconnection/application/service/ApplicationQueryServiceTest.java +++ b/src/test/java/com/example/solidconnection/application/service/ApplicationQueryServiceTest.java @@ -105,151 +105,88 @@ class 지원자_목록_조회_테스트 { void 이번_학기_전체_지원자를_조회한다() { // given Application application1 = applicationFixture.지원서( - user1, - "nickname1", - term.getId(), - gpaScore1.getGpa(), - languageTestScore1.getLanguageTest(), - 괌대학_A_지원_정보.getId(), - null, - null + user1, "nickname1", term.getId(), + gpaScore1.getGpa(), languageTestScore1.getLanguageTest(), + List.of(괌대학_A_지원_정보.getId()) ); Application application2 = applicationFixture.지원서( - user2, - "nickname2", - term.getId(), - gpaScore2.getGpa(), - languageTestScore2.getLanguageTest(), - 버지니아공과대학_지원_정보.getId(), - null, - null + user2, "nickname2", term.getId(), + gpaScore2.getGpa(), languageTestScore2.getLanguageTest(), + List.of(버지니아공과대학_지원_정보.getId()) ); Application application3 = applicationFixture.지원서( - user3, - "nickname3", - term.getId(), - gpaScore3.getGpa(), - languageTestScore3.getLanguageTest(), - 서던덴마크대학교_지원_정보.getId(), - null, - null + user3, "nickname3", term.getId(), + gpaScore3.getGpa(), languageTestScore3.getLanguageTest(), + List.of(서던덴마크대학교_지원_정보.getId()) ); // when - ApplicationsResponse response = applicationQueryService.getApplicants( - user1.getId(), - "", - "" - ); + ApplicationsResponse response = applicationQueryService.getApplicants(user1.getId(), "", ""); // then - assertThat(response.firstChoice()).containsAll(List.of( - ApplicantsResponse.of(괌대학_A_지원_정보, - List.of(application1), user1), - ApplicantsResponse.of(버지니아공과대학_지원_정보, - List.of(application2), user1), - ApplicantsResponse.of(서던덴마크대학교_지원_정보, - List.of(application3), user1) + assertThat(response.choices().get(0)).containsAll(List.of( + ApplicantsResponse.of(괌대학_A_지원_정보, List.of(application1), user1), + ApplicantsResponse.of(버지니아공과대학_지원_정보, List.of(application2), user1), + ApplicantsResponse.of(서던덴마크대학교_지원_정보, List.of(application3), user1) )); } @Test void 이번_학기_특정_지역_지원자를_조회한다() { - //given + // given Application application1 = applicationFixture.지원서( - user1, - "nickname1", - term.getId(), - gpaScore1.getGpa(), - languageTestScore1.getLanguageTest(), - 괌대학_A_지원_정보.getId(), - null, - null + user1, "nickname1", term.getId(), + gpaScore1.getGpa(), languageTestScore1.getLanguageTest(), + List.of(괌대학_A_지원_정보.getId()) ); Application application2 = applicationFixture.지원서( - user2, - "nickname2", - term.getId(), - gpaScore2.getGpa(), - languageTestScore2.getLanguageTest(), - 버지니아공과대학_지원_정보.getId(), - null, - null + user2, "nickname2", term.getId(), + gpaScore2.getGpa(), languageTestScore2.getLanguageTest(), + List.of(버지니아공과대학_지원_정보.getId()) ); applicationFixture.지원서( - user3, - "nickname3", - term.getId(), - gpaScore3.getGpa(), - languageTestScore3.getLanguageTest(), - 서던덴마크대학교_지원_정보.getId(), - null, - null + user3, "nickname3", term.getId(), + gpaScore3.getGpa(), languageTestScore3.getLanguageTest(), + List.of(서던덴마크대학교_지원_정보.getId()) ); // when ApplicationsResponse response = applicationQueryService.getApplicants( - user1.getId(), - regionFixture.영미권().getCode(), - "" - ); + user1.getId(), regionFixture.영미권().getCode(), ""); // then - assertThat(response.firstChoice()).containsExactlyInAnyOrder( - ApplicantsResponse.of(괌대학_A_지원_정보, - List.of(application1), user1), - ApplicantsResponse.of(버지니아공과대학_지원_정보, - List.of(application2), user1) + assertThat(response.choices().get(0)).containsExactlyInAnyOrder( + ApplicantsResponse.of(괌대학_A_지원_정보, List.of(application1), user1), + ApplicantsResponse.of(버지니아공과대학_지원_정보, List.of(application2), user1) ); } @Test void 이번_학기_지원자를_대학_국문_이름으로_필터링해서_조회한다() { - //given + // given Application application1 = applicationFixture.지원서( - user1, - "nickname1", - term.getId(), - gpaScore1.getGpa(), - languageTestScore1.getLanguageTest(), - 괌대학_A_지원_정보.getId(), - null, - null + user1, "nickname1", term.getId(), + gpaScore1.getGpa(), languageTestScore1.getLanguageTest(), + List.of(괌대학_A_지원_정보.getId()) ); Application application2 = applicationFixture.지원서( - user2, - "nickname2", - term.getId(), - gpaScore2.getGpa(), - languageTestScore2.getLanguageTest(), - 버지니아공과대학_지원_정보.getId(), - null, - null + user2, "nickname2", term.getId(), + gpaScore2.getGpa(), languageTestScore2.getLanguageTest(), + List.of(버지니아공과대학_지원_정보.getId()) ); applicationFixture.지원서( - user3, - "nickname3", - term.getId(), - gpaScore3.getGpa(), - languageTestScore3.getLanguageTest(), - 서던덴마크대학교_지원_정보.getId(), - null, - null + user3, "nickname3", term.getId(), + gpaScore3.getGpa(), languageTestScore3.getLanguageTest(), + List.of(서던덴마크대학교_지원_정보.getId()) ); // when - ApplicationsResponse response = applicationQueryService.getApplicants( - user1.getId(), - null, - "미국" - ); + ApplicationsResponse response = applicationQueryService.getApplicants(user1.getId(), null, "미국"); // then - assertThat(response.firstChoice()).containsExactlyInAnyOrder( - ApplicantsResponse.of(괌대학_A_지원_정보, - List.of(application1), user1), - ApplicantsResponse.of(버지니아공과대학_지원_정보, - List.of(application2), user1) + assertThat(response.choices().get(0)).containsExactlyInAnyOrder( + ApplicantsResponse.of(괌대학_A_지원_정보, List.of(application1), user1), + ApplicantsResponse.of(버지니아공과대학_지원_정보, List.of(application2), user1) ); } @@ -257,37 +194,22 @@ class 지원자_목록_조회_테스트 { void 현재_학기_지원서만_조회되고_이전_학기_지원서는_제외된다() { // given Term previousTerm = termFixture.이전_학기("2024-2"); - Application application = applicationFixture.지원서( - user1, - "nickname1_past", - previousTerm.getId(), - gpaScore1.getGpa(), - languageTestScore1.getLanguageTest(), - 괌대학_A_지원_정보.getId(), - null, - null + applicationFixture.지원서( + user1, "nickname1_past", previousTerm.getId(), + gpaScore1.getGpa(), languageTestScore1.getLanguageTest(), + List.of(괌대학_A_지원_정보.getId()) ); - Application currentApplication = applicationFixture.지원서( - user1, - "nickname1", - term.getId(), - gpaScore1.getGpa(), - languageTestScore1.getLanguageTest(), - 괌대학_A_지원_정보.getId(), - null, - null + user1, "nickname1", term.getId(), + gpaScore1.getGpa(), languageTestScore1.getLanguageTest(), + List.of(괌대학_A_지원_정보.getId()) ); // when - ApplicationsResponse response = applicationQueryService.getApplicants( - user1.getId(), - "", - "" - ); + ApplicationsResponse response = applicationQueryService.getApplicants(user1.getId(), "", ""); // then - assertThat(response.firstChoice()).containsExactlyInAnyOrder( + assertThat(response.choices().get(0)).containsExactlyInAnyOrder( ApplicantsResponse.of(괌대학_A_지원_정보, List.of(currentApplication), user1), ApplicantsResponse.of(버지니아공과대학_지원_정보, List.of(), user1), ApplicantsResponse.of(서던덴마크대학교_지원_정보, List.of(), user1) @@ -298,37 +220,23 @@ class 지원자_목록_조회_테스트 { void 동일_유저의_여러_지원서_중_최신_지원서만_조회된다() { // given Application firstApplication = applicationFixture.지원서( - user1, - "nickname1", - term.getId(), - gpaScore1.getGpa(), - languageTestScore1.getLanguageTest(), - 괌대학_A_지원_정보.getId(), - null, - null + user1, "nickname1", term.getId(), + gpaScore1.getGpa(), languageTestScore1.getLanguageTest(), + List.of(괌대학_A_지원_정보.getId()) ); firstApplication.setIsDeleteTrue(); applicationRepository.save(firstApplication); Application secondApplication = applicationFixture.지원서( - user1, - "nickname2", - term.getId(), - gpaScore1.getGpa(), - languageTestScore1.getLanguageTest(), - 버지니아공과대학_지원_정보.getId(), - null, - null + user1, "nickname2", term.getId(), + gpaScore1.getGpa(), languageTestScore1.getLanguageTest(), + List.of(버지니아공과대학_지원_정보.getId()) ); // when - ApplicationsResponse response = applicationQueryService.getApplicants( - user1.getId(), - "", - "" - ); + ApplicationsResponse response = applicationQueryService.getApplicants(user1.getId(), "", ""); // then - assertThat(response.firstChoice().stream() + assertThat(response.choices().get(0).stream() .flatMap(univ -> univ.applicants().stream()) .filter(ApplicantResponse::isMine)) .containsExactly(ApplicantResponse.of(secondApplication, true)); @@ -342,42 +250,27 @@ class 경쟁자_목록_조회_테스트 { void 이번_학기_지원한_대학의_경쟁자_목록을_조회한다() { // given Application application1 = applicationFixture.지원서( - user1, - "nickname1", - term.getId(), - gpaScore1.getGpa(), - languageTestScore1.getLanguageTest(), - 괌대학_A_지원_정보.getId(), - null, - null + user1, "nickname1", term.getId(), + gpaScore1.getGpa(), languageTestScore1.getLanguageTest(), + List.of(괌대학_A_지원_정보.getId()) ); Application application2 = applicationFixture.지원서( - user2, - "nickname2", - term.getId(), - gpaScore2.getGpa(), - languageTestScore2.getLanguageTest(), - 괌대학_A_지원_정보.getId(), - null, - null + user2, "nickname2", term.getId(), + gpaScore2.getGpa(), languageTestScore2.getLanguageTest(), + List.of(괌대학_A_지원_정보.getId()) ); applicationFixture.지원서( - user3, - "nickname3", - term.getId(), - gpaScore3.getGpa(), - languageTestScore3.getLanguageTest(), - 서던덴마크대학교_지원_정보.getId(), - null, - null + user3, "nickname3", term.getId(), + gpaScore3.getGpa(), languageTestScore3.getLanguageTest(), + List.of(서던덴마크대학교_지원_정보.getId()) ); + // when ApplicationsResponse response = applicationQueryService.getApplicantsByUserApplications(user1.getId()); // then - assertThat(response.firstChoice()).containsExactlyInAnyOrder( - ApplicantsResponse.of(괌대학_A_지원_정보, - List.of(application1, application2), user1) + assertThat(response.choices().get(0)).containsExactlyInAnyOrder( + ApplicantsResponse.of(괌대학_A_지원_정보, List.of(application1, application2), user1) ); } @@ -385,41 +278,26 @@ class 경쟁자_목록_조회_테스트 { void 이번_학기_지원한_대학_중_미선택이_있을_때_경쟁자_목록을_조회한다() { // given Application application1 = applicationFixture.지원서( - user1, - "nickname1", - term.getId(), - gpaScore1.getGpa(), - languageTestScore1.getLanguageTest(), - 괌대학_A_지원_정보.getId(), - null, - null + user1, "nickname1", term.getId(), + gpaScore1.getGpa(), languageTestScore1.getLanguageTest(), + List.of(괌대학_A_지원_정보.getId()) ); Application application2 = applicationFixture.지원서( - user2, - "nickname2", - term.getId(), - gpaScore2.getGpa(), - languageTestScore2.getLanguageTest(), - 괌대학_A_지원_정보.getId(), - 버지니아공과대학_지원_정보.getId(), - 서던덴마크대학교_지원_정보.getId() + user2, "nickname2", term.getId(), + gpaScore2.getGpa(), languageTestScore2.getLanguageTest(), + List.of(괌대학_A_지원_정보.getId(), 버지니아공과대학_지원_정보.getId(), 서던덴마크대학교_지원_정보.getId()) ); - Application application3 = applicationFixture.지원서( - user3, - "nickname3", - term.getId(), - gpaScore3.getGpa(), - languageTestScore3.getLanguageTest(), - 서던덴마크대학교_지원_정보.getId(), - null, - null + applicationFixture.지원서( + user3, "nickname3", term.getId(), + gpaScore3.getGpa(), languageTestScore3.getLanguageTest(), + List.of(서던덴마크대학교_지원_정보.getId()) ); // when ApplicationsResponse response = applicationQueryService.getApplicantsByUserApplications(user1.getId()); // then - assertThat(response.firstChoice()) + assertThat(response.choices().get(0)) .hasSize(1) .allSatisfy(uar -> { assertThat(uar.koreanName()).isEqualTo(괌대학_A_지원_정보.getKoreanName()); 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 4b344253..5f105cc2 100644 --- a/src/test/java/com/example/solidconnection/application/service/ApplicationSubmissionServiceTest.java +++ b/src/test/java/com/example/solidconnection/application/service/ApplicationSubmissionServiceTest.java @@ -2,6 +2,7 @@ import static com.example.solidconnection.application.service.ApplicationSubmissionService.APPLICATION_UPDATE_COUNT_LIMIT; import static com.example.solidconnection.common.exception.ErrorCode.APPLY_UPDATE_LIMIT_EXCEED; +import static com.example.solidconnection.common.exception.ErrorCode.CHOICE_COUNT_EXCEEDS_LIMIT; import static com.example.solidconnection.common.exception.ErrorCode.INVALID_GPA_SCORE_STATUS; import static com.example.solidconnection.common.exception.ErrorCode.INVALID_LANGUAGE_TEST_SCORE_STATUS; import static org.assertj.core.api.Assertions.assertThat; @@ -9,6 +10,7 @@ import static org.junit.jupiter.api.Assertions.assertAll; import com.example.solidconnection.application.domain.Application; +import com.example.solidconnection.application.domain.ApplicationChoice; import com.example.solidconnection.application.dto.ApplicationSubmissionResponse; import com.example.solidconnection.application.dto.ApplyRequest; import com.example.solidconnection.application.dto.UnivApplyInfoChoiceRequest; @@ -24,10 +26,14 @@ import com.example.solidconnection.support.TestContainerSpringBootTest; import com.example.solidconnection.term.domain.Term; import com.example.solidconnection.term.fixture.TermFixture; +import com.example.solidconnection.university.domain.HomeUniversity; import com.example.solidconnection.university.domain.UnivApplyInfo; +import com.example.solidconnection.university.fixture.HomeUniversityFixture; import com.example.solidconnection.university.fixture.UnivApplyInfoFixture; +import java.util.List; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -56,17 +62,18 @@ class ApplicationSubmissionServiceTest { @Autowired private TermFixture termFixture; + @Autowired + private HomeUniversityFixture homeUniversityFixture; + private SiteUser user; private UnivApplyInfo 괌대학_A_지원_정보; private UnivApplyInfo 버지니아공과대학_지원_정보; private UnivApplyInfo 서던덴마크대학교_지원_정보; - private Term term; @BeforeEach void setUp() { term = termFixture.현재_학기("2025-2"); - user = siteUserFixture.사용자(); 괌대학_A_지원_정보 = univApplyInfoFixture.괌대학_A_지원_정보(term.getId()); 버지니아공과대학_지원_정보 = univApplyInfoFixture.버지니아공과대학_지원_정보(term.getId()); @@ -79,9 +86,11 @@ void setUp() { GpaScore gpaScore = gpaScoreFixture.GPA_점수(VerifyStatus.APPROVED, user); LanguageTestScore languageTestScore = languageTestScoreFixture.어학_점수(VerifyStatus.APPROVED, user); UnivApplyInfoChoiceRequest univApplyInfoChoiceRequest = new UnivApplyInfoChoiceRequest( - 괌대학_A_지원_정보.getId(), - 버지니아공과대학_지원_정보.getId(), - 서던덴마크대학교_지원_정보.getId() + List.of( + 괌대학_A_지원_정보.getId(), + 버지니아공과대학_지원_정보.getId(), + 서던덴마크대학교_지원_정보.getId() + ) ); ApplyRequest request = new ApplyRequest(gpaScore.getId(), languageTestScore.getId(), univApplyInfoChoiceRequest); @@ -92,46 +101,69 @@ void setUp() { Application savedApplication = applicationRepository .findTopBySiteUserIdAndTermIdAndIsDeleteFalseOrderByIdDesc(user.getId(), term.getId()) .orElseThrow(); + List savedChoices = savedApplication.getChoices(); assertAll( - () -> assertThat(response.totalApplyCount()) - .isEqualTo(APPLICATION_UPDATE_COUNT_LIMIT), - () -> assertThat(response.applyCount()) - .isEqualTo(savedApplication.getUpdateCount()), - () -> assertThat(response.appliedUniversities().firstChoiceUnivApplyInfo()) - .isEqualTo(괌대학_A_지원_정보.getKoreanName()), - () -> assertThat(response.appliedUniversities().secondChoiceUnivApplyInfo()) - .isEqualTo(버지니아공과대학_지원_정보.getKoreanName()), - () -> assertThat(response.appliedUniversities().thirdChoiceUnivApplyInfo()) - .isEqualTo(서던덴마크대학교_지원_정보.getKoreanName()), - () -> assertThat(savedApplication.getVerifyStatus()) - .isEqualTo(VerifyStatus.APPROVED), - () -> assertThat(savedApplication.isDelete()) - .isFalse(), - () -> assertThat(savedApplication.getFirstChoiceUnivApplyInfoId()) - .isEqualTo(괌대학_A_지원_정보.getId()), - () -> assertThat(savedApplication.getSecondChoiceUnivApplyInfoId()) - .isEqualTo(버지니아공과대학_지원_정보.getId()), - () -> assertThat(savedApplication.getThirdChoiceUnivApplyInfoId()) - .isEqualTo(서던덴마크대학교_지원_정보.getId()) + () -> assertThat(response.totalApplyCount()).isEqualTo(APPLICATION_UPDATE_COUNT_LIMIT), + () -> assertThat(response.applyCount()).isEqualTo(savedApplication.getUpdateCount()), + () -> assertThat(response.appliedUniversities().choices()).containsExactly( + 괌대학_A_지원_정보.getKoreanName(), + 버지니아공과대학_지원_정보.getKoreanName(), + 서던덴마크대학교_지원_정보.getKoreanName() + ), + () -> assertThat(savedApplication.getVerifyStatus()).isEqualTo(VerifyStatus.APPROVED), + () -> assertThat(savedApplication.isDelete()).isFalse(), + () -> assertThat(savedChoices).extracting(ApplicationChoice::getUnivApplyInfoId) + .containsExactly( + 괌대학_A_지원_정보.getId(), + 버지니아공과대학_지원_정보.getId(), + 서던덴마크대학교_지원_정보.getId() + ) ); } + @Nested + @DisplayName("최대 지망 수 초과 검증") + class 최대_지망_수_초과 { + + @Test + void 출신대학_최대_지망수를_초과하면_예외가_발생한다() { + // given + HomeUniversity homeUniversity = homeUniversityFixture.인하대학교(2); + SiteUser userWithHomeUniv = siteUserFixture.국내_대학_정보_소지_사용자(homeUniversity.getId()); + GpaScore gpaScore = gpaScoreFixture.GPA_점수(VerifyStatus.APPROVED, userWithHomeUniv); + LanguageTestScore languageTestScore = languageTestScoreFixture.어학_점수(VerifyStatus.APPROVED, userWithHomeUniv); + UnivApplyInfoChoiceRequest choiceRequest = new UnivApplyInfoChoiceRequest( + List.of( + 괌대학_A_지원_정보.getId(), + 버지니아공과대학_지원_정보.getId(), + 서던덴마크대학교_지원_정보.getId() + ) + ); + + // when & then + assertThatCode(() -> + applicationSubmissionService.apply( + userWithHomeUniv.getId(), + new ApplyRequest(gpaScore.getId(), languageTestScore.getId(), choiceRequest) + ) + ) + .isInstanceOf(CustomException.class) + .hasMessage(CHOICE_COUNT_EXCEEDS_LIMIT.getMessage()); + } + } + @Test void 미승인된_GPA_성적으로_지원하면_예외가_발생한다() { // given GpaScore gpaScore = gpaScoreFixture.GPA_점수(VerifyStatus.PENDING, user); LanguageTestScore languageTestScore = languageTestScoreFixture.어학_점수(VerifyStatus.APPROVED, user); UnivApplyInfoChoiceRequest univApplyInfoChoiceRequest = new UnivApplyInfoChoiceRequest( - 괌대학_A_지원_정보.getId(), - null, - null + List.of(괌대학_A_지원_정보.getId()) ); ApplyRequest request = new ApplyRequest(gpaScore.getId(), languageTestScore.getId(), univApplyInfoChoiceRequest); // when & then - assertThatCode(() -> - applicationSubmissionService.apply(user.getId(), request) - ) + assertThatCode(() -> applicationSubmissionService.apply(user.getId(), request)) .isInstanceOf(CustomException.class) .hasMessage(INVALID_GPA_SCORE_STATUS.getMessage()); } @@ -142,16 +174,12 @@ void setUp() { GpaScore gpaScore = gpaScoreFixture.GPA_점수(VerifyStatus.APPROVED, user); LanguageTestScore languageTestScore = languageTestScoreFixture.어학_점수(VerifyStatus.PENDING, user); UnivApplyInfoChoiceRequest univApplyInfoChoiceRequest = new UnivApplyInfoChoiceRequest( - 괌대학_A_지원_정보.getId(), - null, - null + List.of(괌대학_A_지원_정보.getId()) ); ApplyRequest request = new ApplyRequest(gpaScore.getId(), languageTestScore.getId(), univApplyInfoChoiceRequest); // when & then - assertThatCode(() -> - applicationSubmissionService.apply(user.getId(), request) - ) + assertThatCode(() -> applicationSubmissionService.apply(user.getId(), request)) .isInstanceOf(CustomException.class) .hasMessage(INVALID_LANGUAGE_TEST_SCORE_STATUS.getMessage()); } @@ -162,9 +190,7 @@ void setUp() { GpaScore gpaScore = gpaScoreFixture.GPA_점수(VerifyStatus.APPROVED, user); LanguageTestScore languageTestScore = languageTestScoreFixture.어학_점수(VerifyStatus.APPROVED, user); UnivApplyInfoChoiceRequest univApplyInfoChoiceRequest = new UnivApplyInfoChoiceRequest( - 괌대학_A_지원_정보.getId(), - null, - null + List.of(괌대학_A_지원_정보.getId()) ); ApplyRequest request = new ApplyRequest(gpaScore.getId(), languageTestScore.getId(), univApplyInfoChoiceRequest); @@ -173,9 +199,7 @@ void setUp() { } // when & then - assertThatCode(() -> - applicationSubmissionService.apply(user.getId(), request) - ) + assertThatCode(() -> applicationSubmissionService.apply(user.getId(), request)) .isInstanceOf(CustomException.class) .hasMessage(APPLY_UPDATE_LIMIT_EXCEED.getMessage()); } diff --git a/src/test/java/com/example/solidconnection/university/dto/validation/ValidUnivApplyInfoChoiceValidatorTest.java b/src/test/java/com/example/solidconnection/university/dto/validation/ValidUnivApplyInfoChoiceValidatorTest.java index 94b7eb7f..93445e89 100644 --- a/src/test/java/com/example/solidconnection/university/dto/validation/ValidUnivApplyInfoChoiceValidatorTest.java +++ b/src/test/java/com/example/solidconnection/university/dto/validation/ValidUnivApplyInfoChoiceValidatorTest.java @@ -2,7 +2,6 @@ import static com.example.solidconnection.common.exception.ErrorCode.DUPLICATE_UNIV_APPLY_INFO_CHOICE; import static com.example.solidconnection.common.exception.ErrorCode.FIRST_CHOICE_REQUIRED; -import static com.example.solidconnection.common.exception.ErrorCode.THIRD_CHOICE_REQUIRES_SECOND; import static org.assertj.core.api.Assertions.assertThat; import com.example.solidconnection.application.dto.UnivApplyInfoChoiceRequest; @@ -10,6 +9,7 @@ import jakarta.validation.Validation; import jakarta.validation.Validator; import jakarta.validation.ValidatorFactory; +import java.util.List; import java.util.Set; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; @@ -31,7 +31,7 @@ void setUp() { @Test void 정상적인_지망_선택은_유효하다() { // given - UnivApplyInfoChoiceRequest request = new UnivApplyInfoChoiceRequest(1L, 2L, 3L); + UnivApplyInfoChoiceRequest request = new UnivApplyInfoChoiceRequest(List.of(1L, 2L, 3L)); // when Set> violations = validator.validate(request); @@ -43,7 +43,7 @@ void setUp() { @Test void 첫_번째_지망만_선택하는_것은_유효하다() { // given - UnivApplyInfoChoiceRequest request = new UnivApplyInfoChoiceRequest(1L, null, null); + UnivApplyInfoChoiceRequest request = new UnivApplyInfoChoiceRequest(List.of(1L)); // when Set> violations = validator.validate(request); @@ -53,23 +53,9 @@ void setUp() { } @Test - void 두_번째_지망_없이_세_번째_지망을_선택하면_예외가_발생한다() { + void 지망을_선택하지_않으면_예외가_발생한다() { // given - UnivApplyInfoChoiceRequest request = new UnivApplyInfoChoiceRequest(1L, null, 3L); - - // when - Set> violations = validator.validate(request); - - // then - assertThat(violations) - .extracting(MESSAGE) - .contains(THIRD_CHOICE_REQUIRES_SECOND.getMessage()); - } - - @Test - void 첫_번째_지망을_선택하지_않으면_예외가_발생한다() { - // given - UnivApplyInfoChoiceRequest request = new UnivApplyInfoChoiceRequest(null, 2L, 3L); + UnivApplyInfoChoiceRequest request = new UnivApplyInfoChoiceRequest(List.of()); // when Set> violations = validator.validate(request); @@ -84,7 +70,7 @@ void setUp() { @Test void 대학을_중복_선택하면_예외가_발생한다() { // given - UnivApplyInfoChoiceRequest request = new UnivApplyInfoChoiceRequest(1L, 1L, 2L); + UnivApplyInfoChoiceRequest request = new UnivApplyInfoChoiceRequest(List.of(1L, 1L, 2L)); // when Set> violations = validator.validate(request); diff --git a/src/test/java/com/example/solidconnection/university/fixture/HomeUniversityFixture.java b/src/test/java/com/example/solidconnection/university/fixture/HomeUniversityFixture.java index 38ae070e..b584745a 100644 --- a/src/test/java/com/example/solidconnection/university/fixture/HomeUniversityFixture.java +++ b/src/test/java/com/example/solidconnection/university/fixture/HomeUniversityFixture.java @@ -13,12 +13,21 @@ public class HomeUniversityFixture { public HomeUniversity 인하대학교() { return homeUniversityFixtureBuilder.homeUniversity() .name("인하대학교") + .maxChoiceCount(3) + .create(); + } + + public HomeUniversity 인하대학교(int maxChoiceCount) { + return homeUniversityFixtureBuilder.homeUniversity() + .name("인하대학교_" + maxChoiceCount + "지망") + .maxChoiceCount(maxChoiceCount) .create(); } public HomeUniversity 인천대학교() { return homeUniversityFixtureBuilder.homeUniversity() .name("인천대학교") + .maxChoiceCount(3) .create(); } } diff --git a/src/test/java/com/example/solidconnection/university/fixture/HomeUniversityFixtureBuilder.java b/src/test/java/com/example/solidconnection/university/fixture/HomeUniversityFixtureBuilder.java index 092b2a0c..5287123e 100644 --- a/src/test/java/com/example/solidconnection/university/fixture/HomeUniversityFixtureBuilder.java +++ b/src/test/java/com/example/solidconnection/university/fixture/HomeUniversityFixtureBuilder.java @@ -12,6 +12,7 @@ public class HomeUniversityFixtureBuilder { private final HomeUniversityRepository homeUniversityRepository; private String name; + private int maxChoiceCount = 3; public HomeUniversityFixtureBuilder homeUniversity() { return new HomeUniversityFixtureBuilder(homeUniversityRepository); @@ -22,8 +23,13 @@ public HomeUniversityFixtureBuilder name(String name) { return this; } + public HomeUniversityFixtureBuilder maxChoiceCount(int maxChoiceCount) { + this.maxChoiceCount = maxChoiceCount; + return this; + } + public HomeUniversity create() { return homeUniversityRepository.findByName(name) - .orElseGet(() -> homeUniversityRepository.save(new HomeUniversity(null, name))); + .orElseGet(() -> homeUniversityRepository.save(new HomeUniversity(null, name, maxChoiceCount))); } } From 19a190af6da01c44c8de3db354758c6ef0f83fc5 Mon Sep 17 00:00:00 2001 From: whqtker Date: Tue, 9 Jun 2026 16:47:18 +0900 Subject: [PATCH 12/22] =?UTF-8?q?refactor:=20=EC=BB=A8=EB=B2=A4=EC=85=98?= =?UTF-8?q?=EC=97=90=20=EB=A7=9E=EA=B2=8C=20=EB=A1=9C=EC=A7=81=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - private 메서드 위치 - Wrapper 타입 - 메서드 레퍼런스 방식 --- .../dto/AdminHomeUniversityCreateRequest.java | 4 +- .../dto/AdminHomeUniversityUpdateRequest.java | 4 +- .../service/ApplicationSubmissionService.java | 47 ++++++++++--------- 3 files changed, 26 insertions(+), 29 deletions(-) diff --git a/src/main/java/com/example/solidconnection/admin/university/dto/AdminHomeUniversityCreateRequest.java b/src/main/java/com/example/solidconnection/admin/university/dto/AdminHomeUniversityCreateRequest.java index 0e706509..9451e810 100644 --- a/src/main/java/com/example/solidconnection/admin/university/dto/AdminHomeUniversityCreateRequest.java +++ b/src/main/java/com/example/solidconnection/admin/university/dto/AdminHomeUniversityCreateRequest.java @@ -2,7 +2,6 @@ import jakarta.validation.constraints.Min; import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Size; public record AdminHomeUniversityCreateRequest( @@ -10,9 +9,8 @@ public record AdminHomeUniversityCreateRequest( @Size(max = 100, message = "협정 대학명은 100자 이하여야 합니다") String name, - @NotNull(message = "최대 지망 수는 필수입니다") @Min(value = 1, message = "최대 지망 수는 1 이상이어야 합니다") - Integer maxChoiceCount + int maxChoiceCount ) { } diff --git a/src/main/java/com/example/solidconnection/admin/university/dto/AdminHomeUniversityUpdateRequest.java b/src/main/java/com/example/solidconnection/admin/university/dto/AdminHomeUniversityUpdateRequest.java index 5f5df8af..464192bf 100644 --- a/src/main/java/com/example/solidconnection/admin/university/dto/AdminHomeUniversityUpdateRequest.java +++ b/src/main/java/com/example/solidconnection/admin/university/dto/AdminHomeUniversityUpdateRequest.java @@ -2,7 +2,6 @@ import jakarta.validation.constraints.Min; import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Size; public record AdminHomeUniversityUpdateRequest( @@ -10,9 +9,8 @@ public record AdminHomeUniversityUpdateRequest( @Size(max = 100, message = "협정 대학명은 100자 이하여야 합니다") String name, - @NotNull(message = "최대 지망 수는 필수입니다") @Min(value = 1, message = "최대 지망 수는 1 이상이어야 합니다") - Integer maxChoiceCount + int maxChoiceCount ) { } 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 25cae958..faa36f43 100644 --- a/src/main/java/com/example/solidconnection/application/service/ApplicationSubmissionService.java +++ b/src/main/java/com/example/solidconnection/application/service/ApplicationSubmissionService.java @@ -25,6 +25,7 @@ import com.example.solidconnection.siteuser.repository.SiteUserRepository; import com.example.solidconnection.term.domain.Term; import com.example.solidconnection.term.repository.TermRepository; +import com.example.solidconnection.university.domain.HomeUniversity; import com.example.solidconnection.university.domain.UnivApplyInfo; import com.example.solidconnection.university.repository.HomeUniversityRepository; import com.example.solidconnection.university.repository.UnivApplyInfoRepository; @@ -93,12 +94,31 @@ public ApplicationSubmissionResponse apply(long siteUserId, ApplyRequest applyRe return ApplicationSubmissionResponse.of(APPLICATION_UPDATE_COUNT_LIMIT, newApplication, uniApplyInfos); } + private GpaScore getValidGpaScore(SiteUser siteUser, Long gpaScoreId) { + GpaScore gpaScore = gpaScoreRepository.findGpaScoreBySiteUserIdAndId(siteUser.getId(), gpaScoreId) + .orElseThrow(() -> new CustomException(GPA_SCORE_NOT_FOUND)); + if (gpaScore.getVerifyStatus() != VerifyStatus.APPROVED) { + throw new CustomException(INVALID_GPA_SCORE_STATUS); + } + return gpaScore; + } + + private LanguageTestScore getValidLanguageTestScore(SiteUser siteUser, Long languageTestScoreId) { + LanguageTestScore languageTestScore = languageTestScoreRepository + .findLanguageTestScoreBySiteUserIdAndId(siteUser.getId(), languageTestScoreId) + .orElseThrow(() -> new CustomException(INVALID_LANGUAGE_TEST_SCORE)); + if (languageTestScore.getVerifyStatus() != VerifyStatus.APPROVED) { + throw new CustomException(INVALID_LANGUAGE_TEST_SCORE_STATUS); + } + return languageTestScore; + } + private int resolveMaxChoiceCount(SiteUser siteUser) { if (siteUser.getHomeUniversityId() == null) { return DEFAULT_MAX_CHOICE_COUNT; } return homeUniversityRepository.findById(siteUser.getHomeUniversityId()) - .map(hu -> hu.getMaxChoiceCount()) + .map(HomeUniversity::getMaxChoiceCount) .orElse(DEFAULT_MAX_CHOICE_COUNT); } @@ -114,23 +134,10 @@ private List buildChoices(List univApplyInfoIds) { .toList(); } - private GpaScore getValidGpaScore(SiteUser siteUser, Long gpaScoreId) { - GpaScore gpaScore = gpaScoreRepository.findGpaScoreBySiteUserIdAndId(siteUser.getId(), gpaScoreId) - .orElseThrow(() -> new CustomException(GPA_SCORE_NOT_FOUND)); - if (gpaScore.getVerifyStatus() != VerifyStatus.APPROVED) { - throw new CustomException(INVALID_GPA_SCORE_STATUS); - } - return gpaScore; - } - - private LanguageTestScore getValidLanguageTestScore(SiteUser siteUser, Long languageTestScoreId) { - LanguageTestScore languageTestScore = languageTestScoreRepository - .findLanguageTestScoreBySiteUserIdAndId(siteUser.getId(), languageTestScoreId) - .orElseThrow(() -> new CustomException(INVALID_LANGUAGE_TEST_SCORE)); - if (languageTestScore.getVerifyStatus() != VerifyStatus.APPROVED) { - throw new CustomException(INVALID_LANGUAGE_TEST_SCORE_STATUS); + private void validateUpdateLimitNotExceed(Application application) { + if (application.getUpdateCount() >= APPLICATION_UPDATE_COUNT_LIMIT) { + throw new CustomException(APPLY_UPDATE_LIMIT_EXCEED); } - return languageTestScore; } private String getRandomNickname() { @@ -140,10 +147,4 @@ private String getRandomNickname() { } return randomNickname; } - - private void validateUpdateLimitNotExceed(Application application) { - if (application.getUpdateCount() >= APPLICATION_UPDATE_COUNT_LIMIT) { - throw new CustomException(APPLY_UPDATE_LIMIT_EXCEED); - } - } } From fcfa59bc92c9d7d04839f510773adaaa334fd542 Mon Sep 17 00:00:00 2001 From: whqtker Date: Tue, 9 Jun 2026 16:57:19 +0900 Subject: [PATCH 13/22] =?UTF-8?q?refactor:=20=EC=A7=80=EB=A7=9D=20?= =?UTF-8?q?=EB=A6=AC=EC=8A=A4=ED=8A=B8=EC=97=90=EC=84=9C=20null=EC=9D=B8?= =?UTF-8?q?=20=EA=B2=BD=EC=9A=B0=20=EA=B2=80=EC=A6=9D=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../solidconnection/common/exception/ErrorCode.java | 1 + .../dto/validation/ValidUnivApplyInfoChoiceValidator.java | 8 ++++++++ 2 files changed, 9 insertions(+) diff --git a/src/main/java/com/example/solidconnection/common/exception/ErrorCode.java b/src/main/java/com/example/solidconnection/common/exception/ErrorCode.java index bb33cf62..7e3c5e6c 100644 --- a/src/main/java/com/example/solidconnection/common/exception/ErrorCode.java +++ b/src/main/java/com/example/solidconnection/common/exception/ErrorCode.java @@ -98,6 +98,7 @@ public enum ErrorCode { FIRST_CHOICE_REQUIRED(HttpStatus.BAD_REQUEST.value(), "1지망 대학교를 입력해주세요."), THIRD_CHOICE_REQUIRES_SECOND(HttpStatus.BAD_REQUEST.value(), "2지망 없이 3지망을 선택할 수 없습니다."), DUPLICATE_UNIV_APPLY_INFO_CHOICE(HttpStatus.BAD_REQUEST.value(), "지망 선택이 중복되었습니다."), + INVALID_UNIV_APPLY_INFO_CHOICE(HttpStatus.BAD_REQUEST.value(), "유효하지 않은 지망 대학교가 포함되어 있습니다."), CHOICE_COUNT_EXCEEDS_LIMIT(HttpStatus.BAD_REQUEST.value(), "지망 수가 최대 지망 수를 초과했습니다."), // community diff --git a/src/main/java/com/example/solidconnection/university/dto/validation/ValidUnivApplyInfoChoiceValidator.java b/src/main/java/com/example/solidconnection/university/dto/validation/ValidUnivApplyInfoChoiceValidator.java index ff45b704..c2ce4134 100644 --- a/src/main/java/com/example/solidconnection/university/dto/validation/ValidUnivApplyInfoChoiceValidator.java +++ b/src/main/java/com/example/solidconnection/university/dto/validation/ValidUnivApplyInfoChoiceValidator.java @@ -2,12 +2,14 @@ import static com.example.solidconnection.common.exception.ErrorCode.DUPLICATE_UNIV_APPLY_INFO_CHOICE; import static com.example.solidconnection.common.exception.ErrorCode.FIRST_CHOICE_REQUIRED; +import static com.example.solidconnection.common.exception.ErrorCode.INVALID_UNIV_APPLY_INFO_CHOICE; import com.example.solidconnection.application.dto.UnivApplyInfoChoiceRequest; import jakarta.validation.ConstraintValidator; import jakarta.validation.ConstraintValidatorContext; import java.util.HashSet; import java.util.List; +import java.util.Objects; import java.util.Set; public class ValidUnivApplyInfoChoiceValidator @@ -25,6 +27,12 @@ public boolean isValid(UnivApplyInfoChoiceRequest request, ConstraintValidatorCo return false; } + if (ids.stream().anyMatch(Objects::isNull)) { + context.buildConstraintViolationWithTemplate(INVALID_UNIV_APPLY_INFO_CHOICE.getMessage()) + .addConstraintViolation(); + return false; + } + if (hasDuplicate(ids)) { context.buildConstraintViolationWithTemplate(DUPLICATE_UNIV_APPLY_INFO_CHOICE.getMessage()) .addConstraintViolation(); From b69a41f8483f177128bfa1bc77bb8f0b10a13739 Mon Sep 17 00:00:00 2001 From: whqtker Date: Tue, 9 Jun 2026 17:24:38 +0900 Subject: [PATCH 14/22] =?UTF-8?q?refactor:=20=EC=B4=88=EA=B8=B0=20?= =?UTF-8?q?=EC=83=81=ED=83=9C=EC=97=90=EC=84=9C=EB=8F=84=20maxChoiceCount?= =?UTF-8?q?=EB=A7=8C=ED=81=BC=EC=9D=98=20=EB=A6=AC=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=ED=81=AC=EA=B8=B0=EA=B0=80=20=EC=83=9D=EC=84=B1=EB=90=98?= =?UTF-8?q?=EB=8F=84=EB=A1=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/ApplicationQueryService.java | 31 +++++++++++++------ 1 file changed, 21 insertions(+), 10 deletions(-) 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 9d0f33ad..fe24fe89 100644 --- a/src/main/java/com/example/solidconnection/application/service/ApplicationQueryService.java +++ b/src/main/java/com/example/solidconnection/application/service/ApplicationQueryService.java @@ -15,7 +15,9 @@ import com.example.solidconnection.siteuser.repository.SiteUserRepository; import com.example.solidconnection.term.domain.Term; import com.example.solidconnection.term.repository.TermRepository; +import com.example.solidconnection.university.domain.HomeUniversity; import com.example.solidconnection.university.domain.UnivApplyInfo; +import com.example.solidconnection.university.repository.HomeUniversityRepository; import com.example.solidconnection.university.repository.UnivApplyInfoRepository; import com.example.solidconnection.university.repository.custom.UnivApplyInfoFilterRepositoryImpl; import io.micrometer.common.util.StringUtils; @@ -32,11 +34,14 @@ @Service public class ApplicationQueryService { + private static final int DEFAULT_MAX_CHOICE_COUNT = 3; + private final ApplicationRepository applicationRepository; private final UnivApplyInfoRepository univApplyInfoRepository; private final UnivApplyInfoFilterRepositoryImpl universityFilterRepository; private final SiteUserRepository siteUserRepository; private final TermRepository termRepository; + private final HomeUniversityRepository homeUniversityRepository; @Transactional(readOnly = true) public ApplicationsResponse getApplicants(long siteUserId, String regionCode, String keyword) { @@ -59,7 +64,8 @@ public ApplicationsResponse getApplicants(long siteUserId, String regionCode, St List applications = applicationRepository .findAllByUnivApplyInfoIds(univApplyInfoIds, VerifyStatus.APPROVED, term.getId()); - return classifyApplicationsByChoice(univApplyInfos, applications, siteUser); + int maxChoiceCount = resolveMaxChoiceCount(siteUser); + return classifyApplicationsByChoice(univApplyInfos, applications, siteUser, maxChoiceCount); } @Transactional(readOnly = true) @@ -86,21 +92,17 @@ public ApplicationsResponse getApplicantsByUserApplications(long siteUserId) { .findAllByUnivApplyInfoIds(univApplyInfoIds, VerifyStatus.APPROVED, term.getId()); List univApplyInfos = univApplyInfoRepository.findAllByIds(univApplyInfoIds); - return classifyApplicationsByChoice(univApplyInfos, applications, siteUser); + int maxChoiceCount = resolveMaxChoiceCount(siteUser); + return classifyApplicationsByChoice(univApplyInfos, applications, siteUser, maxChoiceCount); } private ApplicationsResponse classifyApplicationsByChoice( List univApplyInfos, List applications, - SiteUser siteUser) { - int maxOrder = applications.stream() - .flatMap(a -> a.getChoices().stream()) - .mapToInt(ApplicationChoice::getChoiceOrder) - .max() - .orElse(0); - + SiteUser siteUser, + int maxChoiceCount) { List> allChoices = new ArrayList<>(); - for (int order = 1; order <= maxOrder; order++) { + for (int order = 1; order <= maxChoiceCount; order++) { final int choiceOrder = order; Map> choiceMap = buildChoiceMapForOrder(applications, choiceOrder); allChoices.add(createUniversityApplicantsResponses(univApplyInfos, choiceMap, siteUser)); @@ -108,6 +110,15 @@ private ApplicationsResponse classifyApplicationsByChoice( return new ApplicationsResponse(allChoices); } + private int resolveMaxChoiceCount(SiteUser siteUser) { + if (siteUser.getHomeUniversityId() == null) { + return DEFAULT_MAX_CHOICE_COUNT; + } + return homeUniversityRepository.findById(siteUser.getHomeUniversityId()) + .map(HomeUniversity::getMaxChoiceCount) + .orElse(DEFAULT_MAX_CHOICE_COUNT); + } + private Map> buildChoiceMapForOrder(List applications, int order) { Map> map = new HashMap<>(); for (Application application : applications) { From fec195fc3ddb3a83d5ff6161f1e0991a5b036ac7 Mon Sep 17 00:00:00 2001 From: whqtker Date: Tue, 9 Jun 2026 17:26:37 +0900 Subject: [PATCH 15/22] =?UTF-8?q?refactor:=20=EB=B6=88=ED=95=84=EC=9A=94?= =?UTF-8?q?=ED=95=9C=20=EA=B2=80=EC=A6=9D=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/service/ApplicationQueryService.java | 1 - 1 file changed, 1 deletion(-) 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 fe24fe89..8ddefe47 100644 --- a/src/main/java/com/example/solidconnection/application/service/ApplicationQueryService.java +++ b/src/main/java/com/example/solidconnection/application/service/ApplicationQueryService.java @@ -81,7 +81,6 @@ public ApplicationsResponse getApplicantsByUserApplications(long siteUserId) { List univApplyInfoIds = userLatestApplication.getChoices().stream() .map(ApplicationChoice::getUnivApplyInfoId) - .distinct() .collect(Collectors.toList()); if (univApplyInfoIds.isEmpty()) { From 074b5408cbab7d64a82597bd13d7db22e73e7e90 Mon Sep 17 00:00:00 2001 From: whqtker Date: Tue, 9 Jun 2026 17:40:32 +0900 Subject: [PATCH 16/22] =?UTF-8?q?refactor:=20BatchSize=EB=A1=9C=20N+1=20?= =?UTF-8?q?=EB=AC=B8=EC=A0=9C=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../example/solidconnection/application/domain/Application.java | 2 ++ 1 file changed, 2 insertions(+) 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 9e8628e7..60d9c667 100644 --- a/src/main/java/com/example/solidconnection/application/domain/Application.java +++ b/src/main/java/com/example/solidconnection/application/domain/Application.java @@ -25,6 +25,7 @@ import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; +import org.hibernate.annotations.BatchSize; import org.hibernate.annotations.ColumnDefault; import org.hibernate.annotations.DynamicInsert; import org.hibernate.annotations.DynamicUpdate; @@ -80,6 +81,7 @@ public class Application extends BaseEntity { joinColumns = @JoinColumn(name = "application_id") ) @OrderBy("choiceOrder ASC") + @BatchSize(size = 100) private List choices = new ArrayList<>(); public Application( From 608deb7d9538294ecd93fa962ccef2af4e7d6c63 Mon Sep 17 00:00:00 2001 From: whqtker Date: Wed, 10 Jun 2026 09:14:01 +0900 Subject: [PATCH 17/22] =?UTF-8?q?test:=20=EB=B6=88=ED=95=84=EC=9A=94?= =?UTF-8?q?=ED=95=9C=20Nested=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ApplicationSubmissionServiceTest.java | 54 +++++++++---------- 1 file changed, 24 insertions(+), 30 deletions(-) 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 5f105cc2..138a8203 100644 --- a/src/test/java/com/example/solidconnection/application/service/ApplicationSubmissionServiceTest.java +++ b/src/test/java/com/example/solidconnection/application/service/ApplicationSubmissionServiceTest.java @@ -33,7 +33,6 @@ import java.util.List; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -121,35 +120,30 @@ void setUp() { ); } - @Nested - @DisplayName("최대 지망 수 초과 검증") - class 최대_지망_수_초과 { - - @Test - void 출신대학_최대_지망수를_초과하면_예외가_발생한다() { - // given - HomeUniversity homeUniversity = homeUniversityFixture.인하대학교(2); - SiteUser userWithHomeUniv = siteUserFixture.국내_대학_정보_소지_사용자(homeUniversity.getId()); - GpaScore gpaScore = gpaScoreFixture.GPA_점수(VerifyStatus.APPROVED, userWithHomeUniv); - LanguageTestScore languageTestScore = languageTestScoreFixture.어학_점수(VerifyStatus.APPROVED, userWithHomeUniv); - UnivApplyInfoChoiceRequest choiceRequest = new UnivApplyInfoChoiceRequest( - List.of( - 괌대학_A_지원_정보.getId(), - 버지니아공과대학_지원_정보.getId(), - 서던덴마크대학교_지원_정보.getId() - ) - ); - - // when & then - assertThatCode(() -> - applicationSubmissionService.apply( - userWithHomeUniv.getId(), - new ApplyRequest(gpaScore.getId(), languageTestScore.getId(), choiceRequest) - ) - ) - .isInstanceOf(CustomException.class) - .hasMessage(CHOICE_COUNT_EXCEEDS_LIMIT.getMessage()); - } + @Test + void 출신대학_최대_지망수를_초과하면_예외가_발생한다() { + // given + HomeUniversity homeUniversity = homeUniversityFixture.인하대학교(2); + SiteUser userWithHomeUniv = siteUserFixture.국내_대학_정보_소지_사용자(homeUniversity.getId()); + GpaScore gpaScore = gpaScoreFixture.GPA_점수(VerifyStatus.APPROVED, userWithHomeUniv); + LanguageTestScore languageTestScore = languageTestScoreFixture.어학_점수(VerifyStatus.APPROVED, userWithHomeUniv); + UnivApplyInfoChoiceRequest choiceRequest = new UnivApplyInfoChoiceRequest( + List.of( + 괌대학_A_지원_정보.getId(), + 버지니아공과대학_지원_정보.getId(), + 서던덴마크대학교_지원_정보.getId() + ) + ); + + // when & then + assertThatCode(() -> + applicationSubmissionService.apply( + userWithHomeUniv.getId(), + new ApplyRequest(gpaScore.getId(), languageTestScore.getId(), choiceRequest) + ) + ) + .isInstanceOf(CustomException.class) + .hasMessage(CHOICE_COUNT_EXCEEDS_LIMIT.getMessage()); } @Test From 5a59f2e4314dee89533a09ed90b06f8e4c129de0 Mon Sep 17 00:00:00 2001 From: whqtker Date: Wed, 10 Jun 2026 09:29:49 +0900 Subject: [PATCH 18/22] =?UTF-8?q?chore:=20mock=EB=8D=B0=EC=9D=B4=ED=84=B0?= =?UTF-8?q?=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/resources/data.sql | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/resources/data.sql b/src/main/resources/data.sql index 58eefa5b..c8d040bf 100644 --- a/src/main/resources/data.sql +++ b/src/main/resources/data.sql @@ -49,8 +49,8 @@ VALUES ('test@test.email', 'yonso', 'https://github.com/nayonsoso.png', 'CONSIDERING', 'MENTEE', '$2a$10$psmwlxPfqWnIlq9JrlQJkuXr1XtjRNsyVOgcTWYZub5jFfn0TML76', 'EMAIL'); -- 12341234 -INSERT INTO home_university (id, name) -VALUES (1, '인하대학교'); +INSERT INTO home_university (id, name, max_choice_count) +VALUES (1, '인하대학교', 3); INSERT INTO host_university(id, country_code, region_code, english_name, format_name, korean_name, accommodation_url, english_course_url, homepage_url, From d9ad3034a4ccab62c0fab6bbea0b9fa01733e016 Mon Sep 17 00:00:00 2001 From: whqtker Date: Wed, 10 Jun 2026 09:41:56 +0900 Subject: [PATCH 19/22] =?UTF-8?q?refactor:=20=EC=9C=A0=ED=9A=A8=ED=95=9C?= =?UTF-8?q?=20id=20=EA=B2=80=EC=A6=9D=EC=9D=84=20=EC=84=9C=EB=B9=84?= =?UTF-8?q?=EC=8A=A4=EC=97=90=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 저장 전에 검증하여 잘못된 id가 DB에 삽입되는 경우 방지 - 중복 조회 제거 --- .../service/ApplicationSubmissionService.java | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) 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 faa36f43..8a9379f6 100644 --- a/src/main/java/com/example/solidconnection/application/service/ApplicationSubmissionService.java +++ b/src/main/java/com/example/solidconnection/application/service/ApplicationSubmissionService.java @@ -7,6 +7,7 @@ import static com.example.solidconnection.common.exception.ErrorCode.INVALID_GPA_SCORE_STATUS; import static com.example.solidconnection.common.exception.ErrorCode.INVALID_LANGUAGE_TEST_SCORE; import static com.example.solidconnection.common.exception.ErrorCode.INVALID_LANGUAGE_TEST_SCORE_STATUS; +import static com.example.solidconnection.common.exception.ErrorCode.UNIV_APPLY_INFO_NOT_FOUND; import static com.example.solidconnection.common.exception.ErrorCode.USER_NOT_FOUND; import com.example.solidconnection.application.domain.Application; @@ -64,6 +65,7 @@ public ApplicationSubmissionResponse apply(long siteUserId, ApplyRequest applyRe int maxChoiceCount = resolveMaxChoiceCount(siteUser); validateChoiceCount(choiceRequest, maxChoiceCount); + List univApplyInfos = getValidUnivApplyInfos(choiceRequest.univApplyInfoIds()); List choices = buildChoices(choiceRequest.univApplyInfoIds()); Optional existingApplication = @@ -89,9 +91,7 @@ public ApplicationSubmissionResponse apply(long siteUserId, ApplyRequest applyRe newApplication.setVerifyStatus(VerifyStatus.APPROVED); applicationRepository.save(newApplication); - List uniApplyInfos = univApplyInfoRepository.findAllByIds(choiceRequest.univApplyInfoIds()); - - return ApplicationSubmissionResponse.of(APPLICATION_UPDATE_COUNT_LIMIT, newApplication, uniApplyInfos); + return ApplicationSubmissionResponse.of(APPLICATION_UPDATE_COUNT_LIMIT, newApplication, univApplyInfos); } private GpaScore getValidGpaScore(SiteUser siteUser, Long gpaScoreId) { @@ -122,6 +122,14 @@ private int resolveMaxChoiceCount(SiteUser siteUser) { .orElse(DEFAULT_MAX_CHOICE_COUNT); } + private List getValidUnivApplyInfos(List ids) { + List univApplyInfos = univApplyInfoRepository.findAllByIds(ids); + if (univApplyInfos.size() != ids.size()) { + throw new CustomException(UNIV_APPLY_INFO_NOT_FOUND); + } + return univApplyInfos; + } + private void validateChoiceCount(UnivApplyInfoChoiceRequest request, int maxChoiceCount) { if (request.univApplyInfoIds().size() > maxChoiceCount) { throw new CustomException(CHOICE_COUNT_EXCEEDS_LIMIT); From e15525dc426e6d0a89329eb317ba08da70577f9e Mon Sep 17 00:00:00 2001 From: whqtker Date: Wed, 10 Jun 2026 09:48:40 +0900 Subject: [PATCH 20/22] =?UTF-8?q?chore:=20CHECK=20=EC=A0=9C=EC=95=BD?= =?UTF-8?q?=EC=A1=B0=EA=B1=B4=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 최대 1지망 지원 가능 --- src/main/resources/db/migration/V50__dynamic_choice_count.sql | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/resources/db/migration/V50__dynamic_choice_count.sql b/src/main/resources/db/migration/V50__dynamic_choice_count.sql index aee657b2..ecdf300b 100644 --- a/src/main/resources/db/migration/V50__dynamic_choice_count.sql +++ b/src/main/resources/db/migration/V50__dynamic_choice_count.sql @@ -1,5 +1,6 @@ ALTER TABLE home_university - ADD COLUMN max_choice_count INT NOT NULL DEFAULT 3; + ADD COLUMN max_choice_count INT NOT NULL DEFAULT 3, + ADD CONSTRAINT chk_max_choice_count CHECK (max_choice_count >= 1); CREATE TABLE application_choice ( From 7a438e2d32a1e5040cdb2b8f39fa43bf6baf2955 Mon Sep 17 00:00:00 2001 From: whqtker Date: Wed, 10 Jun 2026 09:51:05 +0900 Subject: [PATCH 21/22] =?UTF-8?q?chore:=20FK,=20=EC=9D=B8=EB=8D=B1?= =?UTF-8?q?=EC=8A=A4=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../solidconnection/application/domain/Application.java | 3 ++- .../resources/db/migration/V50__dynamic_choice_count.sql | 6 +++++- 2 files changed, 7 insertions(+), 2 deletions(-) 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 60d9c667..e168eece 100644 --- a/src/main/java/com/example/solidconnection/application/domain/Application.java +++ b/src/main/java/com/example/solidconnection/application/domain/Application.java @@ -78,7 +78,8 @@ public class Application extends BaseEntity { @ElementCollection(fetch = FetchType.EAGER) @CollectionTable( name = "application_choice", - joinColumns = @JoinColumn(name = "application_id") + joinColumns = @JoinColumn(name = "application_id"), + indexes = @Index(name = "idx_app_choice_univ_apply_info_id", columnList = "univ_apply_info_id") ) @OrderBy("choiceOrder ASC") @BatchSize(size = 100) diff --git a/src/main/resources/db/migration/V50__dynamic_choice_count.sql b/src/main/resources/db/migration/V50__dynamic_choice_count.sql index ecdf300b..a3cd0e68 100644 --- a/src/main/resources/db/migration/V50__dynamic_choice_count.sql +++ b/src/main/resources/db/migration/V50__dynamic_choice_count.sql @@ -9,8 +9,12 @@ CREATE TABLE application_choice univ_apply_info_id BIGINT NOT NULL, PRIMARY KEY (application_id, choice_order), CONSTRAINT fk_app_choice_application - FOREIGN KEY (application_id) REFERENCES application (id) + FOREIGN KEY (application_id) REFERENCES application (id), + CONSTRAINT fk_app_choice_univ_apply_info + FOREIGN KEY (univ_apply_info_id) REFERENCES university_info_for_apply (id) ); +CREATE INDEX idx_app_choice_univ_apply_info_id ON application_choice (univ_apply_info_id); + ALTER TABLE application MODIFY COLUMN first_choice_university_info_for_apply_id BIGINT NULL; From bf5cbfa6a5c2e8b859690507093acc4e22d9fa33 Mon Sep 17 00:00:00 2001 From: whqtker Date: Wed, 10 Jun 2026 10:19:05 +0900 Subject: [PATCH 22/22] =?UTF-8?q?test:=20containsExactlyInAnyOrder?= =?UTF-8?q?=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 응답의 개수를 정확히 판별하기 위해 --- .../application/service/ApplicationQueryServiceTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 7e15148d..a897284c 100644 --- a/src/test/java/com/example/solidconnection/application/service/ApplicationQueryServiceTest.java +++ b/src/test/java/com/example/solidconnection/application/service/ApplicationQueryServiceTest.java @@ -124,11 +124,11 @@ class 지원자_목록_조회_테스트 { ApplicationsResponse response = applicationQueryService.getApplicants(user1.getId(), "", ""); // then - assertThat(response.choices().get(0)).containsAll(List.of( + assertThat(response.choices().get(0)).containsExactlyInAnyOrder( ApplicantsResponse.of(괌대학_A_지원_정보, List.of(application1), user1), ApplicantsResponse.of(버지니아공과대학_지원_정보, List.of(application2), user1), ApplicantsResponse.of(서던덴마크대학교_지원_정보, List.of(application3), user1) - )); + ); } @Test