Conversation
워크스루이 풀 리퀘스트는 새로운 설정 파일과 코스 관련 기능을 추가합니다. Changes
Sequence Diagram(s)sequenceDiagram
participant C as Client
participant CC as CourseController
participant CS as CourseService
participant VR as VetRepository
participant CR as CouresRepository
C->>CC: POST /courses (CreateCourseRequest)
CC->>CS: createCourse(name, description, difficulty, vetId)
CS->>VR: findById(vetId)
VR--)CS: Vet or Error
CS->>CR: save(Course)
CR--)CS: Saved Course
CS-->>CC: Course 객체 반환
CC-->>C: ResponseEntity (course id)
Poem
✨ Finishing Touches
🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
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)
Other keywords and placeholders
Documentation and Community
|
There was a problem hiding this comment.
Actionable comments posted: 6
🧹 Nitpick comments (16)
src/main/java/org/springframework/samples/petclinic/course/dto/CreateCourseRequest.java (2)
6-10: 요청 DTO에 유효성 검증이 필요합니다현재
CreateCourseRequest클래스에는 필드에 대한 유효성 검증이 없습니다. 강의명, 설명, 난이도 등에 대한 기본적인 유효성 검증을 추가하는 것이 좋습니다.다음과 같이 Bean Validation 어노테이션을 추가하세요:
@Getter() public class CreateCourseRequest { + @NotBlank(message = "강의명은 필수입니다") + @Size(max = 100, message = "강의명은 100자를 넘을 수 없습니다") private String name; + @Size(max = 500, message = "설명은 500자를 넘을 수 없습니다") private String description; + @NotBlank(message = "난이도는 필수입니다") private String difficulty; + @NotNull(message = "강사 ID는 필수입니다") private Integer vetId;위 수정을 적용하시려면 먼저 다음 import를 추가하세요:
import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Size;
5-5: Lombok 어노테이션 최적화
@Getter()와 같이 빈 괄호를 사용하지 않는 것이 관례입니다.다음과 같이 수정하세요:
-@Getter() +@Gettersrc/main/java/org/springframework/samples/petclinic/course/Course.java (2)
22-25: 필드 유효성 검증을 추가하는 것이 좋습니다.현재 문자열 필드들(
name,description,difficulty)에 대한 유효성 검증이 없습니다.@NotBlank,@Size등의 검증 애노테이션을 추가하여 데이터 무결성을 보장하는 것이 좋습니다.+import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Size; @Getter @Setter @Entity @Table(name = "courses") public class Course extends BaseEntity { + @NotBlank(message = "이름은 필수 항목입니다") + @Size(max = 255, message = "이름은 255자를 초과할 수 없습니다") private String name; + @Size(max = 1000, message = "설명은 1000자를 초과할 수 없습니다") private String description; // 예: 초급, 중급, 고급 + @NotBlank(message = "난이도는 필수 항목입니다") private String difficulty;
24-25: 난이도 필드를 Enum으로 관리하는 것이 좋습니다.주석에 명시된 것처럼 난이도는 정해진 값들('초급', '중급', '고급')만 사용할 것으로 보입니다. 이런 경우 String 대신 Enum을 사용하면 타입 안전성을 확보하고 코드의 가독성을 높일 수 있습니다.
- // 예: 초급, 중급, 고급 - private String difficulty; + @Enumerated(EnumType.STRING) + private CourseDifficulty difficulty;그리고 새로운 Enum 클래스를 생성합니다:
package org.springframework.samples.petclinic.course; public enum CourseDifficulty { 초급, 중급, 고급 }.coderabbit.yaml (4)
27-27: 행 끝의 공백을 제거해야 합니다.YAMLlint 정적 분석 도구에서 지적한 대로, 이 줄의 끝에 있는 공백을 제거해야 합니다.
- Review the API endpoints based on this guidelines. + Review the API endpoints based on this guidelines.🧰 Tools
🪛 YAMLlint (1.35.1)
[error] 27-27: trailing spaces
(trailing-spaces)
33-33: 행 끝의 공백을 제거해야 합니다.YAMLlint 정적 분석 도구에서 지적한 대로, 이 줄의 끝에 있는 공백을 제거해야 합니다.
- 4. Level 3: Hypermedia (HATEOAS) for advanced RESTful APIs. + 4. Level 3: Hypermedia (HATEOAS) for advanced RESTful APIs.🧰 Tools
🪛 YAMLlint (1.35.1)
[error] 33-33: trailing spaces
(trailing-spaces)
42-42: 행 끝의 공백을 제거해야 합니다.YAMLlint 정적 분석 도구에서 지적한 대로, 이 줄의 끝에 있는 공백을 제거해야 합니다.
- - Resource identifiers should be unique and only one per URI path. + - Resource identifiers should be unique and only one per URI path.🧰 Tools
🪛 YAMLlint (1.35.1)
[error] 42-42: trailing spaces
(trailing-spaces)
51-51: 행 끝의 공백을 제거해야 합니다.YAMLlint 정적 분석 도구에서 지적한 대로, 이 줄의 끝에 있는 공백을 제거해야 합니다.
- - Complex actions can include verbs in the URI (/orders/{orderId}/cancel). + - Complex actions can include verbs in the URI (/orders/{orderId}/cancel).🧰 Tools
🪛 YAMLlint (1.35.1)
[error] 51-51: trailing spaces
(trailing-spaces)
src/main/java/org/springframework/samples/petclinic/course/CourseController.java (5)
3-13: 불필요한 import 문이 있습니다.사용하지 않는 import 문이 있습니다:
org.springframework.samples.petclinic.vet.Vetorg.springframework.samples.petclinic.vet.VetRepositoryorg.springframework.web.bind.annotation.GetMappingorg.springframework.web.bind.annotation.RequestParam이러한 불필요한 import는 제거하는 것이 좋습니다.
package org.springframework.samples.petclinic.course; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; import org.springframework.samples.petclinic.course.dto.CreateCourseRequest; -import org.springframework.samples.petclinic.vet.Vet; -import org.springframework.samples.petclinic.vet.VetRepository; -import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController;
21-21: 불필요한 빈 줄이 있습니다.불필요한 빈 줄은 제거하는 것이 좋습니다.
public class CourseController { private final CourseService courseService; - @PostMapping()
22-22: PostMapping에 명확한 경로를 지정하는 것이 좋습니다.
@PostMapping()보다는 명시적으로@PostMapping("/")로 지정하는 것이 API 설계 관점에서 더 명확합니다.- @PostMapping() + @PostMapping("/")
24-24: System.out.println 대신 로깅 프레임워크를 사용해야 합니다.
System.out.println은 운영 환경에서 사용하기에 적합하지 않습니다. 로깅 프레임워크(예: SLF4J)를 사용하는 것이 좋습니다.+import org.slf4j.Logger; +import org.slf4j.LoggerFactory; @RestController @RequiredArgsConstructor @RequestMapping("/courses") public class CourseController { + private static final Logger log = LoggerFactory.getLogger(CourseController.class); private final CourseService courseService; @PostMapping() public ResponseEntity<Integer> createCourse(@RequestBody CreateCourseRequest request) { - System.out.println(request); + log.info("Course creation request received: {}", request);
23-29: 요청 본문에 대한 유효성 검증이 없습니다.현재 컨트롤러 메서드에서는 요청 본문에 대한 유효성 검증이 없습니다.
@Valid애노테이션을 사용하여 요청 데이터의 유효성을 검증하는 것이 좋습니다.+import jakarta.validation.Valid; @PostMapping() - public ResponseEntity<Integer> createCourse(@RequestBody CreateCourseRequest request) { + public ResponseEntity<Integer> createCourse(@Valid @RequestBody CreateCourseRequest request) {또한 CreateCourseRequest 클래스에 유효성 검증 애노테이션을 추가해야 합니다.
src/main/java/org/springframework/samples/petclinic/course/CourseService.java (3)
28-30: 더 구체적인 예외 처리가 필요합니다.일반적인
RuntimeException보다 더 구체적인 예외를 사용하는 것이 좋습니다. 사용자 정의 예외 클래스를 만들거나 Spring의EntityNotFoundException을 사용하는 것을 고려해보세요.Vet instructor = vetRepository.findById(vetId) - .orElseThrow(() -> new RuntimeException("Vet not found")); + .orElseThrow(() -> new EntityNotFoundException("Vet not found with ID: " + vetId));위 코드를 적용하려면 다음 import를 추가해야 합니다:
import javax.persistence.EntityNotFoundException;또는 사용자 정의 예외를 만들어 사용할 수도 있습니다:
import org.springframework.samples.petclinic.course.exception.VetNotFoundException; // ... .orElseThrow(() -> new VetNotFoundException("Vet not found with ID: " + vetId));
31-39: 입력 검증과 로깅이 필요합니다.현재 코드는 입력값에 대한 검증이 없습니다. 이름, 설명, 난이도가 null이거나 빈 문자열인 경우에 대한 처리가 필요합니다. 또한, 새 코스 생성 시 로깅을 추가하는 것이 좋습니다.
+ // 입력값 검증 + if (name == null || name.trim().isEmpty()) { + throw new IllegalArgumentException("Course name cannot be empty"); + } + Course course = new Course(); course.setName(name); course.setDescription(description); course.setDifficulty(difficulty); course.setInstructor(instructor); courseRepository.save(course); + + log.info("Course created: {}", course.getName()); return course;로깅을 위해 다음과 같은 import와 어노테이션을 추가하세요:
import lombok.extern.slf4j.Slf4j; @Slf4j @Service // ...
1-41: 서비스 메소드에 대한 단위 테스트가 필요합니다.이 서비스 클래스에 대한 단위 테스트를 작성하면 코드의 신뢰성을 높일 수 있습니다. 특히 강사가 존재하지 않는 경우의 예외 처리와 코스 생성 성공 케이스를 테스트하는 것이 중요합니다.
단위 테스트 예제 코드를 생성해드릴까요?
📜 Review details
Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (9)
.coderabbit.yaml(1 hunks)build.gradle(2 hunks)src/main/java/org/springframework/samples/petclinic/course/CouresRepository.java(1 hunks)src/main/java/org/springframework/samples/petclinic/course/Course.java(1 hunks)src/main/java/org/springframework/samples/petclinic/course/CourseController.java(1 hunks)src/main/java/org/springframework/samples/petclinic/course/CourseService.java(1 hunks)src/main/java/org/springframework/samples/petclinic/course/dto/CreateCourseRequest.java(1 hunks)src/main/java/org/springframework/samples/petclinic/vet/VetRepository.java(2 hunks)src/main/resources/db/h2/schema.sql(1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
`**/*.sql`: As a team convention, we do not specify FK constraints. Please avoid suggesting adding FK constraints in code reviews. This file is a TypeScript file, but the SQL state...
**/*.sql: As a team convention, we do not specify FK constraints. Please avoid suggesting adding FK constraints in code reviews. This file is a TypeScript file, but the SQL statements that modify the table structure are written as strings. For columns used as primary keys (PK) or foreign keys (FK), you must follow the convention of specifying the column type as "bigint". If a table column name is "id" or ends with "_id", verify that the column type is "bigint". If the SQL is written with a type other than "bigint", issue a warning. This convention is mandatory, so please raise the severity level of the warning. Add the following characters to the beginning of the warning message so that users can easily notice it: 🚨🚨🚨
src/main/resources/db/h2/schema.sql
🧬 Code Definitions (2)
src/main/java/org/springframework/samples/petclinic/course/dto/CreateCourseRequest.java (1)
src/main/java/org/springframework/samples/petclinic/course/Course.java (1)
Getter(17-31)
src/main/java/org/springframework/samples/petclinic/course/Course.java (1)
src/main/java/org/springframework/samples/petclinic/course/dto/CreateCourseRequest.java (1)
Getter(5-31)
🪛 YAMLlint (1.35.1)
.coderabbit.yaml
[error] 27-27: trailing spaces
(trailing-spaces)
[error] 33-33: trailing spaces
(trailing-spaces)
[error] 42-42: trailing spaces
(trailing-spaces)
[error] 51-51: trailing spaces
(trailing-spaces)
🔇 Additional comments (16)
src/main/java/org/springframework/samples/petclinic/vet/VetRepository.java (1)
41-46: 변경사항이 잘 구현되었습니다
findById메서드가 적절하게 추가되었고, 읽기 전용 트랜잭션으로 올바르게 설정되었습니다. 메서드 반환 타입이Optional<Vet>으로 설정되어 존재하지 않는 ID 처리도 적절합니다.build.gradle (2)
38-38: Lombok 의존성 추가가 적절합니다코드 간소화를 위한 Lombok 의존성이 적절하게 추가되었습니다.
63-63: Lombok 어노테이션 프로세서 추가가
적절합니다Lombok 어노테이션 프로세싱을 위한 의존성이 올바르게 추가되었습니다.
src/main/java/org/springframework/samples/petclinic/course/Course.java (2)
17-21: 엔티티 클래스에 적절한 애노테이션이 사용되었습니다.엔티티 클래스가 적절하게 정의되어 있으며, 테이블명도 명확하게 지정되어 있습니다.
27-29: 다대일 관계가 적절하게 설정되었습니다.
Course와Vet간의 다대일 관계가 적절하게 설정되어 있으며, 외래 키 컬럼 이름도 명확하게 지정되어 있습니다..coderabbit.yaml (8)
1-4: 기본 설정이 올바르게 구성되어 있습니다.한국어 설정과 얼리 액세스 및 프리 티어 활성화가 올바르게 구성되어 있습니다.
6-14: 리뷰 설정이 적절하게 구성되어 있습니다.리뷰 프로필, 요약 설정, 워크플로우 설정이 적절하게 구성되어 있습니다.
14-17: 경로 필터가 적절하게 구성되어 있습니다.swagger.json 및 README.md 파일을 제외하는 필터가 올바르게 설정되어 있습니다.
17-23: SQL 파일 지침이 명확하게 정의되어 있습니다.SQL 파일에 대한 리뷰 지침이 잘 정의되어 있습니다. PK 및 FK 컬럼 타입에 대한 규칙이 명확합니다.
24-56: 컨트롤러 API 엔드포인트 디자인 지침이 잘 정의되어 있습니다.RESTful API 디자인에 대한 Richardson Maturity Model과 URI 규칙, 디자인 원칙 등이 상세하게 정의되어 있습니다.
🧰 Tools
🪛 YAMLlint (1.35.1)
[error] 27-27: trailing spaces
(trailing-spaces)
[error] 33-33: trailing spaces
(trailing-spaces)
[error] 42-42: trailing spaces
(trailing-spaces)
[error] 51-51: trailing spaces
(trailing-spaces)
56-64: 자동 리뷰 설정이 적절하게 구성되어 있습니다.자동 리뷰 및 증분 리뷰가 활성화되어 있고, 다양한 설정이 올바르게 구성되어 있습니다.
64-108: 정적 분석 도구 설정이 잘 구성되어 있습니다.다양한 정적 분석 도구가 활성화되어 있으며, 필요한 설정이 적절하게 구성되어 있습니다.
109-120: 지식 베이스 설정이 적절하게 구성되어 있습니다.학습, 이슈, Jira, Linear 등 지식 베이스 관련 설정이 잘 구성되어 있습니다.
src/main/resources/db/h2/schema.sql (1)
73-73: 인덱스가 적절하게 생성되었습니다.
courses_vet_id인덱스가 적절하게 생성되어 있어vet_id를 기준으로 한 쿼리 성능이 향상될 것입니다.src/main/java/org/springframework/samples/petclinic/course/CourseController.java (1)
15-20: 컨트롤러 클래스 구조가 잘 설계되어 있습니다.컨트롤러 클래스에 적절한 애노테이션이 사용되었으며, 생성자 주입 방식을 통해 의존성이 주입되고 있습니다.
src/main/java/org/springframework/samples/petclinic/course/CourseService.java (1)
1-10: 잘 정의된 패키지 구조와 필요한 임포트만 포함된 것을 확인했습니다.패키지와 임포트 구조가 잘 정의되어 있습니다. 필요한 클래스만 임포트하고 있어 좋습니다.
| public interface CouresRepository extends JpaRepository<Course, Integer> { | ||
| } |
There was a problem hiding this comment.
인터페이스 이름에 오타가 있습니다
인터페이스 이름이 CouresRepository로 되어 있는데, 올바른 철자는 CourseRepository입니다. 's'가 빠져있습니다. 이 오타는 다른 클래스에서 이 레포지토리를 참조할 때 혼란을 줄 수 있습니다.
다음과 같이 수정하세요:
-public interface CouresRepository extends JpaRepository<Course, Integer> {
+public interface CourseRepository extends JpaRepository<Course, Integer> {📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| public interface CouresRepository extends JpaRepository<Course, Integer> { | |
| } | |
| public interface CourseRepository extends JpaRepository<Course, Integer> { | |
| } |
| CREATE TABLE courses ( | ||
| id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, | ||
| name VARCHAR(255), | ||
| description VARCHAR(1000), | ||
| difficulty VARCHAR(255), | ||
| vet_id INTEGER | ||
| ); |
There was a problem hiding this comment.
🚨🚨🚨 ID 및 FK 컬럼 타입이 팀 컨벤션을 따르지 않습니다.
id와 vet_id 컬럼 타입이 팀 컨벤션에 따라 'bigint'가 아닌 'INTEGER'로 정의되어 있습니다. 이는 필수 컨벤션이므로 반드시 수정해야 합니다.
CREATE TABLE courses (
- id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
+ id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
name VARCHAR(255),
description VARCHAR(1000),
difficulty VARCHAR(255),
- vet_id INTEGER
+ vet_id BIGINT
);📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| CREATE TABLE courses ( | |
| id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, | |
| name VARCHAR(255), | |
| description VARCHAR(1000), | |
| difficulty VARCHAR(255), | |
| vet_id INTEGER | |
| ); | |
| CREATE TABLE courses ( | |
| id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, | |
| name VARCHAR(255), | |
| description VARCHAR(1000), | |
| difficulty VARCHAR(255), | |
| vet_id BIGINT | |
| ); |
| public ResponseEntity<Integer> createCourse(@RequestBody CreateCourseRequest request) { | ||
| System.out.println(request); | ||
|
|
||
| Course course = courseService.createCourse(request.getName(), request.getDescription(), request.getDescription(), request.getVetId()); |
There was a problem hiding this comment.
createCourse 메서드 매개변수 오류가 있습니다.
courseService.createCourse 호출 시 세 번째 매개변수로 request.getDifficulty() 대신 request.getDescription()이 잘못 전달되고 있습니다.
- Course course = courseService.createCourse(request.getName(), request.getDescription(), request.getDescription(), request.getVetId());
+ Course course = courseService.createCourse(request.getName(), request.getDescription(), request.getDifficulty(), request.getVetId());📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| Course course = courseService.createCourse(request.getName(), request.getDescription(), request.getDescription(), request.getVetId()); | |
| Course course = courseService.createCourse(request.getName(), request.getDescription(), request.getDifficulty(), request.getVetId()); |
| public ResponseEntity<Integer> createCourse(@RequestBody CreateCourseRequest request) { | ||
| System.out.println(request); | ||
|
|
||
| Course course = courseService.createCourse(request.getName(), request.getDescription(), request.getDescription(), request.getVetId()); | ||
|
|
||
| return ResponseEntity.ok(course.getId()); | ||
| } |
There was a problem hiding this comment.
🛠️ Refactor suggestion
예외 처리가 없습니다.
현재 메서드에는 예외 처리 로직이 없습니다. 예를 들어, 존재하지 않는 VetId로 인한 오류나 DB 관련 예외를 처리하는 로직이 필요합니다.
@PostMapping()
public ResponseEntity<Integer> createCourse(@RequestBody CreateCourseRequest request) {
System.out.println(request);
- Course course = courseService.createCourse(request.getName(), request.getDescription(), request.getDescription(), request.getVetId());
-
- return ResponseEntity.ok(course.getId());
+ try {
+ Course course = courseService.createCourse(request.getName(), request.getDescription(), request.getDifficulty(), request.getVetId());
+ return ResponseEntity.ok(course.getId());
+ } catch (Exception e) {
+ log.error("Course creation failed: {}", e.getMessage(), e);
+ return ResponseEntity.badRequest().build();
+ }
}더 나은 해결책은 예외 유형에 따라 적절한 응답을 반환하는 글로벌 예외 핸들러를 구현하는 것입니다.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| public ResponseEntity<Integer> createCourse(@RequestBody CreateCourseRequest request) { | |
| System.out.println(request); | |
| Course course = courseService.createCourse(request.getName(), request.getDescription(), request.getDescription(), request.getVetId()); | |
| return ResponseEntity.ok(course.getId()); | |
| } | |
| @PostMapping() | |
| public ResponseEntity<Integer> createCourse(@RequestBody CreateCourseRequest request) { | |
| System.out.println(request); | |
| try { | |
| Course course = courseService.createCourse(request.getName(), request.getDescription(), request.getDifficulty(), request.getVetId()); | |
| return ResponseEntity.ok(course.getId()); | |
| } catch (Exception e) { | |
| log.error("Course creation failed: {}", e.getMessage(), e); | |
| return ResponseEntity.badRequest().build(); | |
| } | |
| } |
There was a problem hiding this comment.
API 에서 발생하는 에러를 Controller 마다 catch 절로 처리하지 않고, 공통 관심사로 묶어서 처리하고 싶습니다. 어떻게 하면 API 에서 발생하는 예외들을 공통 관심사의 로직으로 처리할 수 있을까요 ?
There was a problem hiding this comment.
@yakpoong Spring에서는 @RestControllerAdvice와 @ExceptionHandler를 사용하여 API 예외를 중앙에서 처리할 수 있습니다. 구현 방법을 보여드리겠습니다:
- 먼저 커스텀 예외 클래스를 만듭니다:
package org.springframework.samples.petclinic.exception;
import lombok.Getter;
@Getter
public class ResourceNotFoundException extends RuntimeException {
private final String resourceName;
private final String fieldName;
private final Object fieldValue;
public ResourceNotFoundException(String resourceName, String fieldName, Object fieldValue) {
super(String.format("%s not found with %s : '%s'", resourceName, fieldName, fieldValue));
this.resourceName = resourceName;
this.fieldName = fieldName;
this.fieldValue = fieldValue;
}
}- 다른 비즈니스 예외들도 추가합니다:
package org.springframework.samples.petclinic.exception;
public class BadRequestException extends RuntimeException {
public BadRequestException(String message) {
super(message);
}
}- 글로벌 예외 처리기를 구현합니다:
package org.springframework.samples.petclinic.exception;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.context.request.WebRequest;
import java.time.LocalDateTime;
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(ResourceNotFoundException.class)
public ResponseEntity<ErrorResponse> handleResourceNotFoundException(
ResourceNotFoundException ex, WebRequest request) {
log.error("Resource not found exception: {}", ex.getMessage());
ErrorResponse errorResponse = new ErrorResponse(
LocalDateTime.now(),
HttpStatus.NOT_FOUND.value(),
"Resource Not Found",
ex.getMessage(),
request.getDescription(false)
);
return new ResponseEntity<>(errorResponse, HttpStatus.NOT_FOUND);
}
@ExceptionHandler(BadRequestException.class)
public ResponseEntity<ErrorResponse> handleBadRequestException(
BadRequestException ex, WebRequest request) {
log.error("Bad request exception: {}", ex.getMessage());
ErrorResponse errorResponse = new ErrorResponse(
LocalDateTime.now(),
HttpStatus.BAD_REQUEST.value(),
"Bad Request",
ex.getMessage(),
request.getDescription(false)
);
return new ResponseEntity<>(errorResponse, HttpStatus.BAD_REQUEST);
}
@ExceptionHandler(Exception.class)
public ResponseEntity<ErrorResponse> handleGlobalException(
Exception ex, WebRequest request) {
log.error("Unhandled exception: {}", ex.getMessage(), ex);
ErrorResponse errorResponse = new ErrorResponse(
LocalDateTime.now(),
HttpStatus.INTERNAL_SERVER_ERROR.value(),
"Internal Server Error",
ex.getMessage(),
request.getDescription(false)
);
return new ResponseEntity<>(errorResponse, HttpStatus.INTERNAL_SERVER_ERROR);
}
}- 에러 응답 DTO를 정의합니다:
package org.springframework.samples.petclinic.exception;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import java.time.LocalDateTime;
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class ErrorResponse {
private LocalDateTime timestamp;
private int status;
private String error;
private String message;
private String path;
}- 이제 Controller에서는 try-catch 없이 예외를 발생시키면 됩니다:
@PostMapping()
public ResponseEntity<Integer> createCourse(@RequestBody CreateCourseRequest request) {
// 유효성 검사
if (request.getName() == null || request.getName().trim().isEmpty()) {
throw new BadRequestException("Course name cannot be empty");
}
// 서비스 호출
Course course = courseService.createCourse(
request.getName(),
request.getDescription(),
request.getDifficulty(),
request.getVetId()
);
return ResponseEntity.ok(course.getId());
}- CourseService도 수정합니다:
public Course createCourse(String name, String description, String difficulty, Integer vetId) {
// vetRepository.findById()가 빈 Optional을 반환할 경우 예외 발생
Vet vet = vetRepository.findById(vetId)
.orElseThrow(() -> new ResourceNotFoundException("Vet", "id", vetId));
Course course = new Course();
course.setName(name);
course.setDescription(description);
course.setDifficulty(difficulty);
course.setInstructor(vet);
return courseRepository.save(course);
}이 방식을 사용하면 모든 컨트롤러에서 공통적으로 발생하는 예외 처리 로직을 중앙화할 수 있습니다.
There was a problem hiding this comment.
@coderabbitai 예외 처리 로직 중앙화처리는 별도의 Issue를 통해 해결하고 싶습니다. 이슈를 생성해 주세요.
There was a problem hiding this comment.
🧩 Analysis chain
@yakpoong 예외 처리 로직 중앙화에 대한 Issue를 생성해드렸습니다. Issue에는 다음 내용이 포함되어 있습니다:
- 현재 문제점: 컨트롤러마다 개별적으로 예외 처리
- 구현 방안:
@RestControllerAdvice및@ExceptionHandler활용 - 기대 효과: 코드 중복 제거, 일관된 오류 응답 등
- PR 코멘트 참조 링크
해당 Issue는 yakpoong님께 할당되었습니다. 추가적인 요구사항이 있으시면 말씀해주세요.
🏁 Script executed:
#!/bin/bash
# Create a GitHub issue for centralized exception handling
gh issue create \
--title "예외 처리 로직 중앙화 구현" \
--body "## 개요
현재 API에서 발생하는 예외들이 각 Controller에서 개별적으로 처리되고 있습니다. 이를 공통 관심사로 분리하여 중앙화된 예외 처리 메커니즘을 구현해야 합니다.
## 구현 방안
- `@RestControllerAdvice`와 `@ExceptionHandler`를 사용한 글로벌 예외 처리기 구현
- 비즈니스 로직에 맞는 커스텀 예외 클래스 정의 (예: ResourceNotFoundException, BadRequestException 등)
- 표준화된 에러 응답 DTO 정의
- Controller에서 try-catch 블록 제거 및 명시적 예외 발생 패턴 적용
## 기대 효과
- 코드 중복 제거
- 일관된 에러 응답 포맷
- Controller 코드의 가독성 향상
- 예외 처리 로직 변경 시 유지보수 용이성 증가
## 참조
- PR #1 코멘트: https://github.com/inflearn/spring-petclinic/pull/1#discussion_r2009014022" \
--assignee "yakpoong" \
--repo "inflearn/spring-petclinic"
Length of output: 893
@yakpoong 요청하신 대로 예외 처리 로직 중앙화에 대한 이슈를 성공적으로 생성했습니다! 이슈는 다음 링크에서 확인하실 수 있습니다: #2
이슈는 yakpoong님께 할당되었으며, PR #1의 코멘트를 참조 링크로 포함했습니다.
| @Service | ||
| @Transactional(readOnly = true) | ||
| @RequiredArgsConstructor | ||
| public class CourseService { | ||
| private final CouresRepository courseRepository; | ||
| private final VetRepository vetRepository; |
There was a problem hiding this comment.
Repository 이름에 오타가 있습니다.
코드에 CouresRepository라고 되어 있는데, 이는 CourseRepository의 오타로 보입니다. 이로 인해 애플리케이션 시작 시 Bean 주입 오류가 발생할 수 있습니다.
- private final CouresRepository courseRepository;
+ private final CourseRepository courseRepository;📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| @Service | |
| @Transactional(readOnly = true) | |
| @RequiredArgsConstructor | |
| public class CourseService { | |
| private final CouresRepository courseRepository; | |
| private final VetRepository vetRepository; | |
| @Service | |
| @Transactional(readOnly = true) | |
| @RequiredArgsConstructor | |
| public class CourseService { | |
| private final CourseRepository courseRepository; | |
| private final VetRepository vetRepository; |
| @Transactional() | ||
| public Course createCourse(String name, String description, String difficulty, Integer vetId) { | ||
| Collection<Vet> all = vetRepository.findAll(); | ||
|
|
||
| all.forEach(course -> { | ||
| System.out.println(course.getId()); | ||
| }); | ||
|
|
There was a problem hiding this comment.
🛠️ Refactor suggestion
디버깅 코드를 제거하세요.
프로덕션 코드에 System.out.println과 같은 디버깅 코드가 남아있습니다. 또한, 모든 Vet을 조회하는 코드는 실제 기능에 필요하지 않아 보입니다.
람다 표현식에서 파라미터 이름이 course로 되어 있지만, 실제로는 Vet 객체입니다. 이는 코드 가독성을 저하시킵니다.
- @Transactional()
+ @Transactional
public Course createCourse(String name, String description, String difficulty, Integer vetId) {
- Collection<Vet> all = vetRepository.findAll();
-
- all.forEach(course -> {
- System.out.println(course.getId());
- });
-
Vet instructor = vetRepository.findById(vetId)
.orElseThrow(() -> new RuntimeException("Vet not found"));📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| @Transactional() | |
| public Course createCourse(String name, String description, String difficulty, Integer vetId) { | |
| Collection<Vet> all = vetRepository.findAll(); | |
| all.forEach(course -> { | |
| System.out.println(course.getId()); | |
| }); | |
| @Transactional | |
| public Course createCourse(String name, String description, String difficulty, Integer vetId) { | |
| Vet instructor = vetRepository.findById(vetId) | |
| .orElseThrow(() -> new RuntimeException("Vet not found")); |
요약
반려동물용 강의 관리 기능을 추가하였습니다.
주요 코드 구현사항
POST /courses) 를 구현하였습니다.Summary by CodeRabbit