[Spring MVC] DTO ์ค์ต
Categories: Spring
๐ ๊ฐ์ธ์ ์ธ ๊ณต๊ฐ์ผ๋ก ๊ณต๋ถ๋ฅผ ๊ธฐ๋กํ๊ณ ๋ณต์ตํ๊ธฐ ์ํด ์ฌ์ฉํ๋ ๋ธ๋ก๊ทธ์
๋๋ค.
์ ํํ์ง ์์ ์ ๋ณด๊ฐ ์์ ์ ์์ผ๋ ์ฐธ๊ณ ๋ฐ๋๋๋ค :๐ธ
[ํ๋ฆฐ ๋ด์ฉ์ ๋๊ธ๋ก ๋จ๊ฒจ์ฃผ์๋ฉด ๋ณต๋ฐ์ผ์ค๊ฑฐ์์]
[Spring MVC] DTO ์ค์ต
์ฒ์์์ฑ - ์ค๋ฅ๋ฐ์
-
CoffeePostDto
1 2 3 4 5 6 7 8 9 10 11 12
public class CoffeePostDto { @NotBlank private String korName; @NotBlank @Pattern(regexp ="^[A-Za-z]+(\\s[A-Za-z]+)*$", message = "์๋ฌธ๋ง ๊ฐ๋ฅํฉ๋๋ค.") private String engName; @Min(100) @Max(50000) private int price; }
-
CoffeePatchDto
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
package com.springboot.coffee; import com.springboot.member.NotSpace; import javax.validation.constraints.*; @Getter @Setter public class CoffeePatchDto { @Positive private long coffeeId; @NotBlank private String korName; @NotSpace @Pattern(regexp ="^[A-Za-z]+(\\s[A-Za-z]+)*$", message = "์๋ฌธ๋ง ๊ฐ๋ฅํฉ๋๋ค.") @NotBlank private String engName; @Min(100) @Max(50000) private int price; }
-
Coffeecontroller ํด๋์ค์ patchCoffee Method
1 2 3 4 5 6 7 8
@PatchMapping("/{coffee-id}") public ResponseEntity<CoffeePatchDto> patchCoffee(@PathVariable("coffee-id") @Positive long coffeeId, @RequestBody @Valid CoffeePatchDto coffeePatchDto) { coffeePatchDto.setCoffeeId(coffeeId); return new ResponseEntity<>(coffeePatchDto, HttpStatus.OK); }
-
๋ฌธ์
- ์ค๋ฅ๋ฐ์ โ localhost:8080/v1/coffees/1 ๋ก patchํ๋ฉด id๋ 0์ด์๋ง ์ฌ ์ ์๋ค๊ณ ์๋ฌ๋จ
- MethodArgumentNotValidException
1 2 3 4 5 6 7 8 9 10
Validation failed for argument [1] in public org.springframework.http.ResponseEntity com.springboot.coffee.CoffeeController .patchCoffee(long,com.springboot.coffee.CoffeePatchDto): [Field error in object 'coffeePatchDto' on field 'coffeeId' : rejected value [0]; codes [Positive.coffeePatchDto.coffeeId, Positive.coffeeId,Positive.long,Positive]; arguments [org.springframework.context.support .DefaultMessageSourceResolvable: codes [coffeePatchDto.coffeeId,coffeeId]; arguments []; default message [coffeeId]]; default message [0๋ณด๋ค ์ปค์ผ ํฉ๋๋ค]] ]
- CoffeePatchDto์์ @Positive private long coffeeId; - ์ด๊ฒ ๋ฌธ์ ๊ฐ ๋จ.
- ์๋๋ฉด, CoffeePatchDto.coffeeId์ ์ด๊ธฐํ ๊ฐ์ด 0์ด ๋๊ณ @Positive ์ ์ ํจ์ฑ ๊ฒ์ฌ๊ฐ ์คํจ๋์ด ๋ฉ์๋๊ฐ ํธ์ถ๋๊ธฐ ์ ์ ์ ํจ์ฑ ๊ฒ์ฌ์์ ์ด๋ฏธ ์คํจํ๊ธฐ ๋๋ฌธ์ ์์ธ๊ฐ ๋ฐ์ โ CoffeePatchDto์ coffeeId์ @Positive ์ด๋ ธํ ์ด์ ์ ์ ๊ฑฐํ๋ฉด coffeeId์ ๋ํ ์ ํจ์ฑ ๊ฒ์ฌ๊ฐ ์ํ๋์ง ์์ผ๋ฏ๋ก, ์ด๊ธฐํ๋์ง ์์ ์ํ์์๋ ์์ธ๊ฐ ๋ฐ์ํ์ง ์์ต๋๋ค. ๋ฐ๋ผ์, coffeePatchDto.setCoffeeId(coffeeId); ๋ฉ์๋ ํธ์ถ ํ์ ์ ๋๋ก ๋์
- @PathVariable(โcoffee-idโ) @Positive long coffeeId, ์ฌ๊ธฐ๋ง positive ์์ฑํด์ฃผ๋ฉด ๋จ.
์์ํ์ ์ ๊ธฐ๋ณธ์ด๊ธฐํ ๊ฐ์ด ์์.
Referece type์ ์ด๊ธฐํ ํด์ฃผ์ด์ผ ํจ.
์ถ๊ฐ. coffeeId ๊ฒ์ฆ
@PathVariable์ ๊ฒ์ฆ์ด ์ ๋๋ก ๋๋ ค๋ฉด class์์ @Validated ํ์
@Valid๋ ๊ธฐ๋ณธ์ ์ผ๋กย ์ปจํธ๋กค๋ฌ์์๋ง ๋์ํ๋ฉฐ ๊ธฐ๋ณธ์ ์ผ๋ก ๋ค๋ฅธ ๊ณ์ธต์์๋ ๊ฒ์ฆ์ด ์๋จ, ๋ค๋ฅธ ๊ณ์ธต์์ ํ๋ผ๋ฏธํฐ๋ฅผ ๊ฒ์ฆํ๊ธฐ ์ํด์๋ @Validated์ ๊ฒฐํฉ๋์ด์ผ ํจ.
[]
@Valid ๋์์๋ฆฌ
๋ชจ๋ ์์ฒญ์ ํ๋ก ํธ ์ปจํธ๋กค๋ฌ์ธ ๋์คํจ์ฒ ์๋ธ๋ฆฟ์ ํตํด ์ปจํธ๋กค๋ฌ๋ก ์ ๋ฌ๋๋ค. ์ ๋ฌ ๊ณผ์ ์์๋ ์ปจํธ๋กค๋ฌ ๋ฉ์๋์ ๊ฐ์ฒด๋ฅผ ๋ง๋ค์ด์ฃผ๋ ArgumentResolver๊ฐ ๋์ํ๋๋ฐ, @Valid ์ญ์ ArgumentResolver์ ์ํด ์ฒ๋ฆฌ๊ฐ ๋๋ค.๋ํ์ ์ผ๋ก @RequestBody๋ Json ๋ฉ์ธ์ง๋ฅผ ๊ฐ์ฒด๋ก ๋ณํํด์ฃผ๋ ์์ ์ด ArgumentResolver์ ๊ตฌํ์ฒด์ธRequestResponseBodyMethodProcessor๊ฐ ์ฒ๋ฆฌํ๋ฉฐ, ์ด ๋ด๋ถ์์ @Valid๋ก ์์ํ๋ ์ด๋ ธํ ์ด์ ์ด ์์ ๊ฒฝ์ฐ์ ์ ํจ์ฑ ๊ฒ์ฌ๋ฅผ ์งํํจ.
@Valid๋ ๊ธฐ๋ณธ์ ์ผ๋กย ์ปจํธ๋กค๋ฌ์์๋ง ๋์ํ๋ฉฐ ๊ธฐ๋ณธ์ ์ผ๋ก ๋ค๋ฅธ ๊ณ์ธต์์๋ ๊ฒ์ฆ์ด ์๋จ, ๋ค๋ฅธ ๊ณ์ธต์์ ํ๋ผ๋ฏธํฐ๋ฅผ ๊ฒ์ฆํ๊ธฐ ์ํด์๋ @Validated์ ๊ฒฐํฉ๋์ด์ผ ํจ.
์ถ์ฒ : https://mangkyu.tistory.com/174
@Validated ๋์์๋ฆฌ
ํน์ ArgumnetResolver์ ์ํด ์ ํจ์ฑ ๊ฒ์ฌ๊ฐ ์งํ๋์๋ @Valid์ ๋ฌ๋ฆฌ, @Validated๋ AOP ๊ธฐ๋ฐ์ผ๋ก ๋ฉ์๋ ์์ฒญ์ ์ธํฐ์ ํฐํ์ฌ ์ฒ๋ฆฌ๋๋ค. @Validated๋ฅผย ํด๋์ค ๋ ๋ฒจ์ ์ ์ธํ๋ฉด ํด๋น ํด๋์ค์ ์ ํจ์ฑ ๊ฒ์ฆ์ ์ํ AOP์ ์ด๋๋ฐ์ด์ค ๋๋ ์ธํฐ์ ํฐ(MethodValidationInterceptor)๊ฐ ๋ฑ๋ก๋๋ค. ๊ทธ๋ฆฌ๊ณ ํด๋น ํด๋์ค์ ๋ฉ์๋๋ค์ด ํธ์ถ๋ ๋ AOP์ ํฌ์ธํธ ์ปท์ผ๋ก์จ ์์ฒญ์ ๊ฐ๋ก์ฑ์ ์ ํจ์ฑ ๊ฒ์ฆ์ ์งํํ๋ค.์ด๋ฌํ ์ด์ ๋ก @Validated๋ฅผ ์ฌ์ฉํ๋ฉด ์ปจํธ๋กค๋ฌ, ์๋น์ค, ๋ ํฌ์งํ ๋ฆฌ ๋ฑ ๊ณ์ธต์ ๋ฌด๊ดํ๊ฒ ์คํ๋ง ๋น์ด๋ผ๋ฉด ์ ํจ์ฑ ๊ฒ์ฆ์ ์งํํ ์ ์๋ค. ๋์ ํด๋์ค์๋ ์ ํจ์ฑ ๊ฒ์ฆ AOP๊ฐ ์ ์ฉ๋๋๋กย @Validated๋ฅผ, ๊ฒ์ฆ์ ์งํํ ๋ฉ์๋์๋ @Valid๋ฅผ ์ ์ธํด์ฃผ์ด์ผ ํ๋ค.์ด๋ฌํ ์ด์ ๋ก @Valid์ ์ํ ์์ธ๋ MethodArgumentNotValidException์ด๋ฉฐ, @Validated์ ์ํ ์์ธ๋ย ConstraintViolationException
- ์ค๋ฅ๋ฐ์ โ localhost:8080/v1/coffees/1 ๋ก patchํ๋ฉด id๋ 0์ด์๋ง ์ฌ ์ ์๋ค๊ณ ์๋ฌ๋จ
๋ค์ ์์ฑ- ์ค๋ฅ ๊ฐ์ , but Body์ attribute๊ฐ null์ผ ๋๋ ์ ํจ์ฑ ๊ฒ์ฆํจ
1
2
3
4
5
6
7
8
@PatchMapping("/{coffee-id}")
public ResponseEntity patchCoffee(@PathVariable("coffee-id") @Positive long coffeeId,
@RequestBody @Valid CoffeePatchDto coffeePatchDto) {
coffeePatchDto.setCoffeeId(coffeeId);
return new ResponseEntity<>(coffeePatchDto, HttpStatus.OK);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
package com.springboot.coffee;
import com.springboot.member.NotSpace;
import javax.validation.constraints.*;
public class CoffeePatchDto {
private long coffeeId;
@NotSpace
private String korName;
@Pattern(regexp ="^[A-Za-z]+(\\s[A-Za-z]+)*$", message = "์๋ฌธ๋ง ๊ฐ๋ฅํฉ๋๋ค.")
@NotBlank
private String engName;
@Min(100)
@Max(50000)
private integer price;
public String getKorName() {
return korName;
}
public void setKorName(String korName) {
this.korName = korName;
}
public String getEngName() {
return engName;
}
public void setEngName(String engName) {
this.engName = engName;
}
public int getPrice() {
return price;
}
public void setPrice(int price) {
this.price = price;
}
public long getCoffeeId() {
return coffeeId;
}
public void setCoffeeId(long coffeeId) {
this.coffeeId = coffeeId;
}
}
Price์ null๊ฐ์ด ์๋ค์ด์์ @NotBlank ์๋ โ UnexpectedTypeException ๋ฐ์
์๊ณ ๋ณด๋ @NotBlank๋ null ๋ชป ๋ค์ด์ค๊ณ ์คํ๋ ค @min, @max๋ null์ด ๋ค์ด์ฌ ์ ์์.
๊ทธ๋์ int๋ ์ด๊ธฐํ ๊ฐ์ด 0์ด์ฌ์ ๊ทธ๋ ๋ค๋ ๊ฑธ ์๊ฒ ๋์๊ณ ,
Integer๋ก ๋ฐ๊ฟ๋ ์๋ฌ ๋์ ์ ๊ทธ๋ฐ๊ฐ ํ๋๋, ์์ ํ๋์๋ง ๋ณ๊ฒฝํ๊ณ
getter์ setter๋ฅผ ๋ฏธ๋ฆฌ ๋ง๋ค์๋๋ ์ฌ๊ธฐ๋ int๊ฐ์ผ๋ก ์ ์ง๋๊ณ ์์์โฆ.!
๋ชจ๋ ๋ณ๊ฒฝํ๋ null๊ฐ์ด ์ ๋ค์ด์ด.
์์ฑ
[CoffeePatchDto]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import lombok.Getter;
import lombok.Setter;
import com.springboot.member.NotSpace;
import javax.validation.constraints.*;
@Getter @Setter
public class CoffeePatchDto {
private long coffeeId;
@NotSpace //customAnnotation
private String korName;
@Pattern(regexp ="^[A-Za-z]+(\\s[A-Za-z]+)*$", message = "์๋ฌธ๋ง ๊ฐ๋ฅํฉ๋๋ค.")
private String engName;
@Min(100)
@Max(50000)
private Integer price;
}
[CoffeePostDto]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import lombok.Getter;
import lombok.Setter;
import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Pattern;
@Getter
@Setter
public class CoffeePostDto {
@NotBlank
private String korName;
@NotBlank
@Pattern(regexp ="^[A-Za-z]+(\\s[A-Za-z]+)*$", message = "์๋ฌธ๋ง ๊ฐ๋ฅํฉ๋๋ค.")
private String engName;
@Min(100)
@Max(50000)
private int price;
}
[CoffeeController]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import javax.validation.constraints.Positive;
@Validated
@RestController
@RequestMapping("/v1/coffees")
public class CoffeeController {
// 1. DTO ํด๋์ค ๋ฐ ์ ํจ์ฑ ๊ฒ์ฆ์ ์ ์ฉํ์ธ์.
@PostMapping
public ResponseEntity postCoffee(@RequestBody @Valid CoffeePostDto coffeePostDto) {
return new ResponseEntity<>(coffeePostDto, HttpStatus.CREATED);
}
// 2. DTO ํด๋์ค ๋ฐ ์ ํจ์ฑ ๊ฒ์ฆ์ ์ ์ฉํ์ธ์.
@PatchMapping("/{coffee-id}")
public ResponseEntity patchCoffee(@PathVariable("coffee-id") @Positive long coffeeId,
@RequestBody @Valid CoffeePatchDto coffeePatchDto) {
coffeePatchDto.setCoffeeId(coffeeId);
return new ResponseEntity<>(coffeePatchDto, HttpStatus.OK);
}
Comment
์ค๋ฌด์๋ ์ด๋ ๊ฒ null์ ๋ฐ๋ ์ถ์ด์ ์๋ฌธ์ด ๊ณ์ ๋ค์๊ณ DB๋ฅผ ์์ง ์ฌ์ฉํ์ง ์์์ DB์๋ ๋ณ๊ฒฝํ ๊ฐ๋ง ๋ค์ด๊ฐ๋ ๊ฑด์ง ๊ถ๊ธํ๋๋ฐ
๊ฐ๊ฒฉ์ ๋ฐ๊พธ๊ณ ์ถ์ด์ PATCH์ Body ๊ฐ์ผ๋ก {โpriceโ:3000} ๋ง ๋ฃ๊ฒ ๋๋ฉด(korName, engName
๋ง์ฝ DB์ null์ ๋ณด๋ด๋ฉด null์ด ๋ค์ด๊ฐ๊ฒ ๋๋ค๊ณ ํจ.
์ด๊ฑธ ๋ฐฉ์งํ๊ธฐ ์ํด Optional class ๋๋ ๋๋ค์์ ์ฌ์ฉํด์ ๋์ฒดํ ์ ์๋ค๊ณ ํจ. (์ค๋ฌด์์ ๋๋ถ๋ถ ๋๋ค์ ์ฌ์ฉ)
๋ฌดํผ int๋ integer๋๋ฌธ์ ์ ๋ง์ด ๋จน์๋๋ฐ ๊ฐ์ฌ๋์ NotZero๋ผ๋ annotation์ custom์ผ๋ก ๋ง๋ค์ด ๋ฒ๋ฆผ. ๐ฎ ,,, ๊ทธ๋ฆฌ๊ณ custom Annotation ์ค๋ฌด์์๋ ์์ฃผ ์ฌ์ฉ๋๋๋ฐ ํ๋ก์ ํธ ์ ์ ํ๋ฒ ๋ง๋ค์ด๋ณด๋ ๊ฒ๋ ์ข๋ค๊ณ ํ์ฌ, !
๐ ๊ณต์ง
Leave a comment