Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package com.example.solidconnection.application.domain;

import com.example.solidconnection.siteuser.domain.SiteUser;
import com.example.solidconnection.university.domain.UniversityInfoForApply;
import jakarta.persistence.Column;
import jakarta.persistence.Embedded;
import jakarta.persistence.Entity;
Expand Down Expand Up @@ -54,14 +53,14 @@ public class Application {
@Column
private boolean isDelete = false;

@ManyToOne(fetch = FetchType.LAZY)
private UniversityInfoForApply firstChoiceUniversity;
@Column
private Long firstChoiceUniversityApplyInfoId;
Comment thread
Gyuhyeok99 marked this conversation as resolved.
Outdated

@ManyToOne(fetch = FetchType.LAZY)
private UniversityInfoForApply secondChoiceUniversity;
@Column
private Long secondChoiceUniversityApplyInfoId;

@ManyToOne(fetch = FetchType.LAZY)
private UniversityInfoForApply thirdChoiceUniversity;
@Column
private Long thirdChoiceUniversityApplyInfoId;

@ManyToOne(fetch = FetchType.LAZY)
private SiteUser siteUser;
Expand All @@ -85,18 +84,18 @@ public Application(
LanguageTest languageTest,
String term,
Integer updateCount,
UniversityInfoForApply firstChoiceUniversity,
UniversityInfoForApply secondChoiceUniversity,
UniversityInfoForApply thirdChoiceUniversity,
Long firstChoiceUniversityApplyInfoId,
Comment thread
nayonsoso marked this conversation as resolved.
Outdated
Long secondChoiceUniversityApplyInfoId,
Long thirdChoiceUniversityApplyInfoId,
String nicknameForApply) {
this.siteUser = siteUser;
this.gpa = gpa;
this.languageTest = languageTest;
this.term = term;
this.updateCount = updateCount;
this.firstChoiceUniversity = firstChoiceUniversity;
this.secondChoiceUniversity = secondChoiceUniversity;
this.thirdChoiceUniversity = thirdChoiceUniversity;
this.firstChoiceUniversityApplyInfoId = firstChoiceUniversityApplyInfoId;
this.secondChoiceUniversityApplyInfoId = secondChoiceUniversityApplyInfoId;
this.thirdChoiceUniversityApplyInfoId = thirdChoiceUniversityApplyInfoId;
this.nicknameForApply = nicknameForApply;
this.verifyStatus = PENDING;
}
Expand All @@ -106,37 +105,23 @@ public Application(
Gpa gpa,
LanguageTest languageTest,
String term,
UniversityInfoForApply firstChoiceUniversity,
UniversityInfoForApply secondChoiceUniversity,
UniversityInfoForApply thirdChoiceUniversity,
Long firstChoiceUniversityApplyInfoId,
Comment thread
nayonsoso marked this conversation as resolved.
Outdated
Long secondChoiceUniversityApplyInfoId,
Long thirdChoiceUniversityApplyInfoId,
String nicknameForApply) {
this.siteUser = siteUser;
this.gpa = gpa;
this.languageTest = languageTest;
this.term = term;
this.updateCount = 1;
this.firstChoiceUniversity = firstChoiceUniversity;
this.secondChoiceUniversity = secondChoiceUniversity;
this.thirdChoiceUniversity = thirdChoiceUniversity;
this.firstChoiceUniversityApplyInfoId = firstChoiceUniversityApplyInfoId;
this.secondChoiceUniversityApplyInfoId = secondChoiceUniversityApplyInfoId;
this.thirdChoiceUniversityApplyInfoId = thirdChoiceUniversityApplyInfoId;
this.nicknameForApply = nicknameForApply;
this.verifyStatus = PENDING;
}

public void setIsDeleteTrue() {
this.isDelete = true;
}

public void updateUniversityChoice(
UniversityInfoForApply firstChoiceUniversity,
UniversityInfoForApply secondChoiceUniversity,
UniversityInfoForApply thirdChoiceUniversity,
String nicknameForApply) {
if (this.firstChoiceUniversity != null) {
this.updateCount++;
}
this.firstChoiceUniversity = firstChoiceUniversity;
this.secondChoiceUniversity = secondChoiceUniversity;
this.thirdChoiceUniversity = thirdChoiceUniversity;
this.nicknameForApply = nicknameForApply;
}
Comment thread
nayonsoso marked this conversation as resolved.
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,21 @@ public interface ApplicationRepository extends JpaRepository<Application, Long>

boolean existsByNicknameForApply(String nicknameForApply);

List<Application> findAllByFirstChoiceUniversityAndVerifyStatusAndTermAndIsDeleteFalse(
UniversityInfoForApply firstChoiceUniversity, VerifyStatus verifyStatus, String term);

List<Application> findAllBySecondChoiceUniversityAndVerifyStatusAndTermAndIsDeleteFalse(
UniversityInfoForApply secondChoiceUniversity, VerifyStatus verifyStatus, String term);

List<Application> findAllByThirdChoiceUniversityAndVerifyStatusAndTermAndIsDeleteFalse(
UniversityInfoForApply thirdChoiceUniversity, VerifyStatus verifyStatus, String term);
@Query("""
select a
from Application a
join fetch a.siteUser
where (a.firstChoiceUniversityApplyInfoId in :universityIds
or a.secondChoiceUniversityApplyInfoId in :universityIds
or a.thirdChoiceUniversityApplyInfoId in :universityIds)
and a.verifyStatus = :status
and a.term = :term
and a.isDelete = false
""")
List<Application> findApplicationsForChoices(
@Param("universityIds") List<Long> universityIds,
@Param("status") VerifyStatus status,
@Param("term") String term);
Comment thread
nayonsoso marked this conversation as resolved.
Outdated
Comment thread
nayonsoso marked this conversation as resolved.
Outdated

@Query("""
SELECT a FROM Application a
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,8 @@
import com.example.solidconnection.application.dto.ApplicationsResponse;
import com.example.solidconnection.application.dto.UniversityApplicantsResponse;
import com.example.solidconnection.application.repository.ApplicationRepository;
import com.example.solidconnection.cache.annotation.ThunderingHerdCaching;
import com.example.solidconnection.common.exception.CustomException;
import com.example.solidconnection.siteuser.domain.SiteUser;
import com.example.solidconnection.university.domain.University;
import com.example.solidconnection.university.domain.UniversityInfoForApply;
import com.example.solidconnection.university.repository.UniversityInfoForApplyRepository;
import com.example.solidconnection.university.repository.custom.UniversityFilterRepositoryImpl;
Expand All @@ -18,12 +16,9 @@
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Function;
import java.util.*;
Comment thread
nayonsoso marked this conversation as resolved.
Outdated
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static com.example.solidconnection.common.exception.ErrorCode.APPLICATION_NOT_APPROVED;

Expand All @@ -38,97 +33,97 @@ public class ApplicationQueryService {
@Value("${university.term}")
public String term;

/*
* 다른 지원자들의 성적을 조회한다.
* - 유저가 다른 지원자들을 볼 수 있는지 검증한다.
* - 지역과 키워드를 통해 대학을 필터링한다.
* - 지역은 영어 대문자로 받는다 e.g. ASIA
* - 1지망, 2지망 지원자들을 조회한다.
* */
@Transactional(readOnly = true)
// todo: 임시로 단일 키로 캐시 적용. 추후 캐싱 전략 재검토 필요.
@ThunderingHerdCaching(key = "applications:all", cacheManager = "customCacheManager", ttlSec = 86400)
public ApplicationsResponse getApplicants(SiteUser siteUser, String regionCode, String keyword) {
// 국가와 키워드와 지역을 통해 대학을 필터링한다.
List<University> universities
= universityFilterRepository.findByRegionCodeAndKeywords(regionCode, List.of(keyword));

// 1지망, 2지망, 3지망 지원자들을 조회한다.
List<UniversityApplicantsResponse> firstChoiceApplicants = getFirstChoiceApplicants(universities, siteUser, term);
List<UniversityApplicantsResponse> secondChoiceApplicants = getSecondChoiceApplicants(universities, siteUser, term);
List<UniversityApplicantsResponse> thirdChoiceApplicants = getThirdChoiceApplicants(universities, siteUser, term);
return new ApplicationsResponse(firstChoiceApplicants, secondChoiceApplicants, thirdChoiceApplicants);
// 1. 대학 ID 필터링 (regionCode, keyword)
List<Long> universityIds = universityFilterRepository.findByRegionCodeAndKeywords(regionCode, List.of(keyword));
if (universityIds.isEmpty()) return new ApplicationsResponse(List.of(), List.of(), List.of());
Comment thread
nayonsoso marked this conversation as resolved.
Outdated

// 2. 조건에 맞는 모든 Application 한 번에 조회
List<Application> applications = applicationRepository.findApplicationsForChoices(universityIds, VerifyStatus.APPROVED, term);

// 3. 대학정보 조회
List<UniversityInfoForApply> universityInfos = universityInfoForApplyRepository.findByIdsWithUniversityAndLocation(universityIds);
Comment thread
nayonsoso marked this conversation as resolved.
Outdated

// 4. 지원서 분류 및 DTO 변환
return classifyApplicationsByChoice(universityInfos, applications, siteUser);
}

@Transactional(readOnly = true)
public ApplicationsResponse getApplicantsByUserApplications(SiteUser siteUser) {
Application userLatestApplication = applicationRepository.getApplicationBySiteUserAndTerm(siteUser, term);
List<University> userAppliedUniversities = Arrays.asList(
Optional.ofNullable(userLatestApplication.getFirstChoiceUniversity())
.map(UniversityInfoForApply::getUniversity)
.orElse(null),
Optional.ofNullable(userLatestApplication.getSecondChoiceUniversity())
.map(UniversityInfoForApply::getUniversity)
.orElse(null),
Optional.ofNullable(userLatestApplication.getThirdChoiceUniversity())
.map(UniversityInfoForApply::getUniversity)
.orElse(null)
).stream()

List<Long> universityIds = Stream.of(
userLatestApplication.getFirstChoiceUniversityApplyInfoId(),
userLatestApplication.getSecondChoiceUniversityApplyInfoId(),
userLatestApplication.getThirdChoiceUniversityApplyInfoId()
)
.filter(Objects::nonNull)
.collect(Collectors.toList());

List<UniversityApplicantsResponse> firstChoiceApplicants = getFirstChoiceApplicants(userAppliedUniversities, siteUser, term);
List<UniversityApplicantsResponse> secondChoiceApplicants = getSecondChoiceApplicants(userAppliedUniversities, siteUser, term);
List<UniversityApplicantsResponse> thirdChoiceApplicants = getThirdChoiceApplicants(userAppliedUniversities, siteUser, term);
if (universityIds.isEmpty()) {
return new ApplicationsResponse(List.of(), List.of(), List.of());
}

List<Application> applications = applicationRepository.findApplicationsForChoices(universityIds, VerifyStatus.APPROVED, term);
List<UniversityInfoForApply> universityInfos = universityInfoForApplyRepository.findByIdsWithUniversityAndLocation(universityIds);

return classifyApplicationsByChoice(universityInfos, applications, siteUser);
}

private ApplicationsResponse classifyApplicationsByChoice(
List<UniversityInfoForApply> universityInfos,
Comment thread
nayonsoso marked this conversation as resolved.
Outdated
List<Application> applications,
SiteUser siteUser) {

Map<Long, List<Application>> firstChoiceMap = new HashMap<>();
Comment thread
nayonsoso marked this conversation as resolved.
Outdated
Map<Long, List<Application>> secondChoiceMap = new HashMap<>();
Map<Long, List<Application>> thirdChoiceMap = new HashMap<>();

for (Application a : applications) {
if (a.getFirstChoiceUniversityApplyInfoId() != null) {
firstChoiceMap.computeIfAbsent(a.getFirstChoiceUniversityApplyInfoId(), k -> new ArrayList<>()).add(a);
}
if (a.getSecondChoiceUniversityApplyInfoId() != null) {
secondChoiceMap.computeIfAbsent(a.getSecondChoiceUniversityApplyInfoId(), k -> new ArrayList<>()).add(a);
}
if (a.getThirdChoiceUniversityApplyInfoId() != null) {
thirdChoiceMap.computeIfAbsent(a.getThirdChoiceUniversityApplyInfoId(), k -> new ArrayList<>()).add(a);
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

a 말고 application으로 그냥 명확히 하는 건 어떤가요?

if (a.getFirstChoiceUniversityApplyInfoId() != null) {
                firstChoiceMap.computeIfAbsent(a.getFirstChoiceUniversityApplyInfoId(), k -> new ArrayList<>()).add(a);
            }

똑같은 것을 first, second, third냐에 따라만 다르게 호출하니 함수로 빼도 좋을 거 같네요!

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

반영했습니다!

List<UniversityApplicantsResponse> firstChoiceApplicants = universityInfos.stream()
.map(uia -> UniversityApplicantsResponse.of(
uia,
firstChoiceMap.getOrDefault(uia.getId(), List.of()).stream()
.map(ap -> ApplicantResponse.of(ap, Objects.equals(siteUser.getId(), ap.getSiteUser().getId())))
.toList()))
.toList();

Copy link
Copy Markdown
Contributor

@Gyuhyeok99 Gyuhyeok99 May 26, 2025

Choose a reason for hiding this comment

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

여기도 함수로 빼도 좋을 거 같네요!
지망별로 application을 fetch join으로 가져오면 5번의 쿼리로 분류 필요 없이 응답을 할 수 있는데 지금은 3번의 쿼리로 메모리에서 분류를 하고 있으니 어느정도의 트레이드 오프가 있을지 궁금하네요 ㅎㅎ 성능은 지금 방식이 당연히 더 좋을 거 같습니다!

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

이 부분도 반영했습니다!
추가로 성능에 대한 부분은 비교를 한 번 해봐야 할 것 같긴 하네요!

List<UniversityApplicantsResponse> secondChoiceApplicants = universityInfos.stream()
.map(uia -> UniversityApplicantsResponse.of(
uia,
secondChoiceMap.getOrDefault(uia.getId(), List.of()).stream()
.map(ap -> ApplicantResponse.of(ap, Objects.equals(siteUser.getId(), ap.getSiteUser().getId())))
.toList()))
.toList();

List<UniversityApplicantsResponse> thirdChoiceApplicants = universityInfos.stream()
.map(uia -> UniversityApplicantsResponse.of(
uia,
thirdChoiceMap.getOrDefault(uia.getId(), List.of()).stream()
.map(ap -> ApplicantResponse.of(ap, Objects.equals(siteUser.getId(), ap.getSiteUser().getId())))
.toList()))
.toList();

return new ApplicationsResponse(firstChoiceApplicants, secondChoiceApplicants, thirdChoiceApplicants);
}

// 학기별로 상태가 관리된다.
// 금학기에 지원이력이 있는 사용자만 지원정보를 확인할 수 있도록 한다.
@Transactional(readOnly = true)
public void validateSiteUserCanViewApplicants(SiteUser siteUser) {
VerifyStatus verifyStatus = applicationRepository.getApplicationBySiteUserAndTerm(siteUser, term).getVerifyStatus();
if (verifyStatus != VerifyStatus.APPROVED) {
throw new CustomException(APPLICATION_NOT_APPROVED);
}
}

private List<UniversityApplicantsResponse> getFirstChoiceApplicants(List<University> universities, SiteUser siteUser, String term) {
return getApplicantsByChoice(
universities,
siteUser,
uia -> applicationRepository.findAllByFirstChoiceUniversityAndVerifyStatusAndTermAndIsDeleteFalse(uia, VerifyStatus.APPROVED, term)
);
}

private List<UniversityApplicantsResponse> getSecondChoiceApplicants(List<University> universities, SiteUser siteUser, String term) {
return getApplicantsByChoice(
universities,
siteUser,
uia -> applicationRepository.findAllBySecondChoiceUniversityAndVerifyStatusAndTermAndIsDeleteFalse(uia, VerifyStatus.APPROVED, term)
);
}

private List<UniversityApplicantsResponse> getThirdChoiceApplicants(List<University> universities, SiteUser siteUser, String term) {
return getApplicantsByChoice(
universities,
siteUser,
uia -> applicationRepository.findAllByThirdChoiceUniversityAndVerifyStatusAndTermAndIsDeleteFalse(uia, VerifyStatus.APPROVED, term)
);
}

private List<UniversityApplicantsResponse> getApplicantsByChoice(
List<University> searchedUniversities,
SiteUser siteUser,
Function<UniversityInfoForApply, List<Application>> findApplicationsByChoice) {
return universityInfoForApplyRepository.findByUniversitiesAndTerm(searchedUniversities, term).stream()
.map(universityInfoForApply -> UniversityApplicantsResponse.of(
universityInfoForApply,
findApplicationsByChoice.apply(universityInfoForApply).stream()
.map(ap -> ApplicantResponse.of(
ap,
Objects.equals(siteUser.getId(), ap.getSiteUser().getId())))
.toList()))
.toList();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -45,24 +45,14 @@ public class ApplicationSubmissionService {
// 학점 및 어학성적이 모두 유효한 경우에만 지원서 등록이 가능하다.
// 기존에 있던 status field 우선 APRROVED로 입력시킨다.
@Transactional
// todo: 임시로 새로운 신청 생성 시 기존 캐싱 데이터를 삭제한다. 추후 수정 필요
@DefaultCacheOut(
key = {"applications:all"},
cacheManager = "customCacheManager"
)
Comment thread
nayonsoso marked this conversation as resolved.
public ApplicationSubmissionResponse apply(SiteUser siteUser, ApplyRequest applyRequest) {
UniversityChoiceRequest universityChoiceRequest = applyRequest.universityChoiceRequest();
GpaScore gpaScore = getValidGpaScore(siteUser, applyRequest.gpaScoreId());
LanguageTestScore languageTestScore = getValidLanguageTestScore(siteUser, applyRequest.languageTestScoreId());

UniversityInfoForApply firstChoiceUniversity = universityInfoForApplyRepository
.getUniversityInfoForApplyByIdAndTerm(universityChoiceRequest.firstChoiceUniversityId(), term);
UniversityInfoForApply secondChoiceUniversity = Optional.ofNullable(universityChoiceRequest.secondChoiceUniversityId())
.map(id -> universityInfoForApplyRepository.getUniversityInfoForApplyByIdAndTerm(id, term))
.orElse(null);
UniversityInfoForApply thirdChoiceUniversity = Optional.ofNullable(universityChoiceRequest.thirdChoiceUniversityId())
.map(id -> universityInfoForApplyRepository.getUniversityInfoForApplyByIdAndTerm(id, term))
.orElse(null);
Long firstChoiceUniversityId = universityChoiceRequest.firstChoiceUniversityId();
Comment thread
nayonsoso marked this conversation as resolved.
Outdated
Long secondChoiceUniversityId = universityChoiceRequest.secondChoiceUniversityId();
Long thirdChoiceUniversityId = universityChoiceRequest.thirdChoiceUniversityId();

Optional<Application> existingApplication = applicationRepository.findBySiteUserAndTerm(siteUser, term);
int updateCount = existingApplication
Expand All @@ -72,10 +62,22 @@ public ApplicationSubmissionResponse apply(SiteUser siteUser, ApplyRequest apply
return application.getUpdateCount() + 1;
})
.orElse(1);
Application newApplication = new Application(siteUser, gpaScore.getGpa(), languageTestScore.getLanguageTest(),
term, updateCount, firstChoiceUniversity, secondChoiceUniversity, thirdChoiceUniversity, getRandomNickname());

Application newApplication = new Application(
siteUser,
gpaScore.getGpa(),
languageTestScore.getLanguageTest(),
term,
updateCount,
firstChoiceUniversityId,
secondChoiceUniversityId,
thirdChoiceUniversityId,
getRandomNickname()
);

newApplication.setVerifyStatus(VerifyStatus.APPROVED);
applicationRepository.save(newApplication);

return ApplicationSubmissionResponse.from(newApplication);
}

Expand Down
Loading
Loading