책&강의 정리
[Java / Spring] 스프링 MVC 2편 - 백엔드 웹 개발 활용 기술 - 2. 스프링 통합과 폼
마볼링
2023. 6. 14. 16:52
본 내용은 인프런 김영한님의
"스프링 MVC 2편 - 백엔드 웹 개발 활용 기술" 강의 내용을 정리한 것입니다.
📕 타임리프 스프링 통합
스프링 통합 메뉴얼
Tutorial: Thymeleaf + Spring
Preface This tutorial explains how Thymeleaf can be integrated with the Spring Framework, especially (but not only) Spring MVC. Note that Thymeleaf has integrations for both versions 3.x and 4.x of the Spring Framework, provided by two separate libraries c
www.thymeleaf.org
📗 스프링 통합으로 추가되는 기능들
- 스프링의 SpringEL 문법 통합
- ${@myBean.doSomething()} 처럼 스프링 빈 호출 지원
- 편리한 폼 관리를 위한 추가 속성
- th:object (기능 강화, 폼 커맨드 객체 선택)
- th:field, th:errors, th:errorclass
- 폼 컴포넌트 기능
- checkbox, radio button, List 등을 편리하게 사용할 수 있는 기능 지원
- 스프링의 메시지, 국제화 기능의 편리한 통합
- 스프링의 검증, 오류 처리 통합
- 스프링의 변환 서비스 통합(ConversionService)
📗 설정 방법
타임리프 템플릿 엔진을 스프링 빈에 등록하고, 타임리프용 뷰 리졸버를 스프링 빈으로 등록
-> 스프링 부트는 자동화
[build.gradle]
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
타임리프 관련 설정을 변경하고 싶으면 다음을 참고해서 application.properties 에 추가하면 된다.
Common Application Properties
docs.spring.io
📕 입력 폼 처리
<form action="item.html" th:action th:object="${item}" method="post">
<div>
<label for="itemName">상품명</label>
<input type="text" id="itemName" th:field="*{itemName}" class="formcontrol" placeholder="이름을 입력하세요">
</div>
- th:object="${item}": <form> 에서 사용할 객체를 지정. 선택 변수 식( *{...} )을 적용할 수 있음
- th:field="*{itemName}"
- *{itemName} 는 선택 변수 식을 사용했는데, ${item.itemName} 과 같음.
- 앞서 th:object 로 item 을 선택했기 때문에 선택 변수 식을 적용 가능
- th:field 는 id , name , value 속성을 모두 자동으로 생성
- id : th:field 에서 지정한 변수 이름. id="itemName"
- name : th:field 에서 지정한 변수 이름. name="itemName"
- value : th:field 에서 지정한 변수의 값. value=""
- 참고로 해당 예제에서 id 속성을 제거해도 th:field 가 자동으로 생성
렌더링 전
<input type="text" id="itemName" th:field="*{itemName}" class="form-control"
placeholder="이름을 입력하세요">
렌더링 후
<input type="text" id="itemName" class="form-control" placeholder="이름을
입력하세요" name="itemName" value="">
📕 요구사항 추가
[ItemType] - 상품 종류 ENUM
package hello.itemservice.domain.item;
public enum ItemType {
BOOK("도서"), FOOD("음식"), ETC("기타");
private final String description;
ItemType(String description) {
this.description = description;
}
public String getDescription() {
return description;
}
}
[DeliveryCode] - 배송 방식 Class
package hello.itemservice.domain.item;
import lombok.AllArgsConstructor;
import lombok.Data;
/**
* FAST : 빠른 배송
* NORMAL : 일반 배송
* SLOW : 느린 배송
*/
@Data
@AllArgsConstructor
public class DeliveryCode {
private String code;
private String displayName;
}
[Item] - 상품 Data
package hello.itemservice.domain.item;
import lombok.Data;
import java.util.List;
@Data
public class Item {
private Long id;
private String itemName;
private Integer price;
private Integer quantity;
private Boolean open; //판매 여부
private List<String> regions; //등록 지역
private ItemType itemType; //상품 종류
private String deliveryCode; //배송 방식
public Item() {
}
public Item(String itemName, Integer price, Integer quantity) {
this.itemName = itemName;
this.price = price;
this.quantity = quantity;
}
}
📕 체크 박스 단일
- HTML checkbox는 선택이 안되면 클라이언트에서 서버로 값 자체를 보내지 않음
- 스프링 MVC에서는 _open 처럼 기존 체크 박스 이름 앞에 언더스코어( _ )를 붙여서 전송하면 체크를 해제했다고 인식할 수 있음
- <input type="hidden" name="_open" value="on"/>
- 체크 박스 선택: open, _open 전송
- 체크 박스 미선택: _open만 전송
- 타임리프를 사용할 경우 히든 필드를 자동으로 생성
[addForm.html]
<!-- single checkbox -->
<div>판매 여부</div>
<div>
<div class="form-check">
<input type="checkbox" id="open" th:field="*{open}" class="form-check-input">
<label for="open" class="form-check-label">판매 오픈</label>
</div>
</div>
📕 체크 박스 멀티
📗 @ModelAttribute의 특별한 사용법
@ModelAttribute("regions")
public Map<String, String> regions() {
Map<String, String> regions = new LinkedHashMap<>();
regions.put("SEOUL", "서울");
regions.put("BUSAN", "부산");
regions.put("JEJU", "제주");
return regions;
}
- 각각의 컨트롤러에서 model.addAttribute(...)을 사용하여 체크 박스를 구성하는 데이터를 반복해서 넣는 대신
@ModelAttribute를 컨트롤러의 메서드에 적용 - 해당 컨트롤러를 요청할 때 regions에서 반환된 값이 자동으로 model에 담김
- 타임리프는 체크박스를 each 루프 안에서 만들 때 임의로 숫자를 각각의 id 뒤에 부여
- ids.prev(...), ids.next(...)로 동적으로 생성되는 id 값을 사용할 수 있음 ex) th:for="${#ids.prev('regions')}"
[addForm.html]
<!-- multi checkbox -->
<div>
<div>등록 지역</div>
<div th:each="region : ${regions}" class="form-check form-check-inline">
<input type="checkbox" th:field="*{regions}" th:value="${region.key}"
class="form-check-input">
<label th:for="${#ids.prev('regions')}"
th:text="${region.value}" class="form-check-label">서울</label>
</div>
</div>
📕 라디오 버튼
@ModelAttribute("itemTypes")
public ItemType[] itemTypes() {
return ItemType.values();
}
- 체크 박스와 같이 @ModelAttributes 사용 가능
- ENUM.values()를 사용하면 해당 ENUM의 모든 정보를 배열로 반환
[addForm.html]
<!-- radio button -->
<div>
<div>상품 종류</div>
<div th:each="type : ${itemTypes}" class="form-check form-check-inline">
<input type="radio" th:field="*{itemType}" th:value="${type.name()}"
class="form-check-input">
<label th:for="${#ids.prev('itemType')}" th:text="${type.description}"
class="form-check-label">BOOK</label>
</div>
</div>
📕 셀렉트 박스
@ModelAttribute("deliveryCodes")
public List<DeliveryCode> deliveryCodes() {
List<DeliveryCode> deliveryCodes = new ArrayList<>();
deliveryCodes.add(new DeliveryCode("FAST", "빠른 배송"));
deliveryCodes.add(new DeliveryCode("NORMAL", "일반 배송"));
deliveryCodes.add(new DeliveryCode("SLOW", "느린 배송"));
return deliveryCodes;
}
- @ModelAttributes가 있는 deliveryCodes() 메서드는 컨트롤러가 호출될 때마다 사용되므로
deliveryCodes 객체도 계속 생성됨 - 이러한 부분은 미리 생성해두고 재사용하는 것이 더 효율적
[addForm.html]
<!-- SELECT -->
<div>
<div>배송 방식</div>
<select th:field="*{deliveryCode}" class="form-select">
<option value="">==배송 방식 선택==</option>
<option th:each="deliveryCode : ${deliveryCodes}" th:value="${deliveryCode.code}"
th:text="${deliveryCode.displayName}">FAST</option>
</select>
</div>