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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,16 @@ public SuccessResponse<String> sendBulkMail(
return SuccessResponse.ok("๋ฉ”์ผ์„ ๋ฐœ์†ก ์š”์ฒญ์„ ๋ณด๋ƒˆ์Šต๋‹ˆ๋‹ค.");
}

@GetMapping("/recruitment/{recruitmentId}/search")
@Operation(summary = "๊ณต๊ณ  ์ „์ฒด ์ง€์›์ž ๊ฒ€์ƒ‰", description = "๋ฉ”์ผ/๋ฌธ์ž ๋ฐœ์†ก์šฉ์œผ๋กœ ๊ณต๊ณ ์˜ ์ „์ฒด ์ง€์›์ž๋ฅผ ์ด๋ฆ„ ๋˜๋Š” ์ด๋ฉ”์ผ๋กœ ๊ฒ€์ƒ‰ํ•ฉ๋‹ˆ๋‹ค.")
public SuccessResponse<List<ApplicationResponseDTO.ApplicantForMailSms>> searchApplicants(
@PathVariable Long recruitmentId,
@RequestParam(required = false) String keyword
) {
List<ApplicationResponseDTO.ApplicantForMailSms> applicants = applicationService.searchApplicantsForMailSms(recruitmentId, keyword);
return SuccessResponse.ok(applicants);
}

@PostMapping("/bulk-sms")
@Operation(summary = "์ง€์›์„œ ๋Œ€์ƒ ๋‹ค์ค‘ ๋ฌธ์ž ๋ฐœ์†ก", description = "๋ณต์ˆ˜์˜ ์ˆ˜์‹ ์ž ์ „ํ™”๋ฒˆํ˜ธ์™€ ๋ฉ”์‹œ์ง€, ์ฒจ๋ถ€ํŒŒ์ผ(MMS)์„ ๋ฐ›์•„ ์ผ๊ด„ ๋ฐœ์†กํ•ฉ๋‹ˆ๋‹ค.")
public SuccessResponse<String> sendBulkSms(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -555,4 +555,21 @@ public static Applicant from(Application application) {
}
}

@Schema(description = "๋ฉ”์ผ/๋ฌธ์ž ๋ฐœ์†ก์šฉ ์ง€์›์ž ๊ฒ€์ƒ‰ ์‘๋‹ต DTO")
public record ApplicantForMailSms(
@Schema(description = "์ง€์›์„œ ID", example = "1") Long applicationId,
@Schema(description = "์ง€์›์ž ์ด๋ฆ„", example = "ํ™๊ธธ๋™") String name,
@Schema(description = "์ง€์›์ž ์ด๋ฉ”์ผ", example = "hong@example.com") String email,
@Schema(description = "ํ”„๋กœํ•„ ์‚ฌ์ง„ URL", example = "https://example.com/profile.jpg") String profileImageUrl
) {
public static ApplicantForMailSms from(Application application) {
return new ApplicantForMailSms(
application.getId(),
application.getName(),
application.getEmail(),
application.getImageUrl()
);
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ public interface ApplicationJpaRepository extends JpaRepository<Application, Lon
List<Application> findByRecruitmentId(Long recruitmentId);
List<Application> findByRecruitmentIdAndStatusIn(Long recruitmentId, List<ApplicationStatus> statuses);
List<Application> findByRecruitment_IdAndOrganizationRole_Id(Long recruitmentId, Long organizationRoleId);
List<Application> findByRecruitment_IdAndOrganizationRoleIsNull(Long recruitmentId);
Long countByRecruitment_IdAndOrganizationRole_Id(Long recruitmentId, Long organizationRoleId);
List<Application> findDistinctByRecruitment_IdAndEvaluators_Evaluator_IdAndEvaluators_EvaluationType(Long recruitmentId, Long evaluatorId, EvaluationType evaluationType);
Long countByRecruitmentIdAndStatusIn(Long recruitmentId, List<ApplicationStatus> statuses);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ public interface ApplicationRepository {
List<Application> findPassedByRecruitment(Long recruitmentId);
List<Application> findByRecruitmentIdAndStatusIn(Long recruitmentId, List<ApplicationStatus> statuses);
List<Application> findByRecruitment_IdAndOrganizationRole_Id(Long recruitmentId, Long organizationRoleId);
List<Application> findByRecruitment_IdAndOrganizationRoleIsNull(Long recruitmentId);
Long countByRecruitment_IdAndOrganizationRole_Id(Long recruitmentId, Long organizationRoleId);
List<Application> findAllById(List<Long> longs);
List<Application> findDistinctByRecruitment_IdAndEvaluators_Evaluator_IdAndEvaluators_EvaluationType(Long recruitmentId, Long evaluatorId, EvaluationType evaluationType);
Expand All @@ -22,4 +23,5 @@ public interface ApplicationRepository {
List<Application> findForTimeSlot(Long timeSlotId);
Long findPreviousIdInRecruitment(Long recruitmentId, Long currentId);
Long findNextIdInRecruitment(Long recruitmentId, Long currentId);
List<Application> findByRecruitmentIdAndNameOrEmail(Long recruitmentId, String keyword);
}
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,11 @@ public List<Application> findByRecruitment_IdAndOrganizationRole_Id(Long recruit
return applicationJpaRepository.findByRecruitment_IdAndOrganizationRole_Id(recruitmentId, organizationRoleId);
}

@Override
public List<Application> findByRecruitment_IdAndOrganizationRoleIsNull(Long recruitmentId) {
return applicationJpaRepository.findByRecruitment_IdAndOrganizationRoleIsNull(recruitmentId);
}

@Override
public Long countByRecruitment_IdAndOrganizationRole_Id(Long recruitmentId, Long organizationRoleId) {
return applicationJpaRepository.countByRecruitment_IdAndOrganizationRole_Id(recruitmentId, organizationRoleId);
Expand Down Expand Up @@ -154,4 +159,25 @@ public Long findNextIdInRecruitment(Long recruitmentId, Long currentId) {
)
.fetchOne();
}

@Override
public List<Application> findByRecruitmentIdAndNameOrEmail(Long recruitmentId, String keyword) {
com.querydsl.core.types.dsl.BooleanExpression searchPredicate = null;

// ํ‚ค์›Œ๋“œ๊ฐ€ ์žˆ์œผ๋ฉด ์ด๋ฆ„ ๋˜๋Š” ์ด๋ฉ”์ผ๋กœ ๊ฒ€์ƒ‰, ์—†์œผ๋ฉด ์ „์ฒด ๋ชฉ๋ก ์กฐํšŒ
if (keyword != null && !keyword.isBlank()) {
String searchKeyword = keyword.trim();
searchPredicate = application.name.containsIgnoreCase(searchKeyword)
.or(application.email.containsIgnoreCase(searchKeyword));
}

return queryFactory
.selectFrom(application)
.where(
application.recruitment.id.eq(recruitmentId),
searchPredicate // null์ด๋ฉด ๊ฒ€์ƒ‰ ์กฐ๊ฑด ๋ฌด์‹œ๋˜์–ด ์ „์ฒด ๋ชฉ๋ก ์กฐํšŒ
)
.orderBy(application.name.asc())
.fetch();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,5 @@ public interface ApplicationService {
boolean toggleAcquaintance(Long applicationId, Long userId);
List<ApplicationResponseDTO.CandidateDTO> findTimeslotCandidates(Long recruitmentId, Long timeslotId, String query, boolean excludeCurrent);
List<ApplicationResponseDTO.Detail> getAllDetailForExcel(Long recruitmentId, AdminStageFilter stage, AdminApplicationSortField sortBy, Sort.Direction direction, List<Long> organizationRoleIds, List<ApplicationStatus> statuses, String keyword, Long id);
List<ApplicationResponseDTO.ApplicantForMailSms> searchApplicantsForMailSms(Long recruitmentId, String keyword);
}
Original file line number Diff line number Diff line change
Expand Up @@ -206,10 +206,13 @@ public ApplicationResponseDTO.Detail getById(Long id, Long currentUserId) {
applicantAvailabilityRepository.findByApplicationId(id);
List<Evaluation> evaluationList =
evaluationRepository.findEvaluationsForApplication(id);

// ๊ณตํ†ต ํ‰๊ฐ€ ๊ธฐ์ค€(organizationRole์ด null) + ํ•ด๋‹น ์ง€์›์„œ์˜ organizationRole๊ณผ ์ผ์น˜ํ•˜๋Š” ํ‰๊ฐ€ ๊ธฐ์ค€๋งŒ ์กฐํšŒ
List<EvaluationCriteria> criteriaList =
evaluationCriteriaRepository.findByTypeAndRecruitment(
evaluationCriteriaRepository.findCommonAndByOrganizationRole(
recruitmentId,
EvaluationType.DOCUMENT,
recruitmentId
app.getOrganizationRole()
);

return assembler.toDetail(
Expand Down Expand Up @@ -466,6 +469,20 @@ public List<ApplicationResponseDTO.CandidateDTO> findTimeslotCandidates(
return applicationRepository.findEligibleCandidates(recruitmentId, timeslotId, q, excludeCurrent);
}

/**
* ๋ฉ”์ผ/๋ฌธ์ž ๋ฐœ์†ก์šฉ ์ง€์›์ž ๊ฒ€์ƒ‰
* @param recruitmentId ๊ณต๊ณ  ID
* @param keyword ๊ฒ€์ƒ‰์–ด (์ง€์›์ž ์ด๋ฆ„ ๋˜๋Š” ์ด๋ฉ”์ผ)
* @return ์ง€์›์ž ๋ชฉ๋ก (์ง€์›์„œ ID, ์ด๋ฆ„, ์ด๋ฉ”์ผ, ํ”„๋กœํ•„ ์‚ฌ์ง„ URL)
*/
@Override
public List<ApplicationResponseDTO.ApplicantForMailSms> searchApplicantsForMailSms(Long recruitmentId, String keyword) {
List<Application> applications = applicationRepository.findByRecruitmentIdAndNameOrEmail(recruitmentId, keyword);
return applications.stream()
.map(ApplicationResponseDTO.ApplicantForMailSms::from)
.toList();
}

@Override
public List<ApplicationResponseDTO.Detail> getAllDetailForExcel(
Long recruitmentId,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,9 @@ public void distributeEvaluators(ApplicationEvaluatorRequestDTO.Distribute reque
// ์š”์ฒญ ์ด๋ ฅ dto -> ์—”ํ‹ฐํ‹ฐ ๋งคํ•‘
List<DistributionAssignment> assignments = request.assignments().stream()
.map(dto -> {
OrganizationRole role = organizationRoleRepository.getById(dto.organizationRoleId());
OrganizationRole role = dto.organizationRoleId() != null
? organizationRoleRepository.getById(dto.organizationRoleId())
: null;
return DistributionAssignment.builder()
.organizationRole(role)
.evaluationType(dto.evaluationType())
Expand Down Expand Up @@ -77,9 +79,10 @@ public void distributeEvaluators(ApplicationEvaluatorRequestDTO.Distribute reque
throw new CustomException(ErrorCode.INSUFFICIENT_EVALUATORS);
}

// ์ด ์—ญํ• ์„ ์ง€์›ํ•œ ์ง€์›์„œ ๋ฆฌ์ŠคํŠธ
List<Application> apps = applicationRepository
.findByRecruitment_IdAndOrganizationRole_Id(recruitmentId, part.organizationRoleId());
// ์ด ์—ญํ• ์„ ์ง€์›ํ•œ ์ง€์›์„œ ๋ฆฌ์ŠคํŠธ (organizationRoleId๊ฐ€ null์ด๋ฉด ๊ณตํ†ต(์—ญํ•  ๋ฏธ์ง€์ •) ์ง€์›์„œ ์กฐํšŒ)
List<Application> apps = part.organizationRoleId() == null
? applicationRepository.findByRecruitment_IdAndOrganizationRoleIsNull(recruitmentId)
: applicationRepository.findByRecruitment_IdAndOrganizationRole_Id(recruitmentId, part.organizationRoleId());

// ๊ฐ ์ง€์›์„œ๋งˆ๋‹ค ๋žœ๋ค n๋ช… ๋ฐฐ์ •
for (Application app : apps) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,7 @@ public record Distribute(
) {
@Schema(description = "์ง€์› ์—ญํ• (OrganizationRole)๋ณ„ ํ‰๊ฐ€ ๋‹ด๋‹น์ž ๋ฐฐ์ • ์ •๋ณด")
public record PartAssignment(
@Schema(description = "์ง€์› ์—ญํ• (OrganizationRole) ID - ์ง€์›์„œ๊ฐ€ ์ง€์›ํ•œ ์—ญํ• ", example = "4")
@NotNull
@Schema(description = "์ง€์› ์—ญํ• (OrganizationRole) ID - null์ด๋ฉด ๊ณตํ†ต(์—ญํ•  ๋ฏธ์ง€์ • ์ง€์›์„œ) ๋Œ€์ƒ", example = "4")
Long organizationRoleId,

@Schema(description = "ํ‰๊ฐ€ ๋‹ด๋‹น์ž Role(OrganizationRole) ID - ํ‰๊ฐ€๋ฅผ ๋‹ด๋‹นํ•  ์—ญํ• ", example = "7")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ public record Assignment(
) {
public static Assignment from(DistributionAssignment a) {
return new Assignment(
a.getOrganizationRole().getName(),
a.getOrganizationRole() != null ? a.getOrganizationRole().getName() : "๊ณตํ†ต",
a.getEvaluationType(),
a.getCount()
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ public class DistributionAssignment extends BaseEntity {
private DistributionRequest request;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "ORGANIZATION_ROLE_ID", nullable = false)
@JoinColumn(name = "ORGANIZATION_ROLE_ID")
private OrganizationRole organizationRole;

@Enumerated(EnumType.STRING)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,22 @@
package KUSITMS.WITHUS.domain.application.distributionRequest.repository;

import KUSITMS.WITHUS.domain.application.distributionRequest.entity.DistributionRequest;
import KUSITMS.WITHUS.global.exception.CustomException;
import KUSITMS.WITHUS.global.exception.ErrorCode;
import com.querydsl.jpa.impl.JPAQueryFactory;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Repository;

import java.util.List;

import static KUSITMS.WITHUS.domain.application.distributionRequest.entity.QDistributionRequest.distributionRequest;
import static KUSITMS.WITHUS.domain.application.distributionRequest.entity.QDistributionAssignment.distributionAssignment;
import static KUSITMS.WITHUS.domain.organization.organizationRole.entity.QOrganizationRole.organizationRole;

@Repository
@RequiredArgsConstructor
public class DistributionRequestRepositoryImpl implements DistributionRequestRepository {

private final DistributionRequestJpaRepository distributionRequestJpaRepository;
private final JPAQueryFactory queryFactory;

@Override
public DistributionRequest findTopByRecruitmentIdOrderByCreatedAtDesc(Long recruitmentId) {
Expand All @@ -31,7 +35,14 @@ public DistributionRequest save(DistributionRequest distributionRequest) {
}

public List<DistributionRequest> findAllByRecruitmentIdOrderByCreatedAtDesc(Long recruitmentId) {
return distributionRequestJpaRepository.findAllByRecruitmentIdOrderByCreatedAtDesc(recruitmentId);
return queryFactory
.selectFrom(distributionRequest)
.distinct()
.leftJoin(distributionRequest.assignments, distributionAssignment).fetchJoin()
.leftJoin(distributionAssignment.organizationRole, organizationRole).fetchJoin()
.where(distributionRequest.recruitmentId.eq(recruitmentId))
.orderBy(distributionRequest.createdAt.desc())
.fetch();
}
Comment on lines 37 to 46
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

โš ๏ธ Potential issue | ๐ŸŸก Minor

N+1 ์ฟผ๋ฆฌ ๋ฌธ์ œ ํ•ด๊ฒฐ์„ ์œ„ํ•œ fetch join ์ตœ์ ํ™”

QueryDSL์„ ์‚ฌ์šฉํ•˜์—ฌ ๋ช…์‹œ์ ์œผ๋กœ fetch join์„ ์ˆ˜ํ–‰ํ•˜๋Š” ๋ฆฌํŒฉํ† ๋ง์€ ๊ถŒ์žฅ๋ฉ๋‹ˆ๋‹ค. ์ด๋ฅผ ํ†ตํ•ด assignments์™€ organizationRole์„ ์ง€์—ฐ ๋กœ๋”ฉ ์‹œ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ๋Š” N+1 ์ฟผ๋ฆฌ ๋ฌธ์ œ๋ฅผ ๋ฐฉ์ง€ํ•ฉ๋‹ˆ๋‹ค.

๊ตฌํ˜„ ์ƒ์„ธ:

  • fetch join์œผ๋กœ ๊ด€๋ จ ๋ฐ์ดํ„ฐ๋ฅผ ์ฆ‰์‹œ ๋กœ๋”ฉํ•˜๋ฉฐ, distinct()๋Š” OneToMany ์กฐ์ธ์—์„œ ๋ฐœ์ƒํ•˜๋Š” ์ค‘๋ณต ์—”ํ‹ฐํ‹ฐ๋ฅผ ์ œ๊ฑฐํ•ฉ๋‹ˆ๋‹ค
  • leftJoin ์‚ฌ์šฉ์œผ๋กœ ์•„์ง ํ• ๋‹น์ด ์—†๋Š” DistributionRequest๋„ ์กฐํšŒ ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค
  • ์ด ํŒจํ„ด์€ ์—…๊ณ„ ํ‘œ์ค€ ๋ฐฉ์‹์ž…๋‹ˆ๋‹ค

Hibernate ๋ฒ„์ „ ๊ณ ๋ ค์‚ฌํ•ญ:

  • Hibernate 6์—์„œ๋Š” DISTINCT ํ‚ค์›Œ๋“œ๊ฐ€ ๋ถˆํ•„์š”ํ•˜๋ฉฐ ์ž๋™์œผ๋กœ ์ค‘๋ณต์„ ์ œ๊ฑฐํ•ฉ๋‹ˆ๋‹ค
  • Hibernate 5๋ฅผ ์‚ฌ์šฉ ์ค‘์ด๋ผ๋ฉด distinct()๊ฐ€ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๋ ˆ๋ฒจ์—์„œ ์ถ”๊ฐ€ ์ฒ˜๋ฆฌ๋ฅผ ์ˆ˜ํ–‰ํ•˜๋ฏ€๋กœ, ํ”„๋กœ์ ํŠธ์˜ Hibernate ๋ฒ„์ „์„ ํ™•์ธํ•˜์„ธ์š”. ํ•„์š”์‹œ hibernate.query.passDistinctThrough=false ํžŒํŠธ ์‚ฌ์šฉ์„ ๊ฒ€ํ† ํ•˜์„ธ์š”.
๐Ÿค– Prompt for AI Agents
In
src/main/java/KUSITMS/WITHUS/domain/application/distributionRequest/repository/DistributionRequestRepositoryImpl.java
around lines 37 to 46, ensure the fetch-join optimization is correct: keep the
leftJoin(...).fetchJoin() calls to eagerly load assignments and organizationRole
(so DistributionRequest without assignments is still returned), but verify your
Hibernate versionโ€”if using Hibernate 6 you can remove distinct() because
Hibernate will deduplicate results automatically; if using Hibernate 5, keep
distinct() but add the query hint hibernate.query.passDistinctThrough=false (or
set the same property globally) to avoid pushing DISTINCT to the DB and causing
extra work.


}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import KUSITMS.WITHUS.domain.evaluation.evaluationCriteria.entity.EvaluationCriteria;
import KUSITMS.WITHUS.domain.evaluation.evaluationCriteria.enumerate.EvaluationType;
import KUSITMS.WITHUS.domain.organization.organizationRole.entity.OrganizationRole;

import java.util.List;

Expand All @@ -10,5 +11,6 @@ public interface EvaluationCriteriaRepository {
List<EvaluationCriteria> findByTypeAndRecruitment(EvaluationType type, Long recruitmentId);
Long countByRecruitment_IdAndEvaluationType(Long recruitmentId, EvaluationType stage);
List<EvaluationCriteria> findByRecruitment_IdAndEvaluationType(Long recruitmentId, EvaluationType stage);
List<EvaluationCriteria> findCommonAndByOrganizationRole(Long recruitmentId, EvaluationType type, OrganizationRole organizationRole);
List<EvaluationCriteria> findAllById(List<Long> criteriaIds);
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,24 @@

import KUSITMS.WITHUS.domain.evaluation.evaluationCriteria.entity.EvaluationCriteria;
import KUSITMS.WITHUS.domain.evaluation.evaluationCriteria.enumerate.EvaluationType;
import KUSITMS.WITHUS.domain.organization.organizationRole.entity.OrganizationRole;
import KUSITMS.WITHUS.global.exception.CustomException;
import KUSITMS.WITHUS.global.exception.ErrorCode;
import com.querydsl.core.types.dsl.BooleanExpression;
import com.querydsl.jpa.impl.JPAQueryFactory;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Repository;

import java.util.List;

import static KUSITMS.WITHUS.domain.evaluation.evaluationCriteria.entity.QEvaluationCriteria.evaluationCriteria;

@Repository
@RequiredArgsConstructor
public class EvaluationCriteriaRepositoryImpl implements EvaluationCriteriaRepository {

private final EvaluationCriteriaJpaRepository evaluationCriteriaJpaRepository;
private final JPAQueryFactory queryFactory;

@Override
public EvaluationCriteria getById(Long id) {
Expand All @@ -35,6 +41,23 @@ public List<EvaluationCriteria> findByRecruitment_IdAndEvaluationType(Long recru
return evaluationCriteriaJpaRepository.findByRecruitment_IdAndEvaluationType(recruitmentId, stage);
}

@Override
public List<EvaluationCriteria> findCommonAndByOrganizationRole(Long recruitmentId, EvaluationType type, OrganizationRole organizationRole) {
BooleanExpression roleFilter = organizationRole == null
? evaluationCriteria.organizationRole.isNull()
: evaluationCriteria.organizationRole.isNull()
.or(evaluationCriteria.organizationRole.eq(organizationRole));

return queryFactory
.selectFrom(evaluationCriteria)
.where(
evaluationCriteria.recruitment.id.eq(recruitmentId)
.and(evaluationCriteria.evaluationType.eq(type))
.and(roleFilter)
)
.fetch();
}

@Override
public List<EvaluationCriteria> findAllById(List<Long> criteriaIds) {
return evaluationCriteriaJpaRepository.findAllById(criteriaIds);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package KUSITMS.WITHUS.domain.recruitment.position.dto;

import KUSITMS.WITHUS.domain.organization.organizationRole.entity.OrganizationRole;
import KUSITMS.WITHUS.domain.recruitment.position.entity.Position;
import io.swagger.v3.oas.annotations.media.Schema;

Expand All @@ -23,5 +24,17 @@ public static Detail from(Position position) {
position.getColor()
);
}

public static Detail from(OrganizationRole organizationRole) {
if (organizationRole == null) {
return null;
}

return new Detail(
organizationRole.getId(),
organizationRole.getName(),
organizationRole.getColor()
);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,28 @@
import KUSITMS.WITHUS.domain.recruitment.position.repository.PositionRepository;
import KUSITMS.WITHUS.domain.recruitment.recruitment.entity.Recruitment;
import KUSITMS.WITHUS.domain.recruitment.recruitment.repository.RecruitmentRepository;
import com.querydsl.jpa.impl.JPAQueryFactory;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;

import static KUSITMS.WITHUS.domain.recruitment.recruitment.entity.QRecruitment.recruitment;
import static KUSITMS.WITHUS.domain.recruitment.recruitmentOrganizationRole.entity.QRecruitmentOrganizationRole.recruitmentOrganizationRole;
import static KUSITMS.WITHUS.domain.organization.organizationRole.entity.QOrganizationRole.organizationRole;

import KUSITMS.WITHUS.global.exception.CustomException;
import KUSITMS.WITHUS.global.exception.ErrorCode;

@Service
@Transactional(readOnly = true)
@RequiredArgsConstructor
public class PositionServiceImpl implements PositionService {

private final PositionRepository positionRepository;
private final RecruitmentRepository recruitmentRepository;
private final JPAQueryFactory queryFactory;

/**
* ํŒŒํŠธ ์ƒ์„ฑ
Expand Down Expand Up @@ -53,8 +62,20 @@ public void delete(Long id) {

@Override
public List<PositionResponseDTO.Detail> findAllByRecruitmentId(Long recruitmentId) {
return positionRepository.findAllByRecruitmentId(recruitmentId).stream()
.map(PositionResponseDTO.Detail::from)
// Recruitment์˜ RecruitmentOrganizationRole ๋ฆฌ์ŠคํŠธ์™€ ๊ฐ๊ฐ์˜ OrganizationRole์„ ํ•จ๊ป˜ ์กฐํšŒํ•˜๊ธฐ ์œ„ํ•ด fetch join ์‚ฌ์šฉ
Recruitment found = queryFactory
.selectFrom(recruitment)
.leftJoin(recruitment.positions, recruitmentOrganizationRole).fetchJoin()
.leftJoin(recruitmentOrganizationRole.organizationRole, organizationRole).fetchJoin()
.where(recruitment.id.eq(recruitmentId))
.fetchOne();

if (found == null) {
throw new CustomException(ErrorCode.RECRUITMENT_NOT_EXIST);
}

return found.getPositions().stream()
.map(ror -> PositionResponseDTO.Detail.from(ror.getOrganizationRole()))
.toList();
Comment on lines +65 to 79
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

โš ๏ธ Potential issue | ๐ŸŸ  Major

organizationRole์ด null์ผ ๊ฒฝ์šฐ NPE ๋ฐœ์ƒ ๊ฐ€๋Šฅ์„ฑ์ด ์žˆ์Šต๋‹ˆ๋‹ค.

Line 69์—์„œ leftJoin์„ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ์–ด organizationRole์ด null์ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. Line 78์—์„œ ror.getOrganizationRole()์ด null์„ ๋ฐ˜ํ™˜ํ•˜๋ฉด PositionResponseDTO.Detail.from(null) ํ˜ธ์ถœ ์‹œ NullPointerException์ด ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๐Ÿ”Ž null ํ•„ํ„ฐ๋ง์„ ์ถ”๊ฐ€ํ•œ ์ˆ˜์ • ์ œ์•ˆ
         return found.getPositions().stream()
+                .filter(ror -> ror.getOrganizationRole() != null)
                 .map(ror -> PositionResponseDTO.Detail.from(ror.getOrganizationRole()))
                 .toList();

๋˜๋Š” organizationRole์ด ํ•ญ์ƒ ์กด์žฌํ•ด์•ผ ํ•˜๋Š” ๋น„์ฆˆ๋‹ˆ์Šค ์š”๊ตฌ์‚ฌํ•ญ์ด๋ผ๋ฉด, leftJoin ๋Œ€์‹  join์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ์ ์ ˆํ•ฉ๋‹ˆ๋‹ค:

-                .leftJoin(recruitmentOrganizationRole.organizationRole, organizationRole).fetchJoin()
+                .join(recruitmentOrganizationRole.organizationRole, organizationRole).fetchJoin()

Committable suggestion skipped: line range outside the PR's diff.

๐Ÿค– Prompt for AI Agents
In
src/main/java/KUSITMS/WITHUS/domain/recruitment/position/service/PositionServiceImpl.java
around lines 65 to 79, the current leftJoin on
recruitmentOrganizationRole.organizationRole allows organizationRole to be null
and calling PositionResponseDTO.Detail.from(ror.getOrganizationRole()) may throw
an NPE; either change the fetch join to an inner join (replace leftJoin with
join) if business rules guarantee an OrganizationRole exists, or keep the
leftJoin and filter out null organizationRole values before mapping (e.g.,
stream().map(ror ->
ror.getOrganizationRole()).filter(Objects::nonNull).map(PositionResponseDTO.Detail::from)
or explicitly handle nulls by throwing a CustomException); implement the chosen
approach consistently so no null is passed into PositionResponseDTO.Detail.from.

}
}
Loading
Loading