안녕하딤니카
그간 폐관수련으로 공부를 소홀히 하다가 다시 미라클모닝도 시작하고... 열심히 공부 중인 재영입니다
최근에는 스프링의 검증 처리를 하는 방법에 대해 자세히 배우고 있는데
맨날 JSP로 if문 써서 처리하다가... 타임리프 쓰니까 코드 관리하기 좋더라구요
이번 글은 제가 강의 들으면서 검증 처리를 구현한 과정을 간단히 정리하고
초반에 한글이 출력되지 않아 문제를 해결한 과정을 살짝쿵야 남겨볼게요
저도 남겨두면 두고두고 볼 것 같아서요! (오늘도 지극히 제 위주의 글이라는 뜻입니다
그럼 시 작
(수강 강의: 김영한의 스프링 완전정복 로드맵 - 스프링 MVC 2편)
1. Validation(검증) 처리 과정
1) resources 폴더 아래에 errors.properties 파일 생성 후 레벨 별로 분류해서 error 메시지 작성
#==ObjectError==
#Level1
totalPriceMin.item=상품의 가격 * 수량의 합은 {0}원 이상이어야 합니다. 현재 값 = {1}
#Level2 - 생략
totalPriceMin=전체 가격은 {0}원 이상이어야 합니다. 현재 값 = {1}
#==FieldError==
#Level1
required.item.itemName=상품 이름은 필수입니다.
range.item.price=가격은 {0} ~ {1} 까지 허용합니다.
max.item.quantity=수량은 최대 {0} 까지 허용합니다.
#Level2 - 생략
#Level3
required.java.lang.String = 필수 문자입니다.
required.java.lang.Integer = 필수 숫자입니다.
min.java.lang.String = {0} 이상의 문자를 입력해주세요.
min.java.lang.Integer = {0} 이상의 숫자를 입력해주세요.
range.java.lang.String = {0} ~ {1} 까지의 문자를 입력해주세요.
range.java.lang.Integer = {0} ~ {1} 까지의 숫자를 입력해주세요.
max.java.lang.String = {0} 까지의 문자를 허용합니다.
max.java.lang.Integer = {0} 까지의 숫자를 허용합니다.
#Level4
required = 필수 값 입니다.
min= {0} 이상이어야 합니다.
range= {0} ~ {1} 범위를 허용합니다.
max= {0} 까지 허용합니다.
구체적인 것에서 덜 구체적인 걸로 우선순위를 Level 별로 구분해서 errors.properties를 작성합니다
여기저기서 error 메시지를 재활용하는 경우가 많으니 관리의 용이성 측면에서 이렇게 하는 걸 권장합니다
물론 en 파일을 하나 더 만들어두면 에러 메시지의 국제화도 문제없습니다!
(여기서 저는 en 은 생략했어요)
1-1) 검증 오류 메시지의 생성 순서 (우선순위 정리)
itemName (상품명) FieldError 예시로 검증 오류 메시지의 생성 순서를 정리해 보겠습니다
위에 코드에서 Level 별로 나눠놔서 보기 편하지만 한 번 더 정리해 볼게요
- required.item.itemName
- required.itemName
- required.java.lang.String
- required
이 메시지 코드를 기반으로 순서대로 MessageSource에서 메시지를 찾아서 error 메시지를 담고, 출력합니다
2. Controller에서 Validation 코드 작성
1) 검증 후 검증 오류 메시지를 화면에 뿌려주는 과정 정리
제가 작성한 코드를 먼저 보기 전에, 검증 오류 메시지가 화면에 뿌려지는 과정을 간단히 정리하겠습니다
- rejectValue() 호출
- MessageCodesResolver를 사용해서 검증 오류 코드로 메시지 코드들을 생성
- new FieldError()를 생성하면서 메시지 코드들을 보관
- 타임리프 th:erros에서 코드들로 메시지를 순서대로 찾고, 화면에 노출
2) Controller에서 Validation 로직을 구현
@PostMapping("/add")
public String addItem(@ModelAttribute Item item,
BindingResult bindingResult,
RedirectAttributes redirectAttributes,
Model model) {
log.info("objectName={}", bindingResult.getObjectName());
log.info("target={}", bindingResult.getTarget());
// 검증 로직
if (!StringUtils.hasText(item.getItemName())) {
bindingResult.rejectValue("itemName", "required");
}
// 단순 로직은 이렇게 한 줄로 써도 되는데 기능이 별로 없다
// ValidationUtils.rejectIfEmptyOrWhitespace(bindingResult, "itemName", "required");
// 검증에 실패하면 다시 입력 폼으로 돌아가는 로직
if (bindingResult.hasErrors()) {
log.info("errors = {}", bindingResult);
return "validation/v2/addForm";
}
// 성공 로직
Item savedItem = itemRepository.save(item);
redirectAttributes.addAttribute("itemId", savedItem.getId());
redirectAttributes.addAttribute("status", true);
return "redirect:/validation/v2/items/{itemId}";
}
성공 로직 (상품을 정상 등록)과 더불어 검증 로직, 검증에 실패하면 다시 입력 폼으로 돌아가는 로직을 구현합니다
여기서 중요한 점은 BindingResult 파라미터의 위치인데요, @ModelAttribute 바로 옆에 적어줘야 합니다
다른 위치에 적으면 안 됩니다!
지금 근데 상품명 관련 검증 로직만 넣어놔서 컨트롤러 코드가 별로 안 길어보이지만
다른 로직도 다 들어있어서 원본 코드는 좀 복작복작하거든요?
이거 검증 로직만 따로 관리하게 분리해두면 좋은데 제가 아직 강의를 거기까지 안 들었음
일단 컨트롤러에 넣은 상태의 코드로 보겠습니당
3) Thymeleaf (타임리프) 기능을 활용하여 오류처리
Controller 구현이 끝나면 (Controller 구현 전 필요한 Model 구현 과정은 여기서는 생략티비)
타임리프로 필드 오류처리를 표현해 봅시다
타임리프는 스프링의 BindingResult를 활용해서 편리하게 검증 오류를 표현하는 기능을 제공합니다
- #fields : #fields 로 BindingResult 가 제공하는 검증 오류에 접근할 수 있다.
- th:errors : 해당 필드에 오류가 있는 경우에 태그를 출력한다. (th:if 편의 버전)
- th:errorclass : th:field에서 지정한 필드에 오류가 있으면 class 정보를 추가한다.
타임리프의 검증 오류 표현 기능을 활용해서 html 에 코드를 작성해 봅시다
<form action="item.html" th:action th:object="${item}" method="post">
<div>
<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="이름을 입력하세요">
<div class="field-error" th:errors="*{itemName}">
상품명 오류
</div>
</div>
이렇게 작성하면 검증 오류로 메시지 출력을 해야 할 때


요로코롬 잘 나오는 걸 볼 수 있습니다
컨트롤러에서 error.properties 에서 적어둔 코드를 전부 입력하지 않아도, rejectValue() 를 사용하면 코드 구성을 규칙에 맞춰서 알아서 해주기 때문에, 규칙 설정만 잘해주면 코드를 축약할 수 있답니다 (MessageCodesResolver 를 사용하기 때문)
2. 검증, 메시지 국제화 처리 과정에서 properties 파일의 한글을 읽지 못하는 문제 해결 방법
이걸 뭐라고 표현해야 되나... 암튼 메시지 국제화, 검증 오류 내용을 properties 파일에 적고
테스트코드를 실행하면 이런 에러가 날 때가 있습니다

저건 제가 메시지 국제화를 하기 위해 messages.properties 에 안녕 이라는 내용을 적어두고 테스트코드를 돌렸더니,
왜 안녕을 안녕 이라고 읽지 못하고 ?? 라고 읽는 것인가... 갑자기 홍길동이 된 느낌이었습니다
아버지를 아버지라 부르지 못하고...
검증 오류 메시지는 이렇게 출력되는 불상사도 있었습니다

ㅋㅋㅋㅋㅋㅋㅋㅋ 하 세상에
물음표의 축복이 끝이 없네...
웃기죠? 전 하나도 안 웃깁니다
눈물 닦고 저 물음표가 화면에 뿌려지는 이유를 뜯어고치러 가봅시다


분명 저는 한글로 잘 적었습니다
절대 ?? 라고 적지 않았단 말입니다
흠... 이럴 때면 저는 Settings에 들어가서 뜯어보는 편인데요
분명 어딘가 UTF-8로 설정이 되어있지 않았을 것 같은 느낌적인 느낌느낌 이 들었습니다
Settings 로 가봅시다 ([Settings] > [Editor] > [File Encodings] > [Default encoding for properties files])

이봐이봐
Default encoding for properties files 가 ISO-8859-1 로 되어있었습니다
이걸 당장 UTF-8 로 바꿔줍시다

이렇게 말입니다
이렇게 바꾸면 진짜 열받는 게 properties 파일의 한글이 다 ?? 로 바뀝니다
열받는 거 꾹 참고... 한글로 다시 하나하나 적어줍시다
(그니까 이걸 properties 파일 작성 전에 체크했다면 이런 불상사가 생기지 않았을 텐데...)
여러분들은 properties 파일을 작성하다 한글을 쓸 때 노란 줄이 그이면 무시하지 말고 꼭 마우스를 올려보십쇼

이런 경고메시지가 나온다면 필히 저 문제일 겁니다
Default encoding for properties files 를 이때 바로 UTF-8 로 바꿔주도록 합시다
※ 혹시 컴퓨터 세팅의 기본 언어가 영어인 경우에는 이 방법 말고 다른 방법 쓰셔야 됩니다!
application.properties 파일에 이 설정을 추가해 주세요
spring.messages.fallback-to-system-locale=false
기본 설정은 이게 true로 되어 있어서 따로 추가할 필요는 없는 설정입니다 (기본 언어가 한글로 세팅되어 있을 경우)
- true로 설정되어 있을 때, 스프링에서 Locale에 해당하는 파일을 못 찾으면, 시스템 Locale에 해당하는 파일을 찾습니다 (시스템 Locale로 fallback)
- 컴퓨터 기본 언어가 영어일 경우에 Locale.Korea를 파라미터로 넘겨주면, 스프링에서는 messages_ko.properties 파일을 먼저 찾는데, 이 파일이 없으니까 messages_en.properties를 스프링이 참조하는 원리입니다
- 위 설정을 false로 바꾸면, 스프링에서 Locale에 해당하는 파일을 못 찾을 시, messages_en.properties 을 찾는 게 아니라 기본 설정 파일 messages.properties 을 참조하게 됩니다
(cmd + F 으로 spring.messages.fallback-to-system-locale 검색)
이거 뭐 어떻게 끝내야 되지...?
암튼 잘 해결했습니다
명심해. 노란 줄을 절대 무시해선 안된다는 것을.
긴 글 읽어주셔서 감사합니따! 😘
🍀
좋아하는 것을 계속 좋아하세요!
반드시 행복해집니다
백엔드 개발자가 되고 싶어서 열심히 헤딩 중인 재영입니다 :-)
[Github] https://github.com/chujaeyeong
[E-mail] chujy1224@gmail.com
'Study > Spring' 카테고리의 다른 글
| MySQL에서 제공하는 Lock을 이용해서 동시성 제어하기 (Pessimistic Lock, Optimistic Lock, Named Lock) (0) | 2024.08.17 |
|---|---|
| 선착순 쿠폰 발급 시스템 로직 변경으로 Redis의 Set 자료구조 찍먹해보기 (0) | 2024.07.02 |
| 선착순 쿠폰 발급 시스템 개발을 통해 Redis 랑 Kafka 찍먹해보기 (2) | 2024.07.01 |
| [Spring] Autowire 불가 에러 해결 방법 (Could not autowire, 스프링 빈 등록 불가 에러) (0) | 2023.09.26 |
| [Spring] BeanNotOfRequiredTypeException (스프링 빈 등록 에러) 발생 시 체크사항 정리 (0) | 2023.09.16 |