Skip to content

Conversation

@rlajm1203
Copy link
Contributor

@rlajm1203 rlajm1203 commented Mar 25, 2025

📌 관련 이슈

closes #240

✒️ 작업 내용

  • 출석 현황 리스트 조회 / 출석 요약 정보 조회 시 @kssumin 님께서 작성하신, Calendar 서비스를 사용합니다.

스크린샷 🏞️ (선택)

💬 REVIEWER에게 요구사항 💬

Summary by CodeRabbit

  • 새 기능
    • 날짜 입력이 없을 경우, 시스템이 자동으로 적절한 시작 및 종료 날짜를 적용하여 출석 요약과 벌점 정보가 일관되게 표시됩니다.
  • 버그 수정
    • 누락된 날짜 입력으로 인한 데이터 불일치 이슈가 개선되어 보다 안정적인 결과를 제공합니다.

@rlajm1203 rlajm1203 added the 🔧refactor 코드 수정 label Mar 25, 2025
@rlajm1203 rlajm1203 requested review from Daae-Kim and kssumin March 25, 2025 14:18
@rlajm1203 rlajm1203 self-assigned this Mar 25, 2025
@coderabbitai
Copy link

coderabbitai bot commented Mar 25, 2025

Walkthrough

해당 PR에서는 AttendService.java 파일 내의 getMyAttendSummarygetPenaltyInfos 메소드에서 날짜 파라미터가 null일 경우, calendarProvider를 통해 기본 날짜를 할당하도록 로직을 변경하였습니다. 또한, getMyPenaltyRanking 메소드에서 myPenaltyPointnull일 경우 0으로 설정하여 계산의 무결성을 유지하도록 하였습니다.

Changes

파일 변경 내용
eeos/.../AttendService.java getMyAttendSummarygetPenaltyInfos 메소드에서 null 날짜 확인 후 calendarProvider로 기본 날짜 할당, getMyPenaltyRanking에서 myPenaltyPoint가 null일 경우 0으로 설정

Sequence Diagram(s)

sequenceDiagram
    participant C as Client
    participant AS as AttendService
    participant CP as CalendarProvider
    participant DB as Repository

    C->>AS: getMyAttendSummary/getPenaltyInfos(startDate, endDate)
    alt 날짜 파라미터가 null인 경우
        AS->>CP: 기본 startDate와 endDate 요청
    end
    AS->>DB: 데이터 조회 요청
    DB-->>AS: 조회 결과 반환
    AS-->>C: API 응답 반환
Loading

Assessment against linked issues

Objective Addressed Explanation
출석 현황 리스트 조회 시 날짜 정보 누락 시 관리자가 설정한 기본 날짜 사용 (#240)

Possibly related PRs

Suggested reviewers

  • kssumin
  • Daae-Kim

Poem

토끼가 뛰노는 코드 숲속에서
누락된 날짜도 금방 찾아내네
calendarProvider가 마법을 부리면
기본 날짜가 반짝이며 나타나네
우리 코드도 토끼처럼 경쾌하게 달리네 🐇🎉


📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 878f68a and df38544.

📒 Files selected for processing (1)
  • eeos/src/main/java/com/blackcompany/eeos/target/application/service/AttendService.java (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • eeos/src/main/java/com/blackcompany/eeos/target/application/service/AttendService.java
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: test

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai plan to trigger planning for file edits and PR creation.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@github-actions
Copy link

github-actions bot commented Mar 25, 2025

Test Results

89 tests  ±0   86 ✅ ±0   4s ⏱️ ±0s
35 suites ±0    3 💤 ±0 
35 files   ±0    0 ❌ ±0 

Results for commit df38544. ± Comparison against base commit 339e2d6.

♻️ This comment has been updated with latest results.

@coderabbitai coderabbitai bot added the feature 기능 개발 label Mar 25, 2025
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (11)
eeos/src/main/java/com/blackcompany/eeos/program/application/dto/response/CalendarPeriodApplicationQuery.java (1)

1-15: DTO 클래스의 날짜 타입 개선 제안

DTO 클래스가 기능적으로는 잘 구현되어 있습니다. 하지만 java.sql.Timestamp는 JDBC와 데이터베이스 작업을 위해 설계된 클래스로, 일반적인 애플리케이션 코드의 날짜/시간 처리에는 적합하지 않을 수 있습니다.

모던 자바에서는 다음과 같은 Java 8+ 날짜/시간 API를 사용하는 것이 좋습니다:

-import java.sql.Timestamp;
+import java.time.LocalDateTime;
 
 @Getter
 @AllArgsConstructor
 @NoArgsConstructor
 public class CalendarPeriodApplicationQuery implements AbstractApplicationDto {
-	private Timestamp startDate;
-	private Timestamp endDate;
+	private LocalDateTime startDate;
+	private LocalDateTime endDate;
 }

이렇게 변경하면 더 풍부한 날짜/시간 처리 API를 활용할 수 있고, 타입 변환 시 발생할 수 있는 문제도 줄일 수 있습니다.

eeos/src/main/java/com/blackcompany/eeos/program/application/dto/request/CalendarApplicationCommand.java (1)

1-10: 날짜 타입 및 검증 로직 개선 제안

레코드를 사용한 불변 데이터 구조 설계와 검증 애노테이션 적용은 좋은 방식입니다. 두 가지 개선 사항을 제안합니다:

  1. java.sql.Timestamp 대신 Java 8+ 날짜/시간 API 사용:
-import java.sql.Timestamp;
+import java.time.LocalDateTime;

 public record CalendarApplicationCommand(
-		@NotNull(message = "시작 날짜는 필수입니다") Timestamp startDate,
-		@NotNull(message = "종료 날짜는 필수입니다") Timestamp endDate)
+		@NotNull(message = "시작 날짜는 필수입니다") LocalDateTime startDate,
+		@NotNull(message = "종료 날짜는 필수입니다") LocalDateTime endDate)
 		implements AbstractApplicationDto {}
  1. 시작 날짜가 종료 날짜보다 앞서야 한다는 추가 검증 로직 도입:
// 클래스 레벨에 아래 애노테이션 추가
@CheckStartDateBeforeEndDate
public record CalendarApplicationCommand(...)

그리고 별도의 커스텀 검증 애노테이션을 구현하여 두 날짜의 순서를 검증하는 것이 좋습니다.

eeos/src/main/java/com/blackcompany/eeos/target/application/service/AttendService.java (1)

279-282: TODO 주석이 남아있습니다.

이미 calendarProvider를 사용하여 시작일과 종료일을 설정하고 있으므로, 279번 라인의 TODO 주석은 더 이상 필요하지 않습니다.

-	// TODO: startDate 와 endDate 시간 설정하기
 	Timestamp startDate = calendarProvider.getCalendar().getStartDate();
 	Timestamp endDate = calendarProvider.getCalendar().getEndDate();
eeos/src/main/java/com/blackcompany/eeos/program/presentation/controller/CalendarController.java (1)

24-31: TODO 코멘트 처리 및 HTTP 상태 코드 검토 필요

  1. 어드민 검증에 대한 TODO 코멘트가 있습니다. 이 작업이 후속 PR에서 처리될 예정인지, 아니면 현재 PR에서 해결해야 하는지 명확히 하는 것이 좋을 것 같습니다.

  2. PUT 요청에 대해 HTTP 상태 코드로 CREATED(201)를 반환하고 있습니다. 일반적으로 PUT 요청(리소스 업데이트)에는 OK(200) 또는 NO_CONTENT(204)가 더 적합합니다. CREATED는 보통 POST 요청(새 리소스 생성)에 사용됩니다.

- return ApiResponseGenerator.success(response, HttpStatus.CREATED, MessageCode.CREATE);
+ return ApiResponseGenerator.success(response, HttpStatus.OK, MessageCode.UPDATE);
eeos/src/main/java/com/blackcompany/eeos/program/application/model/CalendarModel.java (2)

19-37: 날짜 계산 로직 개선 제안

기본 생성자에서 학기 기간을 계산하는 로직이 잘 구현되어 있으나, 몇 가지 개선 사항을 제안합니다:

  1. 복잡한 종료일 계산 로직(27-31라인)을 별도의 헬퍼 메소드로 추출하여 가독성을 높이는 것이 좋습니다.
  2. 종료일을 23:59:59로 설정하는 것보다 다음날 00:00:00(경계 제외)로 설정하는 패턴이 날짜 계산시 오류를 줄일 수 있습니다.
  3. 학기 기간(3월8월, 9월2월)이 하드코딩되어 있어 변경 시 코드 수정이 필요합니다. 향후 유연성을 위해 설정으로 관리하는 방안을 고려해보세요.
  4. Timestamp 대신 Java 8의 LocalDateTime을 사용하는 것이 더 현대적인 접근법입니다.
-	public CalendarModel() {
-		LocalDate now = LocalDate.now();
-
-		// 9월 이후이면 9월 1일부터 다음 해 2월 말까지
-		if (now.getMonthValue() >= Month.SEPTEMBER.getValue()) {
-			startDate = Timestamp.valueOf(LocalDate.of(now.getYear(), Month.SEPTEMBER, 1).atStartOfDay());
-			endDate =
-					Timestamp.valueOf(
-							LocalDate.of(
-											now.getYear() + 1,
-											Month.FEBRUARY,
-											Month.FEBRUARY.length(Year.isLeap(now.getYear() + 1)))
-									.atTime(23, 59, 59));
-		} else {
-			// 3월 이후이면 3월 1일부터 8월 말까지
-			startDate = Timestamp.valueOf(LocalDate.of(now.getYear(), Month.MARCH, 1).atStartOfDay());
-			endDate = Timestamp.valueOf(LocalDate.of(now.getYear(), Month.AUGUST, 31).atTime(23, 59, 59));
-		}
-	}
+	public CalendarModel() {
+		LocalDate now = LocalDate.now();
+		initializeDefaultPeriod(now);
+	}
+
+	private void initializeDefaultPeriod(LocalDate now) {
+		if (now.getMonthValue() >= Month.SEPTEMBER.getValue()) {
+			// 9월 이후이면 9월 1일부터 다음 해 2월 말까지
+			setFirstSemesterPeriod(now.getYear());
+		} else {
+			// 3월 이후이면 3월 1일부터 8월 말까지
+			setSecondSemesterPeriod(now.getYear());
+		}
+	}
+
+	private void setFirstSemesterPeriod(int year) {
+		startDate = Timestamp.valueOf(LocalDate.of(year, Month.SEPTEMBER, 1).atStartOfDay());
+		// 다음날 00:00:00 사용으로 경계값 문제 방지
+		LocalDate endLocalDate = LocalDate.of(
+				year + 1,
+				Month.MARCH,
+				1);
+		endDate = Timestamp.valueOf(endLocalDate.atStartOfDay());
+	}
+
+	private void setSecondSemesterPeriod(int year) {
+		startDate = Timestamp.valueOf(LocalDate.of(year, Month.MARCH, 1).atStartOfDay());
+		// 다음날 00:00:00 사용으로 경계값 문제 방지
+		LocalDate endLocalDate = LocalDate.of(year, Month.SEPTEMBER, 1);
+		endDate = Timestamp.valueOf(endLocalDate.atStartOfDay());
+	}

14-14: 미사용 로그 어노테이션

@Slf4j 어노테이션이 선언되어 있지만 클래스 내에서 로깅이 사용되지 않고 있습니다. 사용하지 않는 경우 제거하거나, 날짜 계산 로직에 디버그 로그를 추가하는 것이 좋습니다.

eeos/src/main/java/com/blackcompany/eeos/program/persistence/CalendarEntity.java (2)

19-23: 인덱스 이름과 컬럼 리스트 간의 불일치

인덱스 이름(idx_calendar_created_date_id)과 실제 컬럼 리스트(createdDate DESC, calendar_id DESC)가 정확히 일치하지 않습니다. 인덱스 이름에 포함된 컬럼명과 실제 컬럼명을 일치시키는 것이 유지보수성에 도움이 됩니다.

			@Index(
					name = "idx_calendar_created_date_id",
-					columnList = "createdDate DESC, calendar_id DESC")
+					columnList = "created_date DESC, calendar_id DESC")

34-38: 날짜 타입 개선 제안

startDateendDate 필드에 Timestamp 타입을 사용하고 있습니다. JDBC 타입인 Timestamp 대신 Java 8의 LocalDateTime을 사용하면 더 현대적이고 타입 안전한 날짜 처리가 가능합니다. JPA 2.2 이상에서는 LocalDateTime을 기본 지원합니다.

-	private Timestamp startDate;
+	private LocalDateTime startDate;

-	private Timestamp endDate;
+	private LocalDateTime endDate;

또한, 관련 import문도 수정이 필요합니다:

-import java.sql.Timestamp;
+import java.time.LocalDateTime;
eeos/src/main/java/com/blackcompany/eeos/program/persistence/CalendarRepositoryImpl.java (3)

1-8: 필요한 import 선언이 누락된 것 같습니다.

CalendarEntity 클래스를 사용하고 있지만 import 문에 선언되어 있지 않습니다. 필요한 import를 추가해주세요.

package com.blackcompany.eeos.program.persistence;

import com.blackcompany.eeos.program.application.model.CalendarModel;
import com.blackcompany.eeos.program.application.repository.CalendarRepository;
import java.util.Optional;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Repository;
+import com.blackcompany.eeos.program.persistence.entity.CalendarEntity;

26-31: toModel 메서드의 개선 가능성

현재 엔티티에서 모델로 변환할 때 startDateendDate 필드만 매핑하고 있습니다. 엔티티에 다른 중요한 필드(예: ID)가 있다면 이를 모델에도 포함시키는 것이 좋을 수 있습니다. 또한 null 체크도 고려해보세요.

private CalendarModel toModel(CalendarEntity entity) {
+    if (entity == null) {
+        return null;
+    }
    return CalendarModel.builder()
+            .id(entity.getId())
            .startDate(entity.getStartDate())
            .endDate(entity.getEndDate())
            .build();
}

33-38: toEntity 메서드의 개선 가능성

현재 모델에서 엔티티로 변환할 때 startDateendDate 필드만 매핑하고 있습니다. 업데이트 시나리오에서는 ID를 포함시켜야 할 수 있습니다. 또한 null 체크도 고려해보세요.

private CalendarEntity toEntity(CalendarModel model) {
+    if (model == null) {
+        return null;
+    }
    return CalendarEntity.builder()
+            .id(model.getId())
            .startDate(model.getStartDate())
            .endDate(model.getEndDate())
            .build();
}
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e90b5e4 and 0920c11.

📒 Files selected for processing (14)
  • eeos/src/main/java/com/blackcompany/eeos/program/application/dto/request/CalendarApplicationCommand.java (1 hunks)
  • eeos/src/main/java/com/blackcompany/eeos/program/application/dto/response/CalendarPeriodApplicationQuery.java (1 hunks)
  • eeos/src/main/java/com/blackcompany/eeos/program/application/model/CalendarModel.java (1 hunks)
  • eeos/src/main/java/com/blackcompany/eeos/program/application/repository/CalendarRepository.java (1 hunks)
  • eeos/src/main/java/com/blackcompany/eeos/program/application/service/CalendarService.java (1 hunks)
  • eeos/src/main/java/com/blackcompany/eeos/program/application/support/CalendarProvider.java (1 hunks)
  • eeos/src/main/java/com/blackcompany/eeos/program/application/usecase/GetCalendarUsecase.java (1 hunks)
  • eeos/src/main/java/com/blackcompany/eeos/program/application/usecase/UpdateCalendarUsecase.java (1 hunks)
  • eeos/src/main/java/com/blackcompany/eeos/program/persistence/CalendarEntity.java (1 hunks)
  • eeos/src/main/java/com/blackcompany/eeos/program/persistence/CalendarRepositoryImpl.java (1 hunks)
  • eeos/src/main/java/com/blackcompany/eeos/program/persistence/JpaCalendarRepository.java (1 hunks)
  • eeos/src/main/java/com/blackcompany/eeos/program/presentation/controller/CalendarController.java (1 hunks)
  • eeos/src/main/java/com/blackcompany/eeos/program/presentation/dto/UpdateCalendarRequest.java (1 hunks)
  • eeos/src/main/java/com/blackcompany/eeos/target/application/service/AttendService.java (4 hunks)
🔇 Additional comments (19)
eeos/src/main/java/com/blackcompany/eeos/program/application/support/CalendarProvider.java (1)

1-16: 잘 구현된 캘린더 제공자 클래스

이 캘린더 제공자 클래스는 리포지토리와 서비스 계층 사이의 인터페이스 역할을 잘 수행하고 있습니다. 다음과 같은 좋은 설계 원칙들이 적용되었습니다:

  1. 의존성 주입을 사용하여 결합도를 낮추었습니다
  2. 단일 책임 원칙을 따라 캘린더 데이터 제공이라는 하나의 역할만 수행합니다
  3. orElse를 사용하여 널 안전성을 확보했습니다

다만, getCalendar()에서 리포지토리에서 데이터를 가져오지 못했을 때 로깅을 추가하는 것을 고려해볼 수 있습니다.

eeos/src/main/java/com/blackcompany/eeos/program/application/usecase/GetCalendarUsecase.java (1)

1-7: 클린 아키텍처를 잘 적용한 유스케이스 인터페이스

이 인터페이스는 클린 아키텍처의 유스케이스 패턴을 잘 따르고 있습니다. 메서드가 명확하게 정의되어 있고, 단일 책임을 가지며, 인터페이스 분리 원칙(ISP)을 잘 적용하고 있습니다. 이렇게 인터페이스를 통해 계약을 정의함으로써 의존성 역전 원칙(DIP)도 만족시키고 있어 좋습니다.

eeos/src/main/java/com/blackcompany/eeos/program/application/usecase/UpdateCalendarUsecase.java (1)

6-8: 잘 정의된 인터페이스입니다.

이 인터페이스는 캘린더를 업데이트하기 위한 명확한 계약을 정의합니다. 명령-쿼리 분리 원칙을 따르고 있으며, 메서드 시그니처가 간결하고 명확합니다.

eeos/src/main/java/com/blackcompany/eeos/program/presentation/dto/UpdateCalendarRequest.java (2)

7-9: 유효성 검증이 잘 구현되어 있습니다.

시작일과 종료일에 대한 @NotNull 검증과 한글 에러 메시지가 명확하게 제공되어 있습니다.


11-14: 날짜 로직 검증이 적절합니다.

종료일이 시작일 이후인지 확인하는 유효성 검증이 잘 구현되어 있습니다. 단순히 타임스탬프 값을 비교하는 방식으로 효율적으로 구현되었습니다.

eeos/src/main/java/com/blackcompany/eeos/target/application/service/AttendService.java (3)

15-15: 새로운 의존성이 적절하게 추가되었습니다.

CalendarProvider 임포트가 추가되었습니다.


90-90: 의존성 주입이 올바르게 설정되었습니다.

CalendarProvider가 필드로 추가되어 생성자 주입을 통해 적절하게 사용될 수 있습니다.


250-252: 날짜 초기화 로직이 개선되었습니다.

시작일과 종료일이 제공되지 않았을 때 calendarProvider를 통해 동적으로 가져오도록 개선되었습니다. 하드코딩된 값 대신 중앙화된 날짜 관리 방식을 사용하여 유지보수성이 향상되었습니다.

eeos/src/main/java/com/blackcompany/eeos/program/application/service/CalendarService.java (3)

14-20: 서비스 클래스가 잘 구성되어 있습니다.

CalendarService는 적절한 Spring 어노테이션을 사용하고 있으며, 필요한 의존성을 생성자 주입을 통해 받고 있습니다. 또한 인터페이스를 구현하여 느슨한 결합을 유지하고 있습니다.


21-25: 캘린더 조회 메서드가 잘 구현되어 있습니다.

getCalendar 메서드는 calendarProvider를 통해 캘린더 모델을 가져와 적절한 응답 객체로 변환하고 있습니다.


27-34: 트랜잭션 관리가 적절하게 구현되어 있습니다.

updateCalendar 메서드는 @Transactional 어노테이션을 통해 쓰기 작업에 대한 트랜잭션을 적절하게 관리하고 있습니다. 또한 명령 객체의 값을 모델로 변환하고 저장소를 통해 업데이트한 후 응답 객체를 반환하는 로직이 명확합니다.

eeos/src/main/java/com/blackcompany/eeos/program/presentation/controller/CalendarController.java (2)

1-20: 코드 구조가 깔끔하게 작성되었습니다.

컨트롤러 클래스가 적절히 구성되어 있으며, 의존성 주입이 생성자를 통해 이루어지고 있어 좋은 패턴을 따르고 있습니다.


33-37: GET 엔드포인트 구현이 적절합니다.

캘린더 정보를 조회하는 API가 잘 구현되어 있습니다. 적절한 HTTP 상태 코드와 메시지 코드를 사용하고 있습니다.

eeos/src/main/java/com/blackcompany/eeos/program/persistence/JpaCalendarRepository.java (1)

1-8: 적절한 JPA 레포지토리 인터페이스 정의

Spring Data JPA 패턴을 잘 따르고 있으며, findTopByOrderByCreatedDateDesc() 메서드를 통해 가장 최근에 생성된 캘린더 엔티티를 조회하는 기능이 명확합니다. Optional을 반환 타입으로 사용하여 값이 없을 수 있는 상황을 적절히 처리하고 있습니다.

eeos/src/main/java/com/blackcompany/eeos/program/application/repository/CalendarRepository.java (1)

1-10: 잘 설계된 레포지토리 인터페이스

애플리케이션 계층에서의 레포지토리 패턴을 잘 구현하고 있습니다. getCalendar()에서 Optional을 반환 타입으로 사용하여 값이 없을 수 있는 상황을 적절히 처리하고 있으며, 메소드 이름이 기능을 명확하게 나타내고 있습니다.

eeos/src/main/java/com/blackcompany/eeos/program/persistence/CalendarEntity.java (1)

11-26: 엔티티 구조 및 소프트 삭제 구현

CalendarEntity 클래스의 전반적인 구조가 잘 설계되어 있습니다. @SQLDelete@Where 어노테이션을 사용한 소프트 삭제 구현이 적절하게 되어 있으며, 생성자에 대한 접근 제한자(@NoArgsConstructor(access = AccessLevel.PROTECTED), @AllArgsConstructor(access = AccessLevel.PRIVATE))도 JPA 엔티티에 적합하게 설정되어 있습니다.

eeos/src/main/java/com/blackcompany/eeos/program/persistence/CalendarRepositoryImpl.java (3)

11-12: 적절한 의존성 주입 방식을 사용하고 있습니다.

@RequiredArgsConstructorfinal 필드를 사용하여 의존성 주입을 처리한 것은 좋은 방식입니다. Spring에서 권장하는 생성자 주입 패턴을 따르고 있습니다.


14-17: 코드가 간결하고 Optional을 적절히 활용하고 있습니다.

findTopByOrderByCreatedDateDesc()를 통해 최신 캘린더 엔티티를 조회하고, Optional의 map 메서드를 사용하여 엔티티를 모델로 변환하는 방식이 효율적입니다.


19-24:

Details

❓ Verification inconclusive

ID 처리에 대한 고려가 필요합니다.

현재 updateCalendar 메서드에서는 ID 필드를 처리하지 않고 있습니다. 이 메서드가 새로운 엔티티를 생성하는 것인지, 기존 엔티티를 업데이트하는 것인지 명확하지 않습니다. 의도에 따라 ID 필드를 처리하는 로직을 추가하는 것이 좋겠습니다.


🌐 Web query:

Spring Data JPA 에서 save 메서드의 동작 방식은?

💡 Result:

Spring Data JPA의 save() 메서드는 엔티티의 상태에 따라 persist 또는 merge를 선택적으로 호출합니다. 이 동작은 엔티티가 새로운 객체인지 여부를 판단하는 isNew() 메서드를 기반으로 결정됩니다.

기본 동작 방식

  1. 새로운 엔티티 저장 (INSERT)

    • ID가 null이거나 @GeneratedValue로 생성된 경우: em.persist()를 호출하여 즉시 INSERT 쿼리를 생성하지 않고, 트랜잭션 커밋 시점에 실행됩니다.
    • 예시 코드:
      @Entity
      public class Item {
          @Id @GeneratedValue
          private Long id;  // ID가 자동 생성되는 경우
      }
  2. 기존 엔티티 수정 (UPDATE)

    • ID가 이미 존재하는 경우: em.merge()를 호출합니다. 이때 SELECT 쿼리를 먼저 실행하여 DB에 해당 엔티티가 존재하는지 확인한 후, 없으면 INSERT, 있으면 UPDATE 쿼리를 실행합니다[1][4][14].
    • 예시 코드:
      Item item = itemRepository.findById(1L).orElseThrow();
      item.setName("Updated");  // Dirty Checking으로 UPDATE 처리

ID를 직접 할당하는 경우의 문제점

수동으로 ID를 설정하는 엔티티에서는 isNew()false로 판단되어 merge()가 호출되며, 이로 인해 불필요한 SELECT 쿼리가 발생합니다[1][4][14].

@Entity
public class Item {
    @Id
    private String id;  // ID를 수동으로 할당
}

해결 방법: Persistable 인터페이스를 구현하여 isNew() 로직을 재정의합니다.

@Entity
@EntityListeners(AuditingEntityListener.class)
public class Item implements Persistable<String> {
    @Id
    private String id;
    
    @CreatedDate  // 엔티티 저장 시 자동으로 설정됨
    private LocalDateTime createdDate;

    @Override
    public boolean isNew() {
        return createdDate == null;  // 생성 시간으로 새로운 엔티티 판단
    }
}
  • @CreatedDate 필드가 null이면 새로운 엔티티로 간주되어 persist()가 호출됩니다[1][4][12].

Merge vs. Dirty Checking

  • Merge 사용 시 문제점:

    • 전체 컬럼을 UPDATE하며, SELECT 쿼리가 선행됩니다[2][4].
    • 권장되지 않으며, Dirty Checking을 통해 변경된 필드만 UPDATE하는 것이 효율적입니다[2][3].
  • Dirty Checking 예시:

    @Transactional
    public void updateItem(Long id, String newName) {
        Item item = itemRepository.findById(id).orElseThrow();
        item.setName(newName);  // 별도의 save() 호출 없이 트랜잭션 종료 시 자동 반영
    }

성능 최적화 팁

  1. save() 대신 Dirty Checking 사용:
    트랜잭션 내에서 엔티티를 조회하고 필드를 수정하면 save() 없이도 UPDATE가 자동 반영됩니다[3][6].
  2. saveAll() 사용:
    여러 엔티티를 저장할 때 saveAll()을 사용하면 트랜잭션 1회로 처리되어 성능이 향상됩니다[9].

요약

  • save()ID 생성 전략과 엔티티 상태(new 또는 managed)에 따라 동작이 달라집니다.
  • ID를 수동으로 할당할 경우 Persistable 구현이 필수적입니다.
  • 업데이트 시에는 save()보다 Dirty Checking을 활용하는 것이 성능상 유리합니다.

Citations:


ID 필드 처리 로직 검토 필요
현재 updateCalendar() 메서드에서는 toEntity(model) 호출 결과를 즉시 jpaCalendarRepository.save()로 전달하고 있습니다. Spring Data JPA의 save() 메서드는 엔티티의 ID 상태에 따라 신규 엔티티(persist) 또는 기존 엔티티 업데이트(merge)를 결정합니다.

  • 만약 CalendarEntity의 ID가 @GeneratedValue 등을 통해 자동으로 관리된다면, ID 필드에 대한 별도의 처리가 없더라도 정상적으로 동작할 수 있습니다.
  • 하지만 업데이트가 의도된 경우라면, 전달받은 CalendarModel에 올바른 ID 값이 포함되어 있는지 확인하거나, 엔티티가 기존에 존재하는지 검증하는 로직을 추가하는 것이 좋겠습니다.
  • 만약 수동으로 ID가 지정되거나, 업데이트와 신규 생성의 로직이 명확히 구분되어야 한다면, Persistable 인터페이스를 구현하여 isNew() 메서드로 신규 여부를 재정의하는 방법도 고려해 보시기 바랍니다.

해당 부분에 대해 위의 사항을 검토하시어, 의도에 맞는 ID 처리가 구현될 수 있도록 추가 검토 및 수정 부탁드립니다.

@rlajm1203 rlajm1203 merged commit 8d2efe3 into develop Mar 25, 2025
3 checks passed
@rlajm1203 rlajm1203 deleted the refactor/BM/#240/attend-list branch March 25, 2025 16:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature 기능 개발 🔧refactor 코드 수정

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[REFACTOR] 나의 출석 현황 리스트 조회 API를 수정합니다.

3 participants