SPRING/INFLEARN

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

ozllzL 2023. 1. 13. 03:06

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

3. ๋ฉ”์‹œ์ง€, ๊ตญ์ œํ™”

- ๋ฉ”์‹œ์ง€, ๊ตญ์ œํ™” ์†Œ๊ฐœ

messages.properties๋ผ๋Š” ๋ฉ”์„ธ์ง€ ๊ด€๋ฆฌ์šฉ ํŒŒ์ผ์„ ๋งŒ๋“ค์ž

-> key๊ฐ’์„ ์ €์žฅํ•ด๋†“๊ณ  ๋ถˆ๋Ÿฌ์™€์„œ ์‚ฌ์šฉํ•œ๋‹ค.

    ๋ณ€๊ฒฝํ•  ์ผ์ด ์ƒ๊ธฐ๋ฉด ์ด ํŒŒ์ผ์˜ key๋ฅผ ๋ณ€๊ฒฝํ•จ์œผ๋กœ์จ ๋ชจ๋‘์— ์ ์šฉ๋˜๊ฒŒ

 

messages_en.properties, messages_kor.properties ์ด๋ ‡๊ฒŒ ์‚ฌ์šฉํ•˜๋ฉด ๊ตญ์ œํ™”๋„ ๊ฐ€๋Šฅ

-> HTTP accept-language ํ•ด๋” ๊ฐ’์„ ์‚ฌ์šฉํ•˜๊ฑฐ๋‚˜ ์‚ฌ์šฉ์ž๊ฐ€ ์ง์ ‘ ์–ธ์–ด๋ฅผ ์„ ํƒํ•˜์—ฌ ์ฟ ํ‚ค์— ์ €์žฅ ํ•˜๋Š” ๋ฐฉ๋ฒ•์œผ๋กœ

 

์Šคํ”„๋ง์€ ๊ธฐ๋ณธ์ ์ธ ๋ฉ”์‹œ์ง€์™€ ๊ตญ์ œํ™” ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•œ๋‹ค.

 

- ์Šคํ”„๋ง ๋ฉ”์‹œ์ง€ ์†Œ์Šค ์„ค์ •

1. MessageSource(๊ตฌํ˜„์ฒด : ResourceBundleMessageSource)๋ฅผ ์ง์ ‘ ๋นˆ์œผ๋กœ ๋“ฑ๋กํ•œ๋‹ค.

2. application.properties์— ๋‹ค์Œ ์„ค์ •

spring.messages.basename=messages,config.i18n.messages

basename์˜ ๊ธฐ๋ณธ๊ฐ’์ด message์ด๋ฏ€๋กœ ๊ตณ์ด ์ž‘์„ฑํ•˜์ง€ ์•Š๊ณ  messages_en.properties, messages_kor.properties ๋“ฑ๋ก๋งŒ ํ•ด๋„ ์ž˜ ๋จ 

๋‹ค๋ฅธ ๋‚ด์šฉ์€ application.properties ๊ณต์‹ ๋ฌธ์„œ ์ฐธ๊ณ 

 

messages.properties (๊ธฐ๋ณธ๊ฐ’)

hello=์•ˆ๋…•
hello.name=์•ˆ๋…• {0}

messages_en.properties

hello=hello
hello.name=hello {0}

 

- ์Šคํ”„๋ง ๋ฉ”์‹œ์ง€ ์†Œ์Šค ์‚ฌ์šฉ

 

MessageSource๋ฅผ ๋ถˆ๋Ÿฌ๋‹ค๊ฐ€ ์‚ฌ์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•

@SpringBootTest
public class MessageSourceTest {
    @Autowired
    MessageSource ms;
    @Test
    void helloMessage() {
        String result = ms.getMessage("hello", null, null);
        assertThat(result).isEqualTo("์•ˆ๋…•");
    }

    //no_code๋ผ๋Š” ๊ฑด ์—†์Œ -> exception
    @Test
    void notFoundMessageCode() {
        assertThatThrownBy(() -> ms.getMessage("no_code", null, null))
                .isInstanceOf(NoSuchMessageException.class);
    }

    //default message๋ฅผ ์„ค์ •ํ•ด ์ค„ ์ˆ˜ ์žˆ์Œ
    @Test
    void notFoundMessageCodeDefaultMessage() {
        String result = ms.getMessage("no_code", null, "๊ธฐ๋ณธ ๋ฉ”์‹œ์ง€", null);
        assertThat(result).isEqualTo("๊ธฐ๋ณธ ๋ฉ”์‹œ์ง€");
    }

    //messages ํŒŒ์ผ์˜ {0} ๋ถ€๋ถ„์€ ์น˜ํ™˜ํ•  ์ˆ˜ ์žˆ๋Š” ๋ถ€๋ถ„, Spring์œผ๋กœ ์น˜ํ™˜๋จ
    @Test
    void argumentMessage() {
        String result = ms.getMessage("hello.name", new Object[]{"Spring"}, null);
        assertThat(result).isEqualTo("์•ˆ๋…• Spring");
    }

    //ํ•œ๊ตญ์ผ ๋•Œ -> default messages
    @Test
    void defaultLang() {
        assertThat(ms.getMessage("hello", null, null)).isEqualTo("์•ˆ๋…•");
        assertThat(ms.getMessage("hello", null, Locale.KOREA)).isEqualTo("์•ˆ๋…•");
    }

    //์˜์–ด๊ถŒ์ผ ๋•Œ -> messages_en
    @Test
    void enLang() {
        assertThat(ms.getMessage("hello", null,
                Locale.ENGLISH)).isEqualTo("hello");
    }
}

 

- ์›น ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์— ๋ฉ”์„ธ์ง€ ์ ์šฉํ•˜๊ธฐ

ํƒ€์ž„๋ฆฌํ”„๋กœ ์ ์šฉํ•˜๊ธฐ

th:text="#{page.addItem}"

??: ํ•œ๊ตญ์–ด๊ฐ€ ๊นจ์ง€๋Š”๋ฐ์š”..

- ์›น ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์— ๊ตญ์ œํ™” ์ ์šฉํ•˜๊ธฐ

๊ทธ๋ƒฅ ํ•˜๋ฉด ๋œ๋‹ค.

 

4. ๊ฒ€์ฆ1 - Validation

- ๊ฒ€์ฆ ์š”๊ตฌ์‚ฌํ•ญ

 

<์š”๊ตฌ์‚ฌํ•ญ: ๊ฒ€์ฆ ๋กœ์ง ์ถ”๊ฐ€>
ํƒ€์ž… ๊ฒ€์ฆ
๊ฐ€๊ฒฉ, ์ˆ˜๋Ÿ‰์— ๋ฌธ์ž๊ฐ€ ๋“ค์–ด๊ฐ€๋ฉด ๊ฒ€์ฆ ์˜ค๋ฅ˜ ์ฒ˜๋ฆฌ
ํ•„๋“œ ๊ฒ€์ฆ
์ƒํ’ˆ๋ช…: ํ•„์ˆ˜, ๊ณต๋ฐฑX
๊ฐ€๊ฒฉ: 1000์› ์ด์ƒ, 1๋ฐฑ๋งŒ์› ์ดํ•˜
์ˆ˜๋Ÿ‰: ์ตœ๋Œ€ 9999
ํŠน์ • ํ•„๋“œ์˜ ๋ฒ”์œ„๋ฅผ ๋„˜์–ด์„œ๋Š” ๊ฒ€์ฆ
๊ฐ€๊ฒฉ * ์ˆ˜๋Ÿ‰์˜ ํ•ฉ์€ 10,000์› ์ด์ƒ

 

๊ฒ€์ฆ ๋กœ์ง์„ ๋ฒ—์–ด๋‚ฌ๋‹ค๊ณ  ์—๋Ÿฌ ํŽ˜์ด์ง€๋กœ ๋„˜์–ด๊ฐ€๋ฉด ์•ˆ๋จ,

์œ ์ €์—๊ฒŒ ์–ด๋””๊ฐ€ ํ‹€๋ ธ๋Š”์ง€ ์•Œ๋ ค์ค˜์•ผํ•œ๋‹ค.

 

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

 

- ๊ฒ€์ฆ ์ง์ ‘ ์ฒ˜๋ฆฌ - ์†Œ๊ฐœ

์ƒํ’ˆ ๋“ฑ๋ก ํผ์—์„œ ์ €์žฅ์— ์‹คํŒจํ•˜๋ฉด ์˜ค๋ฅ˜ ๊ฒฐ๊ณผ์™€ ํ•จ๊ป˜ ์ƒํ’ˆ ๋“ฑ๋ก ํผ์œผ๋กœ ๋Œ์•„์˜ค๋Š”

 

- ๊ฒ€์ฆ ์ง์ ‘ ์ฒ˜๋ฆฌ - ๊ฐœ๋ฐœ

 

@PostMapping("/add")
    public String addItem(@ModelAttribute Item item, RedirectAttributes redirectAttributes, Model model) {

        //๊ฒ€์ฆ ์˜ค๋ฅ˜ ๊ฒฐ๊ณผ๋ฅผ ๋ณด๊ด€
        Map<String, String> errors = new HashMap<>();

        //๊ฒ€์ฆ ๋กœ์ง
        if (!StringUtils.hasText(item.getItemName())) {
            errors.put("itemName", "์ƒํ’ˆ ์ด๋ฆ„์€ ํ•„์ˆ˜์ž…๋‹ˆ๋‹ค.");
        }
        if (item.getPrice() == null || item.getPrice() < 1000 || item.getPrice() >
                1000000) {
            errors.put("price", "๊ฐ€๊ฒฉ์€ 1,000 ~ 1,000,000 ๊นŒ์ง€ ํ—ˆ์šฉํ•ฉ๋‹ˆ๋‹ค.");
        }
        if (item.getQuantity() == null || item.getQuantity() >= 9999) {
            errors.put("quantity", "์ˆ˜๋Ÿ‰์€ ์ตœ๋Œ€ 9,999 ๊นŒ์ง€ ํ—ˆ์šฉํ•ฉ๋‹ˆ๋‹ค.");
        }

        //ํŠน์ • ํ•„๋“œ๊ฐ€ ์•„๋‹Œ ๋ณตํ•ฉ ๋ฃฐ ๊ฒ€์ฆ
        if (item.getPrice() != null && item.getQuantity() != null) {
            int resultPrice = item.getPrice() * item.getQuantity();
            if (resultPrice < 10000) {
                errors.put("globalError", "๊ฐ€๊ฒฉ * ์ˆ˜๋Ÿ‰์˜ ํ•ฉ์€ 10,000์› ์ด์ƒ์ด์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ํ˜„์žฌ ๊ฐ’ = " + resultPrice);
            }
        }

        //๊ฒ€์ฆ์— ์‹คํŒจํ•˜๋ฉด ๋‹ค์‹œ ์ž…๋ ฅ ํผ์œผ๋กœ
        if (!errors.isEmpty()) {
            model.addAttribute("errors", errors);
            return "validation/v1/addForm";
        }

        //์„ฑ๊ณต ๋กœ์ง
        Item savedItem = itemRepository.save(item);
        redirectAttributes.addAttribute("itemId", savedItem.getId());
        redirectAttributes.addAttribute("status", true);
        return "redirect:/validation/v1/items/{itemId}";
    }

๊ฒ€์ฆํ•ด์„œ ์—๋Ÿฌ ๋ฉ”์„ธ์ง€๋ฅผ ๋‚ด๋Š” ์ฝ”๋“œ์ด๋‹ค.

model์˜ errors์˜ ๋ฉ”์„ธ์ง€๋ฅผ html์— ๋„ฃ์–ด์ค€๋‹ค.

<div class="field-error" th:if="${errors?.containsKey('itemName')}"
     th:text="${errors['itemName']}">
<input type="text" id="itemName" th:field="*{itemName}"
       th:class="${errors?.containsKey('itemName')} ? 'form-control field-error' : 'form-control'"

class๋ฅผ ๋ฐ”๊ฟ” ํ…Œ๋งˆ๋ฅผ ๋ฐ”๊พธ๊ณ  ์—๋Ÿฌ ๋ฉ”์„ธ์ง€๋ฅผ ์ถœ๋ ฅํ•  ์ˆ˜ ์žˆ๋‹ค.

 

๋ฌธ์ œ์ :

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

๋ทฐ ํ…œํ”Œ๋ฆฟ์—์„œ ์ค‘๋ณต ์ฒ˜๋ฆฌ๊ฐ€ ๋งŽ๋‹ค.