SPRING/INFLEARN

[๊ฐ•์˜] ์Šคํ”„๋ง MVC 2ํŽธ - ๋ฐฑ์—”๋“œ ์›น ๊ฐœ๋ฐœ ํ•ต์‹ฌ ๊ธฐ์ˆ  4

ozllzL 2023. 3. 20. 19:49

 

https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-mvc-2

 

์Šคํ”„๋ง MVC 2ํŽธ - ๋ฐฑ์—”๋“œ ์›น ๊ฐœ๋ฐœ ํ™œ์šฉ ๊ธฐ์ˆ  - ์ธํ”„๋Ÿฐ | ๊ฐ•์˜

์›น ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๊ฐœ๋ฐœ์— ํ•„์š”ํ•œ ๋ชจ๋“  ์›น ๊ธฐ์ˆ ์„ ๊ธฐ์ดˆ๋ถ€ํ„ฐ ์ดํ•ดํ•˜๊ณ , ์™„์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. MVC 2ํŽธ์—์„œ๋Š” MVC 1ํŽธ์˜ ํ•ต์‹ฌ ์›๋ฆฌ์™€ ๊ตฌ์กฐ ์œ„์— ์‹ค๋ฌด ์›น ๊ฐœ๋ฐœ์— ํ•„์š”ํ•œ ๋ชจ๋“  ํ™œ์šฉ ๊ธฐ์ˆ ๋“ค์„ ํ•™์Šตํ•  ์ˆ˜ ์žˆ

www.inflearn.com

4. ๊ฒ€์ฆ1 - Validation

โ€ป ๊ฒ€์ฆ

  • ์ปจํŠธ๋กค๋Ÿฌ์˜ ์ค‘์š”ํ•œ ์—ญํ•  ์ค‘ ํ•˜๋‚˜๋Š” HTTP ์š”์ฒญ์ด ์ •์ƒ์ธ์ง€ ๊ฒ€์ฆํ•˜๋Š” ๊ฒƒ์ด๋‹ค.
  • ํด๋ผ์ด์–ธํŠธ ๊ฒ€์ฆ์€ ์กฐ์ž‘์ด ๊ฐ€๋Šฅํ•˜์—ฌ ๋ณด์•ˆ์— ์ทจ์•ฝํ•˜๋ฉฐ, ์„œ๋ฒ„๋งŒ์œผ๋กœ ๊ฒ€์ฆํ•˜๋ฉด ์ฆ‰๊ฐ์ ์ธ ๊ณ ๊ฐ ์‚ฌ์šฉ์„ฑ์ด ๋ถ€์กฑํ•ด์ง€๋ฏ€๋กœ ์ด ๋‘˜์„ ์ ์ ˆํžˆ ์„ž์–ด์„œ ์‚ฌ์šฉํ•œ๋‹ค. ์ตœ์ข…์ ์ธ ์„œ๋ฒ„ ๊ฒ€์ฆ์€ ํ•„์ˆ˜๋‹ค.

โ€ป ๊ฒ€์ฆ ๋ฐฉ๋ฒ•๋“ค

v1

error๋ฅผ Map์œผ๋กœ ๋งŒ๋“ค๊ณ ,

if๋ฌธ์œผ๋กœ ๊ฒ€์ฆ์„ ํ•œ ๋‹ค์Œ, ์ƒ๊ธด ์˜ค๋ฅ˜๋ฅผ error Map์— ๋‹ด์•„์„œ

model.getAttribute("error",error);๋กœ ์—๋Ÿฌ ๋งต์„ ๋„ฃ์–ด์„œ view ํŒŒ์ผ๋กœ redirectํ•œ๋‹ค.

 

view์—์„œ๋Š” ์•„๋ž˜์ฒ˜๋Ÿผ ์—๋Ÿฌ ์—ฌ๋ถ€๋ฅผ ๊ฐ€์ ธ์˜จ๋‹ค.

<label for="itemName" th:text="#{label.item.itemName}">์ƒํ’ˆ๋ช…</label>
<input type="text" id="itemName" th:field="*{itemName}"
       th:class="${errors?.containsKey('itemName')} ? 'form-control field-error' : 'form-control'"
       class="form-control" placeholder="์ด๋ฆ„์„ ์ž…๋ ฅํ•˜์„ธ์š”">
<div class="field-error" th:if="${errors?.containsKey('itemName')}"
     th:text="${errors['itemName']}">
    ์ƒํ’ˆ๋ช… ์˜ค๋ฅ˜
</div>

๋‹จ์  : ํƒ€์ž… ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด ์ปจํŠธ๋กค๋Ÿฌ์— ๋“ค์–ด์˜ค๊ธฐ๋„ ์ „์— ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•˜๊ธฐ ๋•Œ๋ฌธ์— ํƒ€์ž… ์—๋Ÿฌ ์ฒ˜๋ฆฌ๋ฅผ ํ•  ์ˆ˜ ์—†๋‹ค.

           ์ค‘๋ณต์ด ๋งŽ๋‹ค.

           ๊ณ ๊ฐ์ด ์ž…๋ ฅํ•œ 

v2

๋งต์„ ๋งŒ๋“ค์ง€ ์•Š๊ณ , ์Šคํ”„๋ง์—์„œ ์ง€์›ํ•˜๋Š” BindingResult๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•์ด๋‹ค.

ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ BindingResult๊นŒ์ง€ ๋ฐ›์•„์˜ค๊ณ 

if๋ฌธ์œผ๋กœ ์—๋Ÿฌ๋ฅผ ์ฒ˜๋ฆฌํ•˜์—ฌ

bindingResult.addError(new ObjectError("item", "๊ฐ€๊ฒฉ * ์ˆ˜๋Ÿ‰์˜ ํ•ฉ์€ 10,000์› ์ด์ƒ์ด์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ํ˜„์žฌ ๊ฐ’ = " + resultPrice)); ๊ผด๋กœ ์—๋Ÿฌ๋ฅผ ์ฒ˜๋ฆฌํ•œ๋‹ค.

+ ObjectError vs FieldError

ํ•„๋“œ์— ์˜ค๋ฅ˜๊ฐ€ ์žˆ์œผ๋ฉด FieldError(์˜ค๋ฅ˜ ๋ฐœ์ƒ์‹œ ์‚ฌ์šฉ์ž ์ž…๋ ฅ ๊ฐ’ ์œ ์ง€ ๊ธฐ๋Šฅ ์ œ๊ณต)

ํŠน์ • ํ•„๋“œ๋ฅผ ๋„˜์–ด์„œ๋Š” ์˜ค๋ฅ˜๊ฐ€ ์žˆ์œผ๋ฉด ObjectError

BindingResult⊂Errors๋ผ์„œ ๋ฐ”๊ฟ” ์จ๋„ ๋˜์ง€๋งŒ ๊ธฐ๋Šฅ์ด ์ ์Œ

 

View์—์„œ๋Š” ์•„๋ž˜์ฒ˜๋Ÿผ ๊ฐ€์ ธ๋‹ค ์‚ฌ์šฉํ•œ๋‹ค.

<label for="itemName" th:text="#{label.item.itemName}">์ƒํ’ˆ๋ช…</label>
<input type="text" id="itemName" th:field="*{itemName}"
       th:errorclass="field-error" class="form-control" placeholder="์ด๋ฆ„์„ ์ž…๋ ฅํ•˜์„ธ์š”">
<!--th:field๋กœ ์ธํ•ด ๋ชจ๋“ ๊ฒŒ ๋‹ค ๋จ-->
<div class="field-error" th:errors="*{itemName}">
    ์ƒํ’ˆ๋ช… ์˜ค๋ฅ˜
</div>

 

BindingResult๊ฐ€ ์žˆ์œผ๋ฉด ์˜ค๋ฅ˜๊ฐ€ ์ƒ๊ฒจ๋„ ์ปจํŠธ๋กค๋Ÿฌ๋ฅผ ํ˜ธ์ถœํ•ด์ค€๋‹ค ->์—†์œผ๋ฉด ์•„์˜ˆ ์˜ค๋ฅ˜ํŽ˜์ด์ง€๋กœ ๊ฐ€๋ฒ„๋ฆผ

 

- ๋ฐœ์ „ ->

๋ฉ”์„ธ์ง€ ํŒŒ์ผ์ฒ˜๋Ÿผ errors.properties ํŒŒ์ผ์„ ๋งŒ๋“ค์–ด ๋”ฐ๋กœ ๊ด€๋ฆฌํ•˜๋Š” ๋ฐฉ๋ฒ•์ด ์žˆ๋‹ค. ๊ตญ์ œํ™”๋„ ๊ฐ€๋Šฅํ•˜๋‹ค.

 

- ๋ฐœ์ „ ->

FieldError๋‚˜ ObjectError๋ฅผ ์ƒ์„ฑํ•˜์ง€ ์•Š๊ณ 

bindingResult.rejectValue("price", "range", new Object[]{1000, 1000000}, null);

์ด๋ ‡๊ฒŒ๋งŒ ์ž‘์„ฑํ•  ์ˆ˜๋„ ์žˆ๋‹ค.

์—๋Ÿฌ ์ฝ”๋“œ๋Š” errors.properties ์—์„œ ํ•„๋“œ๋ช…๊ณผ ์กฐํ•ฉํ•ด์„œ ์ฐพ์•„์ค€๋‹ค.

(ex)range.item.price, range.price, range.java.lang.Integer, range)

์—ฌ๋Ÿฌ ๊ฐœ ์žˆ๋Š” ๊ฒฝ์šฐ, ๊ตฌ์ฒด์ ์ธ ์ˆœ๋ถ€ํ„ฐ ์šฐ์„ ์ˆœ์œ„๋กœ ์‚ฌ์šฉํ•œ๋‹ค. (์˜ค๋ฅ˜ ๋ฉ”์„ธ์ง€ ์žฌ์‚ฌ์šฉ ๊ฐ€๋Šฅ)

MessageCodesResolver๊ฐ€ ์ด๋Ÿฐ ๊ธฐ๋Šฅ์„ ์ง€์›ํ•œ๋‹ค.

 

โ€ป Validatior ๋ถ„๋ฆฌ

Validator๋ฅผ implementํ•ด์„œ CustomValidator๋ฅผ ๋งŒ๋“ ๋‹ค.

CustomValidator ์•ˆ์—๋Š” bindingResult๋ฅผ ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ๋ฐ›์•„,

if๋ฌธ์œผ๋กœ ์—๋Ÿฌ๋ฅผ ์ฒดํฌํ•˜๊ณ  ์—๋Ÿฌ๋ฅผ bindingResult์— ์ €์žฅํ•œ๋‹ค.

customValidator.validate(target, bindingResult); ํ•œ ์ค„๋กœ validation์„ ํ•  ์ˆ˜ ์žˆ๋‹ค.

-๋ฐœ์ „->

@InitBinder
public void init(WebDataBinder dataBinder) {
    log.info("init binder {}", dataBinder);
    dataBinder.addValidators(itemValidator);
}

์— validator๋“ค์„ ๋“ฑ๋กํ•ด๋‘๋ฉด,

ํŒŒ๋ผ๋ฏธํ„ฐ์˜ ๊ฒ€์ฆํ•  ๊ฐ์ฒด ์•ž์—@Validated๋ฅผ ๋ถ™์ž„์œผ๋กœ์จ ๊ฒ€์ฆ์„ ์ ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

 

5. ๊ฒ€์ฆ2 - Bean Validation

 

โ€ป Bean Validation

๊ฒ€์ฆ ์ฝ”๋“œ๋ฅผ ์• ๋…ธํ…Œ์ด์…˜ ํ•˜๋‚˜๋กœ ๋๋‚ด์ž

 

๊ฒ€์ฆ ์• ๋…ธํ…Œ์ด์…˜ ๋ชจ์Œ:

https://docs.jboss.org/hibernate/validator/6.2/reference/en-US/html_single/#validator-defineconstraints-spec

 

Hibernate Validator 6.2.5.Final - Jakarta Bean Validation Reference Implementation: Reference Guide

Validating data is a common task that occurs throughout all application layers, from the presentation to the persistence layer. Often the same validation logic is implemented in each layer which is time consuming and error-prone. To avoid duplication of th

docs.jboss.org

@Data
public class Item {

    private Long id;

    @NotBlank
    private String itemName;

    @NotNull
    @Range(min = 1000, max = 10000)
    private Integer price;

    @NotNull
    @Max(9999)
    private Integer quantity;
}

๊ฒ€์ฆ ์ˆœ์„œ
1. @ModelAttribute ๊ฐ๊ฐ์˜ ํ•„๋“œ์— ํƒ€์ž… ๋ณ€ํ™˜ ์‹œ๋„
    1. ์„ฑ๊ณตํ•˜๋ฉด ๋‹ค์Œ์œผ๋กœ
    2. ์‹คํŒจํ•˜๋ฉด typeMismatch ๋กœ FieldError ์ถ”๊ฐ€
2. Validator ์ ์šฉ

-> ๋ฐ”์ธ๋”ฉ์— ์„ฑ๊ณตํ•œ ํ•„๋“œ๋งŒ Bean Validation ์ ์šฉ

๋ฐ”์ธ๋”ฉ์— ์„ฑ๊ณตํ•˜์ง€ ๋ชปํ•˜๋ฉด ์ผ๋‹จ TypeMismatch๊ฐ€ ๋จผ์ € ๋œฌ๋‹ค

 

์ฃผ์˜ : ๊ธ€๋กœ๋ฒŒ Validator๋ฅผ ์ง์ ‘ ๋“ฑ๋กํ•˜๋ฉด Bean Validator๊ฐ€ ๊ธ€๋กœ๋ฒŒ Validator๋กœ ๋“ฑ๋ก๋˜์ง€ ์•Š๋Š”๋‹ค.

 

โ€ป ObjectError

@ScriptAssert(lang = "javascript", script = "_this.price * _this.quantity >=
10000") ๋ฐฉ์‹์„ ์‚ฌ์šฉํ•˜๋ฉด ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๋Š”๋ฐ, ์–ต์ง€์ธ ๋ถ€๋ถ„์ด ์ข€ ์žˆ๋‹ค. -> ์ด ๋ถ€๋ถ„๋งŒ ์ž๋ฐ” ์ฝ”๋“œ๋กœ ์ฒ˜๋ฆฌํ•˜๋Š” ๊ฒŒ ์ฐจ๋ผ๋ฆฌ ๋‚˜์Œ

 

โ€ป Groups

@Validated ๋Š” ์Šคํ”„๋ง ์ „์šฉ ๊ฒ€์ฆ ์• ๋…ธํ…Œ์ด์…˜์ด๊ณ , @Valid ๋Š” ์ž๋ฐ” ํ‘œ์ค€ ๊ฒ€์ฆ ์• ๋…ธํ…Œ์ด์…˜์ด๋‹ค

Validated๋Š” groups๊ฐ€ ๊ฐ€๋Šฅํ•ด์„œ, Validation class๋“ค์„ ๋“ฑ๋ก์‹œ์ผœ ๋†“๊ณ , ํ•„์š”ํ•œ ๊ฒƒ๋งŒ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

@Data
public class Item {

    @NotNull(groups = UpdateCheck.class)
    private Long id;

    @NotBlank(groups = {SaveCheck.class, UpdateCheck.class})
    private String itemName;

    @NotNull(groups = {SaveCheck.class, UpdateCheck.class})
    @Range(min = 1000, max = 10000)
    private Integer price;

    @NotNull(groups = {SaveCheck.class, UpdateCheck.class})
    @Max(9999)
    private Integer quantity;
}
@PostMapping("/add")
public String addItemV5(@Validated(SaveCheck.class) @ModelAttribute Item item, BindingResult bindingResult, RedirectAttributes redirectAttributes, Model model) {


@PostMapping("/{itemId}/edit")
public String edit(@PathVariable Long itemId, @Validated(UpdateCheck.class) @ModelAttribute Item item, BindingResult bindingResult) {

โ€ป Form ์ „์†ก ๊ฐ์ฒด ๋ถ„๋ฆฌ 

์‹ค๋ฌด์—์„œ๋Š” ์‚ฌ์‹ค ๋“ฑ๋ก, ์ˆ˜์ •์˜ ๊ฒฝ์šฐ ๋ฐ›๋Š” ๋ฐ์ดํ„ฐ๊ฐ€ ์•„์˜ˆ ๋‹ค๋ฅด๋‹ค.

๋”ฐ๋ผ์„œ groups๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ๋ณด๋‹ค ์•„์˜ˆ ๋ณ„๋„์˜ ๊ฐ์ฒด๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ๋‚ซ๋‹ค.

 

 

โ€ป @ModelAttribute vs @RequestBody

(@ModelAttribute ๋Š” HTTP ์š”์ฒญ ํŒŒ๋ผ๋ฏธํ„ฐ(URL ์ฟผ๋ฆฌ ์ŠคํŠธ๋ง, POST Form)๋ฅผ ๋‹ค๋ฃฐ ๋•Œ ์‚ฌ์šฉํ•œ๋‹ค.
@RequestBody ๋Š” HTTP Body์˜ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ์ฒด๋กœ ๋ณ€ํ™˜ํ•  ๋•Œ ์‚ฌ์šฉํ•œ๋‹ค. ์ฃผ๋กœ API JSON ์š”์ฒญ์„ ๋‹ค๋ฃฐ ๋•Œ
์‚ฌ์šฉํ•œ๋‹ค.)

RequestBody์—๋„ Valid, Validated๋ฅผ ์ ์šฉํ•  ์ˆ˜ ์žˆ์Œ.

ํ•˜์ง€๋งŒ, @ModelAttribute ๊ฐ๊ฐ์˜ ํ•„๋“œ ๋‹จ์œ„๋กœ ์„ธ๋ฐ€ํ•˜๊ฒŒ ์ ์šฉ๋จ -> ํŠน์ • ํ•„๋“œ๊ฐ€ ๋ฐ”์ธ๋”ฉ ๋˜์ง€ ์•Š์•„๋„ ๋‚˜๋จธ์ง€ ํ•„๋“œ ์‚ฌ์šฉ ๊ฐ€๋Šฅ

๋ฐ˜๋ฉด @RequestBody๋Š” HttpMessageConverter ๋‹จ๊ณ„์—์„œ JSON ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ์ฒด๋กœ ๋ณ€๊ฒฝํ•˜์ง€ ๋ชปํ•˜๋ฉด ์ดํ›„
๋‹จ๊ณ„ ์ž์ฒด๊ฐ€ ์ง„ํ–‰๋˜์ง€ ์•Š๊ณ  ์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค. ์ปจํŠธ๋กค๋Ÿฌ๋„ ํ˜ธ์ถœ๋˜์ง€ ์•Š๊ณ , Validator๋„ ์ ์šฉํ•  ์ˆ˜ ์—†๋‹ค.