Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
313002b
chore: application_choice 테이블 추가 및 지망 수 컬럼 추가하는 스크립트 작성
whqtker Jun 9, 2026
e88da76
feat: ApplicationChoice 클래스 추가, 최대 지망 수 필드 추가
whqtker Jun 9, 2026
4a1d292
chore: n지망 컬럼 제약 조건 변경
whqtker Jun 9, 2026
011d40b
feat: DTO 및 검증 로직 변경
whqtker Jun 9, 2026
f6ac86c
feat: JPQL 쿼리 변경
whqtker Jun 9, 2026
fd83a34
feat: 서비스 메서드 변경
whqtker Jun 9, 2026
7fc96b2
feat: 어드민 국내 대학 삽입 시 최대 지망 수 관련 DTO에 제약조건 추가
whqtker Jun 9, 2026
27b148e
feat: 대학 검색 응답 필드 변경
whqtker Jun 9, 2026
2575ea2
feat: 최대 지망 수 포함하도록 서비스 메서드 변경
whqtker Jun 9, 2026
2e954aa
feat: QueryDSL 조인 -> 동적 선택으로 변경
whqtker Jun 9, 2026
fcd574e
test: 테스트 픽스처 및 테스트 변경
whqtker Jun 9, 2026
19a190a
refactor: 컨벤션에 맞게 로직 변경
whqtker Jun 9, 2026
fcfa59b
refactor: 지망 리스트에서 null인 경우 검증 추가
whqtker Jun 9, 2026
b69a41f
refactor: 초기 상태에서도 maxChoiceCount만큼의 리스트 크기가 생성되도록
whqtker Jun 9, 2026
fec195f
refactor: 불필요한 검증 제거
whqtker Jun 9, 2026
074b540
refactor: BatchSize로 N+1 문제 해결
whqtker Jun 9, 2026
608deb7
test: 불필요한 Nested 제거
whqtker Jun 10, 2026
5a59f2e
chore: mock데이터 수정
whqtker Jun 10, 2026
d9ad303
refactor: 유효한 id 검증을 서비스에 추가
whqtker Jun 10, 2026
e15525d
chore: CHECK 제약조건 추가
whqtker Jun 10, 2026
7a438e2
chore: FK, 인덱스 추가
whqtker Jun 10, 2026
bf5cbfa
test: containsExactlyInAnyOrder로 변경
whqtker Jun 10, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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<String> choices) {

}
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
package com.example.solidconnection.admin.university.dto;

import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;

public record AdminHomeUniversityCreateRequest(
@NotBlank(message = "협정 대학명은 필수입니다")
@Size(max = 100, message = "협정 대학명은 100자 이하여야 합니다")
String name
String name,

@Min(value = 1, message = "최대 지망 수는 1 이상이어야 합니다")
int maxChoiceCount
) {

}
Original file line number Diff line number Diff line change
Expand Up @@ -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()
);
}
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
package com.example.solidconnection.admin.university.dto;

import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;

public record AdminHomeUniversityUpdateRequest(
@NotBlank(message = "협정 대학명은 필수입니다")
@Size(max = 100, message = "협정 대학명은 100자 이하여야 합니다")
String name
String name,

@Min(value = 1, message = "최대 지망 수는 1 이상이어야 합니다")
int maxChoiceCount
) {

}
Original file line number Diff line number Diff line change
Expand Up @@ -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));
}

Expand All @@ -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);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,27 @@
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;
import org.hibernate.annotations.BatchSize;
import org.hibernate.annotations.ColumnDefault;
import org.hibernate.annotations.DynamicInsert;
import org.hibernate.annotations.DynamicUpdate;
Expand All @@ -29,13 +37,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 {

Expand Down Expand Up @@ -70,18 +72,19 @@ 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"),
indexes = @Index(name = "idx_app_choice_univ_apply_info_id", columnList = "univ_apply_info_id")
)
@OrderBy("choiceOrder ASC")
@BatchSize(size = 100)
private List<ApplicationChoice> choices = new ArrayList<>();

public Application(
SiteUser siteUser,
Gpa gpa,
Expand All @@ -101,39 +104,14 @@ public Application(
LanguageTest languageTest,
long termId,
Integer updateCount,
long firstChoiceUnivApplyInfoId,
Long secondChoiceUnivApplyInfoId,
Long thirdChoiceUnivApplyInfoId,
List<ApplicationChoice> 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;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,6 @@

import java.util.List;

public record ApplicationsResponse(
List<ApplicantsResponse> firstChoice,
List<ApplicantsResponse> secondChoice,
List<ApplicantsResponse> thirdChoice) {
public record ApplicationsResponse(List<List<ApplicantsResponse>> choices) {

}
Original file line number Diff line number Diff line change
Expand Up @@ -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<Long> univApplyInfoIds) {

}
Original file line number Diff line number Diff line change
@@ -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<String> choices) {

public static UnivApplyInfoResponse of(Application application, List<UnivApplyInfo> univApplyInfos) {
Map<Long, String> univApplyInfoMap = univApplyInfos.stream()
.collect(Collectors.toMap(
UnivApplyInfo::getId,
UnivApplyInfo::getKoreanName
));
Map<Long, String> nameById = univApplyInfos.stream()
.collect(Collectors.toMap(UnivApplyInfo::getId, UnivApplyInfo::getKoreanName));

List<String> choiceNames = application.getChoices().stream()
.sorted(Comparator.comparingInt(ApplicationChoice::getChoiceOrder))
.map(choice -> nameById.get(choice.getUnivApplyInfoId()))
.toList();
Comment thread
whqtker marked this conversation as resolved.

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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,20 +16,19 @@ public interface ApplicationRepository extends JpaRepository<Application, Long>
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<Application> findAllByUnivApplyInfoIds(@Param("univApplyInfoIds") List<Long> univApplyInfoIds, @Param("status") VerifyStatus status, @Param("termId") long termId);
List<Application> findAllByUnivApplyInfoIds(
@Param("univApplyInfoIds") List<Long> univApplyInfoIds,
@Param("status") VerifyStatus status,
@Param("termId") long termId);

// TODO: 근본 해결 필요
// 지원서 유일성은 DB 제약으로 강제하고
// 이 조회는 임시 회피 로직을 제거하는 방향으로 수정 필요.
Optional<Application> findTopBySiteUserIdAndTermIdAndIsDeleteFalseOrderByIdDesc(long siteUserId, long termId);

default Application getApplicationBySiteUserIdAndTermId(long siteUserId, long termId) {
Expand Down
Loading
Loading