From 96a13be543e3964e4a6f4bdfeb092cc0ac5ffe73 Mon Sep 17 00:00:00 2001 From: sso0om Date: Tue, 14 Oct 2025 11:59:49 +0900 Subject: [PATCH 1/2] =?UTF-8?q?Feat:=20=EC=8B=9C=EA=B0=84=20=EC=A7=80?= =?UTF-8?q?=EB=82=9C=20=EC=8A=AC=EB=A1=AF=20=EC=83=81=ED=83=9C=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD=20=EC=8A=A4=EC=BC=80=EC=A4=84=EB=A7=81=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84=20(#318)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Feat: 30분마다 예약 이력 없는 기간 지난 스케줄링 제거 * Refactor: 서버 시간 기준으로 변경 --- .../slot/repository/MentorSlotRepository.java | 24 ++++++++++++------- .../slot/service/MentorSlotService.java | 24 ++++++++++++++++++- .../slot/service/MentorSlotServiceTest.java | 5 ++-- 3 files changed, 42 insertions(+), 11 deletions(-) diff --git a/back/src/main/java/com/back/domain/mentoring/slot/repository/MentorSlotRepository.java b/back/src/main/java/com/back/domain/mentoring/slot/repository/MentorSlotRepository.java index 82c73db5..12f3e6df 100644 --- a/back/src/main/java/com/back/domain/mentoring/slot/repository/MentorSlotRepository.java +++ b/back/src/main/java/com/back/domain/mentoring/slot/repository/MentorSlotRepository.java @@ -3,6 +3,7 @@ import com.back.domain.mentoring.slot.dto.response.MentorSlotDto; import com.back.domain.mentoring.slot.entity.MentorSlot; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; @@ -24,12 +25,7 @@ public interface MentorSlotRepository extends JpaRepository { ms.mentor.member.id, ms.startDateTime, ms.endDateTime, - CASE - WHEN ms.startDateTime < CURRENT_TIMESTAMP - AND ms.status = com.back.domain.mentoring.slot.constant.MentorSlotStatus.AVAILABLE - THEN com.back.domain.mentoring.slot.constant.MentorSlotStatus.EXPIRED - ELSE ms.status - END, + ms.status, r.id ) FROM MentorSlot ms @@ -60,7 +56,7 @@ List findMySlots( FROM MentorSlot ms WHERE ms.mentor.id = :mentorId AND ms.status = 'AVAILABLE' - AND ms.startDateTime >= CURRENT_TIMESTAMP + AND ms.startDateTime >= :now AND ms.startDateTime < :end AND ms.endDateTime >= :start ORDER BY ms.startDateTime ASC @@ -68,7 +64,8 @@ List findMySlots( List findAvailableSlots( @Param("mentorId") Long mentorId, @Param("start") LocalDateTime start, - @Param("end") LocalDateTime end + @Param("end") LocalDateTime end, + @Param("now") LocalDateTime now ); @Query(""" @@ -100,4 +97,15 @@ boolean existsOverlappingExcept( @Param("start") LocalDateTime start, @Param("end") LocalDateTime end ); + + @Modifying + @Query(""" + UPDATE MentorSlot ms + SET ms.status = com.back.domain.mentoring.slot.constant.MentorSlotStatus.EXPIRED + WHERE ms.startDateTime < :now + AND ms.status = com.back.domain.mentoring.slot.constant.MentorSlotStatus.AVAILABLE + """) + int expirePassedSlots( + @Param("now") LocalDateTime now + ); } diff --git a/back/src/main/java/com/back/domain/mentoring/slot/service/MentorSlotService.java b/back/src/main/java/com/back/domain/mentoring/slot/service/MentorSlotService.java index 0103cecf..c9b496e4 100644 --- a/back/src/main/java/com/back/domain/mentoring/slot/service/MentorSlotService.java +++ b/back/src/main/java/com/back/domain/mentoring/slot/service/MentorSlotService.java @@ -12,6 +12,8 @@ import com.back.domain.mentoring.slot.repository.MentorSlotRepository; import com.back.global.exception.ServiceException; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -24,6 +26,7 @@ @Service @RequiredArgsConstructor +@Slf4j public class MentorSlotService { private final MentorSlotRepository mentorSlotRepository; @@ -40,7 +43,8 @@ public List getMyMentorSlots(Mentor mentor, LocalDateTime startDa public List getAvailableMentorSlots(Long mentorId, LocalDateTime startDate, LocalDateTime endDate) { DateTimeValidator.validateTime(startDate, endDate); - return mentorSlotRepository.findAvailableSlots(mentorId, startDate, endDate); + LocalDateTime now = LocalDateTime.now(); + return mentorSlotRepository.findAvailableSlots(mentorId, startDate, endDate, now); } @Transactional(readOnly = true) @@ -147,6 +151,24 @@ private LocalDate findNextOrSameDayOfWeek(LocalDate startDate, DayOfWeek targetD } + // ===== 스케줄러 ===== + + /** + * 매 시간 30분에 지난 슬롯 만기 처리 + * - AVAILABLE -> EXPIRED + */ + @Scheduled(cron = "0 */30 * * * *") + @Transactional + public void expirePassedSlots() { + LocalDateTime now = LocalDateTime.now(); + + int updateCount = mentorSlotRepository.expirePassedSlots(now); + if (updateCount > 0) { + log.info("만료된 슬롯 {}개 업데이트 완료 at {}", updateCount, now); + } + } + + // ===== 검증 메서드 ===== private static void validateOwner(MentorSlot mentorSlot, Mentor mentor) { diff --git a/back/src/test/java/com/back/domain/mentoring/slot/service/MentorSlotServiceTest.java b/back/src/test/java/com/back/domain/mentoring/slot/service/MentorSlotServiceTest.java index 7c3870e9..faf752d7 100644 --- a/back/src/test/java/com/back/domain/mentoring/slot/service/MentorSlotServiceTest.java +++ b/back/src/test/java/com/back/domain/mentoring/slot/service/MentorSlotServiceTest.java @@ -174,8 +174,9 @@ void getAvailableMentorSlots() { ); List availableSlots = List.of(slotDto1, slotDto2); + LocalDateTime now = LocalDateTime.now(); - when(mentorSlotRepository.findAvailableSlots(mentor1.getId(), startDate, endDate)) + when(mentorSlotRepository.findAvailableSlots(mentor1.getId(), startDate, endDate, now)) .thenReturn(availableSlots); // when @@ -183,7 +184,7 @@ void getAvailableMentorSlots() { // then assertThat(result).hasSize(2); - verify(mentorSlotRepository).findAvailableSlots(mentor1.getId(), startDate, endDate); + verify(mentorSlotRepository).findAvailableSlots(mentor1.getId(), startDate, endDate, now); } } From 587afc4604bf931e994637d176c2e8e8da012899 Mon Sep 17 00:00:00 2001 From: dooongdaeng Date: Tue, 14 Oct 2025 12:00:18 +0900 Subject: [PATCH 2/2] Fix/304 (#317) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: DB 외래키 문제 발생 로직 주석 처리 * fix: 기존과 동일하게 트랜잭션 처리하도록 수정 * Revert "[Fix] 운영 DB 외래키 문제 제" * fix:fetch join --------- Co-authored-by: sso0om Co-authored-by: 석희성 Co-authored-by: 신지섭 <41179427+tlswltjq@users.noreply.github.com> Co-authored-by: dbfgml20 <53211374+dbfgml2000@users.noreply.github.com> Co-authored-by: dbfgml20 --- .../domain/member/member/dto/MemberSearchResponse.java | 9 ++++++++- .../member/mentee/repository/MenteeRepository.java | 2 +- .../member/mentor/repository/MentorRepository.java | 2 +- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/back/src/main/java/com/back/domain/member/member/dto/MemberSearchResponse.java b/back/src/main/java/com/back/domain/member/member/dto/MemberSearchResponse.java index 4da2b5b2..69945e79 100644 --- a/back/src/main/java/com/back/domain/member/member/dto/MemberSearchResponse.java +++ b/back/src/main/java/com/back/domain/member/member/dto/MemberSearchResponse.java @@ -21,6 +21,13 @@ public record MemberSearchResponse( Long menteeId // 멘티 ID (멘티인 경우에만 값이 있음) ) { public static MemberSearchResponse from(Member member, Mentor mentor, Mentee mentee) { + String jobName = null; + if (mentor != null && mentor.getJob() != null) { + jobName = mentor.getJob().getName(); + } else if (mentee != null && mentee.getJob() != null) { + jobName = mentee.getJob().getName(); + } + return new MemberSearchResponse( member.getId(), member.getEmail(), @@ -30,7 +37,7 @@ public static MemberSearchResponse from(Member member, Mentor mentor, Mentee men member.getIsDeleted(), member.getCreateDate(), member.getModifyDate(), - mentor != null ? mentor.getJob().getName() : (mentee != null ? mentee.getJob().getName() : null), + jobName, mentor != null ? mentor.getCareerYears() : null, mentor != null ? mentor.getId() : null, mentee != null ? mentee.getId() : null diff --git a/back/src/main/java/com/back/domain/member/mentee/repository/MenteeRepository.java b/back/src/main/java/com/back/domain/member/mentee/repository/MenteeRepository.java index 2e5bddda..957f0ade 100644 --- a/back/src/main/java/com/back/domain/member/mentee/repository/MenteeRepository.java +++ b/back/src/main/java/com/back/domain/member/mentee/repository/MenteeRepository.java @@ -19,6 +19,6 @@ public interface MenteeRepository extends JpaRepository { Optional findById(@Param("id") Long id); // 삭제된 멘티 포함 조회 (관리자용) - @Query("SELECT m FROM Mentee m WHERE m.member.id = :memberId") + @Query("SELECT m FROM Mentee m LEFT JOIN FETCH m.job WHERE m.member.id = :memberId") Optional findByMemberIdIncludingDeleted(@Param("memberId") Long memberId); } \ No newline at end of file diff --git a/back/src/main/java/com/back/domain/member/mentor/repository/MentorRepository.java b/back/src/main/java/com/back/domain/member/mentor/repository/MentorRepository.java index d2808c77..6e7bff6f 100644 --- a/back/src/main/java/com/back/domain/member/mentor/repository/MentorRepository.java +++ b/back/src/main/java/com/back/domain/member/mentor/repository/MentorRepository.java @@ -19,6 +19,6 @@ public interface MentorRepository extends JpaRepository { Optional findById(@Param("id") Long id); // 삭제된 멘토 포함 조회 (관리자용) - @Query("SELECT m FROM Mentor m WHERE m.member.id = :memberId") + @Query("SELECT m FROM Mentor m LEFT JOIN FETCH m.job WHERE m.member.id = :memberId") Optional findByMemberIdIncludingDeleted(@Param("memberId") Long memberId); } \ No newline at end of file