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
199 changes: 199 additions & 0 deletions week08/keyword/keyword_08.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
- java의 Exception 종류들
1. **Checked Exception**

컴파일러가 반드시 처리(try-catch 또는 throws)하라고 강제하는 예외 → 주로 외부 환경(IO, 네트워크, DB) 때문에 발생.

- 특징
- 프로그램 실행 전에 컴파일 단계에서 확인됨
- "예상 가능한 문제"이기 때문에 복구 가능한 경우가 많음
- 반드시 예외 처리를 해야 컴파일 가능
- 종류

| 예외 | 설명 |
| --- | --- |
| IOException | 파일/네트워크 입출력 문제 |
| FileNotFoundException | 지정 파일이 없음 |
| SQLException | DB 쿼리 실행/연결 문제 |
| ClassNotFoundException | 클래스를 찾을 수 없음 |
| InterruptedException | 스레드가 인터럽트됨 |
| ParseException | 날짜/숫자 파싱 실패 |
- 예시

```java
try {
FileInputStream fis = new FileInputStream("a.txt");
} catch (IOException e) {
e.printStackTrace();
}
```

**2. Unchecked Exception (RuntimeException)**

컴파일 시점에 검사하지 않는 예외 → 런타임에 갑자기 터짐 → 대부분 개발자의 실수로 인해 발생하는 논리 오류.

- 특징
- 예외 처리 강제 없음
- Null, 인덱스 문제, 잘못된 파라미터 등 버그성 오류
- 복구 불가능하거나 굳이 강제할 필요 없음
- 종류

| 예외 | 설명 |
| --- | --- |
| **NullPointerException** | null 접근 |
| **ArrayIndexOutOfBoundsException** | 배열 인덱스 초과 |
| **IllegalArgumentException** | 잘못된 매개변수 전달 |
| **IllegalStateException** | 객체 상태가 잘못됨 |
| **ArithmeticException** | 0으로 나누기 |
| **ClassCastException** | 잘못된 형변환 |
| **NumberFormatException** | 숫자로 변환 실패 (Integer.parseInt 등) |
- 예시
```java
String s = null;
System.out.println(s.length()); // NPE
```

3. 어떤 상황에 어떤 예외를 쓰나?
- Checked Exception 쓰는 경우
- 외부 요인 때문에 실패할 가능성이 있고

개발자가 그 실패를 "어떤 방식으로든 대응"해야 하는 경우

→ IO, DB, 파일, 네트워크

- Unchecked Exception 쓰는 경우
- 개발자가 잘못된 값을 넘기거나
- 상태를 잘못 사용해서 발생하는 논리 오류
- 잘못된 코드 흐름에 대해 강제적으로 try-catch 시킬 필요 없음

---

- @Valid

## 1. 개념

`@Valid`는 객체에 선언된 Bean Validation 규칙을 실행하라는 트리거 역할을 하는 어노테이션이다.

DTO의 각 필드에 붙어 있는 `@NotBlank`, `@Size`, `@Email` 등의 검증 규칙을 기반으로 값의 유효성을 검사한다.

## 2. 동작 조건

스프링 부트에서는 다음 의존성을 포함하면 자동으로 Bean Validation이 동작한다.

```
implementation 'org.springframework.boot:spring-boot-starter-validation'
```

이 의존성 안에는 Bean Validation API와 Hibernate Validator가 포함되어 있어 별도 설정 없이 `@Valid`가 동작한다.

## 3. @Valid의 사용 위치

### 3-1. 컨트롤러 파라미터 DTO

요청 본문(`@RequestBody`)이나 요청 파라미터(`@ModelAttribute`)로 DTO를 받을 때 `@Valid`를 함께 사용한다.

```java
@PostMapping("/members")
public ResponseEntity<?> createMember(@Valid @RequestBody MemberRequest request) {
...
}
```

- JSON 바디 바인딩 후 검증 실패 시 `MethodArgumentNotValidException`이 발생한다.
- `@ModelAttribute` 기반 요청이면 `BindException`이 발생한다.

---

### 3-2. 중첩 객체(Nested DTO) 검증

DTO 내부에 또 다른 DTO가 있을 경우, 내부 DTO 필드에 `@Valid`를 붙여야 내부 필드의 검증도 함께 수행된다.

```java
public class OrderRequest {

@NotNull
private Long memberId;

@Valid
private Address address;
}
```

---

## 4. 검증 실패 시 처리 흐름

### 4-1. BindingResult 없이 @Valid 사용

컨트롤러 파라미터에 `BindingResult`를 받지 않을 경우, 검증 실패 시 스프링이 곧바로 예외를 던진다.

- `@RequestBody` → `MethodArgumentNotValidException`
- `@ModelAttribute` → `BindException`

이 예외는 보통 `@RestControllerAdvice`에서 공통으로 처리한다.

---

### 4-2. BindingResult와 함께 사용

검증 대상 파라미터 **바로 뒤에** `BindingResult`를 선언하면 예외가 발생하지 않고 컨트롤러 내부에서 검증 결과를 직접 확인할 수 있다.

```java
@PostMapping("/members")
public String create(@Valid MemberForm form, BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
return "members/new-form";
}
...
}
```

`BindingResult`는 필드 에러, 객체 에러를 포함하며 `hasErrors()`로 간단하게 검증 실패 여부를 알 수 있다.

---

## 5. 에러 메시지 처리

검증 어노테이션에 message 값을 직접 작성하거나, 메시지 파일(`messages.properties`)을 통해 관리할 수 있다.

```java
@NotBlank(message = "{member.name.notBlank}")
private String name;
```

```
member.name.notBlank=이름은 필수 입력값이다.
```

Spring의 MessageSource가 코드 → 메시지로 변환해준다.

---

## 6. @Valid vs @Validated

### @Valid

- Bean Validation 표준 어노테이션이다.
- 객체 검증만 수행하며 **검증 그룹(group)** 기능은 없다.

### @Validated

- 스프링이 제공하는 확장 어노테이션이다.
- 그룹 검증을 지원한다.
- 특정 상황(Create, Update 등)에 따라 다른 검증 규칙을 적용할 때 사용한다.

```java
@PostMapping("/members")
public ResponseEntity<?> create(@Validated(Create.class) @RequestBody MemberRequest req) {
...
}
```

---

## 7. 요약

- `@Valid`는 DTO의 유효성 검증을 트리거하는 어노테이션이다.
- 검증 규칙은 Bean Validation 어노테이션(@NotBlank 등)을 통해 정의한다.
- 검증 실패 시 예외가 발생하거나, BindingResult로 오류를 직접 처리할 수 있다.
- 중첩 객체에는 필드에도 `@Valid`를 붙여야 내부 DTO까지 검증이 수행된다.
- 그룹 검증이 필요하면 `@Validated`를 사용한다.
122 changes: 122 additions & 0 deletions week08/mission/mission_08.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
1. 깃허브 링크: [Feat/Chapter8](https://github.com/choehyeonjin/UMC-9th-spring-study/tree/Feat/Chapter8)
2. 회원가입 API
- food ENUM을 와이어프레임에 맞게 수정하였다.
- 이번 구현에 사용하지 않는 member 컬럼 email, phone, social_uid, social_type을 NULL로 수정하였다.

```sql
ALTER TABLE food
MODIFY COLUMN name ENUM(
'한식',
'일식',
'중식',
'양식',
'치킨',
'분식',
'고기구이',
'도시락',
'야식',
'패스트푸드',
'디저트',
'아시안푸드'
)
CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci;

INSERT INTO food (name) VALUES
('한식'),
('일식'),
('중식'),
('양식'),
('치킨'),
('분식'),
('고기구이'),
('도시락'),
('야식'),
('패스트푸드'),
('디저트'),
('아시안푸드');

ALTER TABLE member
MODIFY COLUMN email VARCHAR(255) NULL,
MODIFY COLUMN phone VARCHAR(255) NULL,
MODIFY COLUMN social_type ENUM('KAKAO','NAVER','GOOGLE','APPLE') NULL,
MODIFY COLUMN social_uid VARCHAR(255) NULL;
```

- Swagger 결과

![image.png](./mission_08_(1).png)


2. **가게에 리뷰 추가하기 API**

- 3주차에 작성한 마이 페이지 리뷰 작성한 API 명세서를 기반으로 구현하였다.
- 요구사항 정의
- 별점 입력 페이지
- 가게 ID, 별점(1~5, 0.5 단위)
- 리뷰 작성 페이지
- 가게 ID, 별점, 리뷰 내용
- 리뷰 사진 업로드
- 사진 URL 최대 3개까지 저장 (review_photo 테이블)
- 로그인 없음
- member_id는 하드코딩된 회원으로 처리 (jwt 토큰 사용 X)
- 6주차에 생성한 DB 데이터가 충돌이 나서 새로운 시드를 넣었다.

```sql
INSERT INTO region (id, name) VALUES
(1, '서울');

INSERT INTO store (id, region_id, name, address, identify_number) VALUES
(1, 1, '반이학생마라탕마라반', '서울 서교동', '123-45-67890'),
(2, 1, '장사부마라탕', '서울 개봉동', '999-999');
```

- Swagger 결과

![image.png](./mission_08_(2).png)


3. **가게의 미션을 도전 중인 미션에 추가(미션 도전하기) API**

- 요구사항 정의
- 유저가 특정 미션에 “미션 도전!” 버튼을 누르면 member_mission 테이블에 추가
- 초기 상태는 IN_PROGRESS
- 로그인 기능 없음 → member_id 하드코딩
- API 명세
- **Endpoint**

`POST /missions/challenge`

- **Request Body**

```json
{
"mission_id": 10
}
```

- **Response (201 Created)**

```json
{
"isSuccess": true,
"code": "COMMON201_1",
"message": "리소스가 성공적으로 생성되었습니다.",
"result": {
"memberMissionId": 1,
"missionId": 1,
"status": "IN_PROGRESS",
"expiresAt": "2025-11-30T00:00:00",
"createdAt": "2025-11-23T20:06:38.4427257"
}
}
```

- 와이어프레임 바탕으로 mission 테이블에 시드 데이터를 넣었다.

```sql
INSERT INTO mission (id, store_id, mission_condition, deadline, point) VALUES
(1, 1, '10,000원 이상의 식사 시', DATE_ADD(CURDATE(), INTERVAL 7 DAY), 500);
```

- Swagger 결과
![image.png](./mission_08_(3).png)
Binary file added week08/mission/mission_08_(1).png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added week08/mission/mission_08_(2).png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added week08/mission/mission_08_(3).png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.