스프링 통합으로 추가되는 기능
${@myBean.doSomething()} 처럼 스프링 빈 호출build.gradle에 다음 한줄을 넣어주면 Gradle은 타임리프와 관련된 라이브러리를 다운로드 받고, 스프링 부트는 앞서 설명한 타임리프와 관련된 설정용 스프링 빈을 자동으로 등록해준다.

th:object : 커맨드 객체를 지정한다.--> 이 속성은 폼 요소에서 사용할 객체를 지정하는 데에 쓰입니다.
예를 들어, 폼을 통해 사용자로부터 입력을 받을 때, 이 입력 값을 처리하기 위한 Java 객체를 Thymeleaf로 지정하는 데 사용됩니다.
*{...} : 선택 변수식이라고 한다.(th:object에서 선택한 객체에 접근할 수 있다th:field : HTML 태그의 id, name, value의 속성을 자동 처리해준다. 렌더링 전
<input type="text" th:field="*{itemName}"/>
렌더링 후
<input type="text" id="itemName" name="itemName" th:value="*{itemName}" />
상품 등록 폼
--> FormItemController
th:object를 적용하기 위해서는 해당 오브젝트 정보를 먼저 넘겨주어야 한다. 등록 폼이기 때문에, 데이터가 비어있는 빈 오브젝트 를 만들어서 뷰에 전달한다(model에 new Item()으로 빈 객체를 생성해서 넘겨준 것이다)addForm.html

th:object="${item}" : <form>에서 사용할 커맨드 객체를 지정한다. --> 그러면 선택 변수 식(*{...}를 사용할 수 있다)th:field="*{itemName}": *{itemName} 는 선택 변수 식을 사용했는데, --> ${item.itemName}과 같다.th:object로 item을 사용했기 때문에, 선택 변수식을 사용할 수 있다.id : th:field 에서 지정한 변수 이름과 같다. ( id="itemName" )
name : th:field 에서 지정한 변수 이름과 같다. ( name="itemName" )
value : th:field 에서 지정한 변수의 값을 사용한다. ( value="" )
상품 수정 폼
--> FormItemController

editForm.html

타임리프를 사용해서 폼에서 체크박스, 라디오 버튼, 셀렉트 박스를 편리하게 사용하는 방법을 학솝해보자
예시 이미지

ItemType(상품 종류) enum 생성
public enum ItemType {
BOOK("도서"), FOOD("음식"), ETC("기타");
private final String description;
ItemType(String description) {
this.description = description;
}
}
DeliveryCode(배송 방식) 클래스 생성
@Data
@AllArgsConstructor
public class DeliveryCode {
private String code;
private String displayName;
}
배송 방식은 DeliveryCode 라는 클래스를 사용한다.
--> code 는 FAST 와 같이 시스템에서 전달하는 값이고, displayName 은 빠른 배송 과 같은 고객에게 보여주는 값이다.
@Data: 롬복 애노테이션 중 하나로, 이 애노테이션을 클래스에 추가하면 롬복이 자동으로 toString(), equals(), hashCode(), getter 및 setter 메서드를 생성해준다.
@AllArgsConstructor: 롬복 애노테이션 중 하나로, 이 애노테이션을 클래스에 추가하면 모든 필드를 인자로 받는 생성자를 생성해준다. 이는 객체를 생성할 때 모든 필드를 한 번에 초기화하기 위해 사용된다.
Item 클래스 수정
private Boolean open; //판매여부(체크박스)
private List<String> regions; //등록 지역
private ItemType itemType; //상품 종류
private String deliveryCode; //배송 방식
단순 HTML 체크 박스(먼저 타임리프를 사용하지 않고 순수 HTML로 처리해볼 것이다)
addForm.html
<!--single checkbox-->
<div>판매 여부</div>
<div>
<div class="form-check">
<input type="checkbox" id="open" name="open" class="form-check-input">
<label for="open" class="form-check-label">판매 오픈</label>
</div>
</div>
FormItemController 클래스
log.info("item.open={}", item.getOpen());
@Slf4j 애노테이션을 추가했다. 이후 체크 박스를 체크한 후 실행을 시키면

정상적으로 실행이 되며, opne=on이라는 값이 넘어간다.
--> 스프링은 on 이라는 문자를 (Boolean 타입이라면) true 타입으로 변환해준다.
체크 박스를 체크하지 않은 후 실행을 시키면

다음과 같은 null값이 나온다.
왜 null값이 나온걸까?
_open 처럼 기존 체크박스 앞에 언더스코어(-)를 붙여서 전송하면, 체크박스가 해제했다고 인식을 할 수 있다.addForm.html
<input type="hidden" name="_open" value="on" />
히든 필드를 추가한 후 다시 실행시켜보자

_open=on : 체크박스를 체크하지 않으면 스프링 MVC가 _open만 있는 것을 확인하고, opne의 값이 체크 되지 않았다고 인식한다. 타임리프가 제공하는 폼 기능을 사용해보자
addForm.html
판매 여부 체크 박스의 기존 코드를 제거하고, 타임리프가 제공하는 체크 박스 코드로 변경해보자
<div>판매 여부</div>
<div>
<div class="form-check">
<input type="checkbox" id="open" name="open" th:field="*{open}" class="form-check-input">
<input type="hidden" name="_open" value="on" />
<label for="open" class="form-check-label">판매 오픈</label>
</div>
</div>
th:field="${item.open}"으로 작성해도 무관하다. 실행해보자


상품 상세에도 적용해보자 - item.html
<div>상세 여부</div>
<div class="form-check">
<input type="checkbox" id="open" th:field="${item.open}" class="form-check-input" disabled>
<label for="open" class="form-check-label">판매 오픈</label>
</div>
disabled를 사용해서 상품 상세에서는 체크박스가 선택되지 않도록 설정해두었다. 타임리프의 체크 확인
checked="checked"th:field 를 사용하면, 값이 true 인 경우 체크를 자동으로 처리해준다.상품 수정에도 적용해보자 - editForm.html
<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>
ItemRepository : update()코드도 수정하자
findItem.setOpen(updateParam.getOpen());
수정 처리가 되도록 update()코드 위에 추가해 주었다.


이렇게 수정이 가능해졌다
판매 여부를 체크하여 수정 후, 수정 페이지로 접근해보면, 판매 여부가 자동으로 선택된 것을 확인할 수 있고, 페이지 소스보기를 하면 checked="checked" 를 확인할 수 있다.
판매 여부를 체크 해제 하고 수정 후, 수정 페이지로 접근해보면, 판매 여부가 선택되지 않은 것을 확인할 수 있고, 페이지 소수보기를 하면 checked="checked" 가 없는 것을 확인할 수 있다.
체크박스를 멀티로 사용해서, 하나 이상을 체크할 수 있도록 해보자
등록지역 : 서울, 부산, 제주(체크박스로 다중 선택할 수 있다)
먼저, 위와 같이 등록 지역을 추가하려면(등록 폼, 상세 폼, 수정 폼) 모두 각 페이지에서 서울 부산 제주를 선택할 수 있도록 하기 위해 컨트롤러에서 데이터를 넘겨주어야 한다.
이러한 중복을 해결하기 위해 스프링은 특별한 기능을 제공해준다
FormItemController
@ModelAttribute("regions")
public Map<String,String> regions(){
Map<String, String> regions = new LinkedHashMap<>();
regions.put("SEOUL", "서울");
regions.put("BUSAN", "부산");
regions.put("JEJU", "제주");
return regions;
}
@ModelAttribute
model.addAttribute()을 사용해서 체크 박스를 구성하는데 데이터를 반복해서 넣어주어야 한다.
model.addAttribute("regions", regions); 가 FormItemController 컨트롤러의 각 메서드에 자동으로 적용된다고 보면 된다.addForm.html - 상품 등록 폼
<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>
name은 같아도 되지만, id 값은 모두 달라야 한다.th:field에 의해 타임리프가 체크박스 id를 자동 생성할 때, 루프가 수행되면서, 뒤에 인덱스(1,2,3...)을 자동으로 생성해준다. th:for="${#ids.prev('regions')}"<label for="id값">
으로 label 의 대상이 되는 id 값으로 임의로 지정하는 것은 곤란하다.
--> 타임리프는 ids.prev(...) , ids.next(...) 을 제공해서 동적으로 생성되는 id 값을 사용할 수 있도록 한다.
(ex. label for="regions1", label for="regions2", label for="regions3")
출력 결과(log로 출력)

서울과 부산을 선택했을 때 : item.regions=[SEOUL,BUSAN]

아무것도 선택하지 않았을 떄 : item.regions=[]

로그를 보면, 빈 배열이 들어간 것을 볼 수 있다.
_regions는 앞서 설명한 기능이다. 웹 브라우저에서 체크를 하나도 하지 않았을 때, 클라이언트가 서버에 아무런 데이터를 보내지 않는 것을 방지한다.
item.html - 상세 페이지와 editForm.html도 동일하게 추가해주면 된다.
타임리프의 체크 확인
checked="checked"
멀티 체크 박스에서 등록 지역을 선택해서 저장하면, 조회시에 checked 속성이 추가된 것을 확인할 수 있다.
타임리프는 th:field 에 지정한 값과 th:value 의 값을 비교해서 체크를 자동으로 처리해준다.
라디오 버튼은 여러 선택지 중 하나를 선택할 수 있다. ENUM을 사용해서 적용해보자
상품 종류
FormItemController
@ModelAttribute("itemTypes")
public ItemType[] itemTypes(){
ItemType[] values = ItemType.values();
return values;
}
itemTypes를 (등록, 조회, 수정)에서 모두 사용해야하므로 @ModelAttribute를 이용해보자ItemType.values()를 사용하면 해당 ENUM의 모든 정보를 배열로 반환한다.addForm.html - 상품 등록 폼
<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>
th:value="${type.name()}" : Enum의 name()에 해당, enum의 이름을 그대로 문자로 반환한다.출력 결과

item.html, editForm.html 둘다 같은 코드를 추가해주면 된다
셀렉트 박스는 여러 선택지 중에 하나를 선택할 수 있다
배송 방식
ForItemController 코드 추가
@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;
}
@ModelAttribute 애노테이션 사용addForm.html
<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>
item.html, editForm.html도 모두 배송 방식을 추가해 주었다.
실행 결과

selected="selected" 처리를 해주었다th:field에 지정된 값과 option에 있는 th:value 의 값을 비교해서 selected를 자동으로 처리해준다.