타임리프 - 스프링 통합

JeongHoHyun·2025년 2월 10일

Spring Boot

목록 보기
7/7

타임리프 스프링 통합

타임리프는 스프링 없이도 동작하지만, 스프링과 통합을 위하 다양한 기능을 편리하게 제공한다.

스프링 통합으로 추가되는 기능들

  • 스프링의 SpringEL 문법 통합
  • 편리한 폼 관리를 위한 추가 속성
    • th:object (기능 강화, 폼 커맨드 객체 선택)
    • th:field, th:errors, th:errorclass
  • 폼 컴포넌트 기능
    • checkbox, radio button, List 등을 편리하게 사용할 수 있는 기능 지원
  • 스프링의 메시지, 국제화 기능의 편리한 통합
  • 스프링의 검증, 오류 처리 통합
  • 스프링의 변환 서비스 통합 (Conversion Service)

설정 방법

build.gradle

implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'

📌 입력 폼 처리

  • 'th:object' : 커맨드 객체를 지정한다.
  • *{...} : 선택 변수 식이라고 한다. 'th:object'에서 선택한 객체에 접근한다.
  • th:field
    • HTML 태그의 id, name, value 속성을 자동으로 처리해준다.

📌 체크박스 - 단일

단순 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>
  • 체크박스를 체크하면 HTML Form에서 open=on 이라는 값이 넘어간다.
  • 스프링은 on이라는 문자를 true 타입으로 변환해준다. (스프링 타입 컨버터 동작)

⚠️ 주의 - 체크 박스 선택 X

  • HTML에서 체크박스를 선택하지 않고 폼을 전송하면 open이라는 필드 자체가 서버로 전송되지 않는다.
  • 이 문제를 해결히기위해 스프링 MVC는 약간의 트릭을 사용한다.
    • 히든필드를 하나 만들어서 체크박스의 이름 앞에 언더스코어(_)를 붙여서 전송하면 체크를 해제했다고 인식할 수 있다.
    • 따라서 체크를 해제한 경우 open은 전송되지않고 _open만 전송되는데, 이 경우 스프링 MVC는 체크를 해제했다고 판단한다.
<input type="hidden" name="_open" value="on"/>

체크 박스 체크

open=on&_open=on

  • 체크 박스를 체크하면 스프링 MVC가 open에 값이 있는 것을 확인하고 사용한다.
  • 이떄 _open은 무시한다.

체크 박스 미체크

_open=on

  • 체크 박스를 체크하지 않으면 스프링 MVC가 _open만 있는 것을 확인하고, open의 값이 체크되지 않았다고 인식한다.
  • 이 경우 서버에서 Boolean타입을 찍어보면 결과가 null이 아니라 false인 것을 확인할 수 있다.

⭐️ 타임리프 폼 기능

  • 타임리프가 제공하는 th:field 를 사용하면 별도의 히든 필드를 생성하지 않아도 위의 문제가 해결된다.
<!-- 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의 특별한 사용법

  • 여러곳의 폼에서 model.addAttribute(...)을 공통으로 사용해야하면 각각의 컨트롤러에서 반복해서 넣어주어야 한다.
  • 하지만 @ModelAttibute는 컨트롤러에 있는 별도의 모든 메서드의 model에 내용이 담기게 된다.
  • 이렇게 처리하지 않고 각각의 컨트롤러 메서드에서 모델에 직접 담아서 처리해도 된다.
@ModelAttribute("regions")
    public Map<String, String> regions() {
        Map<String, String> regions = new LinkedHashMap<>();
        regions.put("SEOUL", "서울");
        regions.put("BUSAN", "부산");
        regions.put("JEJU", "제주");
        return regions;
    }

멀티 체크박스 추가 예제

<!-- 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>

th:for="${#ids.prev('regions')}"

  • 멀티 체크박스는 같은 이름의 여러 체크 박스를 만들 수 있다.
  • 이렇게 반복해서 HTML 태그를 생성할 때, 생성된 HTML 태그 속성에서 name은 같아도 되지만, id는 모두 달라야한다!
  • 따라서 타임리프는 체크박스를 each루프 안에서 반복해서 만들 때 임의로 1, 2, 3 숫자를 뒤에 붙여 준다.
  • HTML의 'id'가 타임리프에 의해 동적으로 만들어지기 때문에 \<label for="id 값> 으로 label의 대상이 되는 id값을 임의로 지정하는 것은 곤란하다.
  • 타임리프는 '#ids.prev(...)', '#ids.next(...)'을 제공해서 동적으로 생성되는 id값을 사용할 수 있도록 한다.

📌 라디오 버튼

라디오 버튼 예제

<!-- 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>

타임리프에서 ENUM 직접 접근

<div th:each="type : ${T(hello.itemservice.domain.item.ItemType).values()}">
  • 위와 같이 접근하면 스프링EL문법으로 ENUM을 직접 사용할 수 있다.
  • ENUM에 values()를 호출하면 해당 ENUM의 모든 정보가 배열로 반환된다.
  • 하지만 패키지위 위치를 다 써줘야 하므로, 위치가 변경되면 오류가난다. 그래서 추천하지 않는다.

📌 셀렉트 박스

셀렉트 박스 예제

<!-- 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>
profile
Java Back-End 2022.11.01 💻~ing

0개의 댓글