본 내용은 인프런 김영한님의
"스프링 MVC 2편 - 백엔드 웹 개발 활용 기술" 강의 내용을 정리한 것입니다.
📕 타임리프 스프링 통합
스프링 통합 메뉴얼
📗 스프링 통합으로 추가되는 기능들
- 스프링의 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 에 추가하면 된다.
📕 입력 폼 처리
<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>