스프링 MVC 2편 - 백엔드 웹 개발 활용 기술 - (10) 타임리프 스프링 통합과 폼 - 입력 폼 처리
인프런 스프링 MVC 2편 - 백엔드 웹 개발 활용 기술편을 학습하고 정리한 내용 입니다.
타임리프에서 지원하는 입력 폼 기능을 통해 기존 프로젝트를 타임리프로 개선해 보자.
th:object: 커멘드 객체를 지정한다.*{...}: 선택 변수 식이라고 한다.th:object에서 선택한 객체에 접근한다.th:field- HTML 태그의
id,name,value속성을 자동으로 처리해준다.
- HTML 태그의
1
2
3
4
5
<!-- 렌더링 전 -->
<input type="text" th:field="*{itemName}" />
<!-- 렌더링 후 -->
<input type="text" id="itemName" name="itemName" th:value="*{itemName}" />
등록 폼
th:object를 적용하려면 먼저 해당 오브젝트 정보를 넘겨줘야 한다.
등록 폼이기 때문에 데이터가 비어있는 빈 오브젝트를 만들어서 뷰에 넘겨 준다.
FormItemController
1
2
3
4
5
@GetMapping("/add")
public String addForm(Model model) {
model.addAttribute("item", new Item());
return "form/addForm";
}
이제 타임리프를 사용해 등록폼을 바꿔 보자.
resources/templates/form/addForm.html
1
2
3
4
5
6
7
8
9
10
11
12
13
<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="form-control" placeholder="이름을 입력하세요">
</div>
<div>
<label for="price">가격</label>
<input type="text" id="price" th:field="*{price}" class="form-control" placeholder="가격을 입력하세요">
</div>
<div>
<label for="quantity">수량</label>
<input type="text" id="quantity" th:field="*{quantity}" class="form-control" placeholder="수량을 입력하세요">
</div>
태그에
id항목은 인텔리제이에서 오류 띄워서 그냥 나뒀다. 없애도 상관 없다.

다음과 같이 name, value, id 필드가 자동으로 생성 되었다.
th:object="${item}":<form>에서 사용할 객체를 지정한다. 선택 변수 식(*{...})을 적용할 수 있다.th:field="*{itemName}"*{itmeName}은 선택 변수 식을 사용했는데,${item.itemName}과 같은 의미이다. 앞서th:object로item을 선택했기 때문에 선택 변수 식을 적용할 수 있다.th:object의 선택 범위는 현재 코드 상form내부까지만이다.th:field는name,value,id속성을 모두 자동으로 만들어 준다.id:th:field에서 지정한 변수 이름과 같다.id="itemName"name:th:field에서 지정한 변수 이름과 같다.name="itemName"value:th:field에서 지정한 변수의 값을 사용한다.value=""
수정 폼
1
2
3
4
5
6
@GetMapping("/{itemId}/edit")
public String editForm(@PathVariable Long itemId, Model model) {
Item item = itemRepository.findById(itemId);
model.addAttribute("item", item);
return "form/editForm";
}
다음과 같이 itemId를 넘겨 받아 item객체를 만들어서 폼에 전송한다.
기존 수정 폼 코드
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<form action="item.html" th:action method="post">
<div>
<label for="id">상품 ID</label>
<input type="text" id="id" name="id" class="form-control" value="1" th:value="${item.id}" readonly>
</div>
<div>
<label for="itemName">상품명</label>
<input type="text" id="itemName" name="itemName" class="form-control" value="상품A" th:value="${item.itemName}">
</div>
<div>
<label for="price">가격</label>
<input type="text" id="price" name="price" class="form-control" value="10000" th:value="${item.price}">
</div>
<div>
<label for="quantity">수량</label>
<input type="text" id="quantity" name="quantity" class="form-control" value="10" th:value="${item.quantity}">
</div>
th:object 사용 수정 폼
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<form action="item.html" th:action th:object="${item}" method="post">
<div>
<label for="id">상품 ID</label>
<input type="text" id="id" th:field="*{id}" class="form-control" readonly>
</div>
<div>
<label for="itemName">상품명</label>
<input type="text" id="itemName" th:field="*{itemName}" class="form-control" >
</div>
<div>
<label for="price">가격</label>
<input type="text" id="price" th:field="*{price}" class="form-control" >
</div>
<div>
<label for="quantity">수량</label>
<input type="text" id="quantity" th:field="*{quantity}" class="form-control">
</div>

자 name이랑 value 를 뺏는데 (id도 생략 가능), 잘 렌더링 되었다.
수정 폼은 앞서 설명한 내용과 같다. 수정 폼의 경우 id, name, value를 모두 신경 써야 했는데,
많은 부분이 th:field덕분에 자동으로 처리되는 것을 확인할 수 있다.
th:object, th:field 덕분에 폼을 개발할 때 약간의 편리함을 얻었다.
사실 이 기능의 위력은 검증(Validation)에서 나타난다고 한다.
요구 사항 추가
타임리프를 사용해서 폼에서 체크박스, 라디오 버튼, 셀렉트 박스를 편리하게 사용하는 방법을 학습해보자.
기존 상품 서비스에서 다음 요구 사항이 추가되었다.
- 판매 여부
- 판매 오픈 여부
- 체크 박스로 선택할 수 있다.
- 등록 지역
- 서울, 부산, 제주
- 체크 박스로 다중 선택 할 수 있다.
- 상품 종류
- 도서, 식품, 기타
- 라디오 버튼으로 하나만 선택할 수 있다.
- 배송 방식
- 빠른 배송
- 일반 배송
- 느린 배송
- 셀렉트 박스로 하나만 선택할 수 있다.

이제 클래스나 ENUM을 이용해서 만들어 보자.
ItemType - 상품 종류
hello.itemservice.domain.item.ItemType
1
2
3
4
5
6
7
8
9
public enum ItemType {
BOOK("도서"), FOOD("음식"), ETC("기타");
private final String description;
ItemType(String description) {
this.description = description;
}
}
상품 종류는 ENUM을 사용한다. 설명을 위해 description필드를 추가했다. (생성자)
배송 방식 - DeliveryCode
hello.itemservice.domain.item.DeliveryCode
1
2
3
4
5
6
7
8
9
10
11
12
/**
* FAST : 빠른 배송
* NORMAL : 일반 배송
* SLOW : 느린 배송
* */
@Data
@AllArgsConstructor
public class DeliveryCode {
private String code;
private String displayName;
}
배송 방식은 DeliveryCode라는 클래스를 사용한다. code는 FAST같은 시스템에서 전달하는 값이고, displayName은 빠른 배송 같은 고객에게 보여주는 값이다.
Item - 상품 (추가)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@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;
}
}
다음과 같이 판매 여부, 지역, 종류, 방식이 추가 되었다.
이제 다음 강의부터 이걸 사용해 보자.
댓글남기기