[๊ฐ์] ์คํ๋ง MVC 2ํธ - ๋ฐฑ์๋ ์น ๊ฐ๋ฐ ํต์ฌ ๊ธฐ์ 3
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๋ฅผ ๋ฐ๊ฟ ํ ๋ง๋ฅผ ๋ฐ๊พธ๊ณ ์๋ฌ ๋ฉ์ธ์ง๋ฅผ ์ถ๋ ฅํ ์ ์๋ค.
๋ฌธ์ ์ :
ํ์ ์ค๋ฅ ์ฒ๋ฆฌ๊ฐ ๋์ง ์๋๋ค. ์ปจํธ๋กค๋ฌ๊น์ง ๋ค์ด์ค๊ธฐ๋ ์ ์ ์์ธ๊ฐ ๋ฐ์ํ๋ค.
๋ทฐ ํ ํ๋ฆฟ์์ ์ค๋ณต ์ฒ๋ฆฌ๊ฐ ๋ง๋ค.