[ Spring ]/SpringMVC 2편

[Spring] 타임리프 - 스프링 통합과 폼

쿠릉쿠릉 쾅쾅 2022. 3. 5. 04:00
728x90

 

 

 

타임리프 스프링 통합

https://www.thymeleaf.org/doc/tutorials/3.0/thymeleafspring.html

 

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.doSomethid()}  처럼 스프링 빈 호출 지원
    • 편리한 폼 관리를 위한 추가 속성
      •  th:object  (기능 강화, 폼 커맨드 객체 선택)
        • 커맨드 객체란 별도의 Class를 만들어서 값을 binding 받을 객체를 의미한다.
      •  th:field  th:errors  th:errorclass 
  • 폼 컴포넌트 기능
    • checkbox, radio button, List 등을 편리하게 사용할 수 있는 기능을 지원한다.
  • 스프링 메시지, 국제화 기능의 편리한 통합
  • 스프링 검증, 오류 처리 통합
  • 스프링 변환 서비스 통합 (ConversionService)

 

 

🔍 스프링 부트는 설정을 자동화로 해준다.

implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
  •  build.gradle  에 한 줄을 넣어주면 된다.
  • 그러면 타임리프와 관련된 설정용 스프링 빈을 자동으로 등록해준다.

 

💡 타임리프 설정 변경 및 추가

https://docs.spring.io/spring-boot/docs/current/reference/html/application-properties.html#appendix.application-properties.templating

 

Common Application Properties

 

docs.spring.io

  • 스프링 부트가 제공하는 타임리프 설정 페이지다.
  • 타임리프 관련 설정은  application.properties  에 추가해 설정 및 변경이 가능하다.

 

 

 

입력 폼 처리

  • 타임리프 속성을 사용하여 입력 폼을 효율적으로 쓸 수 있다.

 

🔍 사용 방법

  •  th:object 
    • 커맨드 객체를 지정한다.
  •  *{...} 
    • 선택 변수식이다.
    •  th:object  에서 선택한 객체에 접근한다.
    • 참고)  *{itemName} 은  ${item.itemName} 과 같다.
  •  th:field 
    • HTML 태그의  id  name  value  속성을 자동으로 속성을 모두 자동으로 만들어준다.
    •  id 는  th:field 에서 지정한 변수 이름과 같다.
      • 예)  id="itemName" 
    •  name 은  th:field 에서 지정한 변수 이름과 같다.
      • 예)  name="itemName" 
    •  value 는  th:field 에서 지정한 변수의 값을 사용한다.
      • 예)  value="${itemName}" 

 

 

 

💡 th:object 사용 예

📌 컨트롤러

@GetMapping("/add")
public String addForm(Model model) {
    model.addAttribute("item", new Item());
    return "form/addForm";
}
  •  th:object  를 사용하기 위해선 컨트롤러가 해당 오브젝트 정보를 뷰에게 넘겨주어야 한다.

📌 HTML

<form action="item.html" th:action th:object="${item}" method="post">
  • 컨트롤러의 모델을 통해 데이터를 전달 받는다.
  •  th:object="${item}"  을 통해 커맨드 객체인 item 객체를 넘겨받았다. 이것을 프로퍼티로 사용하면 된다.

 

💡 th:field 사용 예

  • 사용 예) 상위 태그에 itemName 프로퍼티를 갖는 object를  th:object  로 선언해준 상태다.

✔ 랜더링 전

<input type="text" th:field="*{itemName}"/>

✔ 랜더링 후

<input type="text" id="itemName", name="itemName", th:value="*{itemName}" />
  • id는 th:field에서 지정한 변수 이름과 같다.

 

 


 

 

단일 체크 박스 1

 

🔍 HTML checkbox 주의

<input type="checkbox" id="open" name="open" class="form-check-input">
  • HTML checkbox는 check가 되지 않는다면 클라이언트가 서버에게 값을 보내지 않는다.
  • 즉,  open  이라는 프로퍼티가  false  가 아니라 아예 전송을 하지 않았기 때문에 서버에서  null  값으로 존재한다.
  • 만약에 HTML checkbox를 check 한다면  on  이라는 값이 서버로 전달되는데, 이 때 스프링 타입 컨버터에 의해서    true  로 변환해준다.
  • 이러한 특징은 수정의 경우 문제가 될 수 있다. 사용자가 정보를 수정할 때 체크를 한 상태에서 체크를 해제했을 때 아무 값도 넘어가지 않기 때문에 서버는 값이 변경되었는지 안되어있는지 알 수가 없다.

 

🔍 히든 필드를 사용하여 해결

<input type="checkbox" id="open" name="open" class="form-check-input">
<input type="hidden" name="_open" value="on"/>  <!-- 히든필드 추가 -->
  • 스프링은 이런 문제를 해결하기 위해 히든 필드를 지원한다.
  • 히든 필드를 작성할 때 name 속성명을 기존 name 속성명 앞에  를 추가해줘야 한다.
  • 이렇게 작성하면 체크박스를 체크하지 않고 서버로 값을 넘길 때 서버에서  null  이 아니라 명확하게  false  값을 받을 수 있다.

 

 


 

 

단일 체크 박스 2

  • 타임리프 기능을 사용하여 히든 필드를 더 간편하게 사용할 수 있다.
  •  HTML checkbox에서  th:field  속성을 사용하면  id, name, value, hidden field, checked 속성까지 자동으로 설정해준다. 

 

🔍 th:field를 사용하여 해결

✔  히든 필드 사용

<input type="checkbox" id="open" name="open" class="form-check-input">
<input type="hidden" name="_open" value="on"/>  <!-- 히든필드 추가 -->

 

✔ 히든필드 없이  th:field  사용 (권장)

<input type="checkbox" id="open" th:field="${item.open}" class="form-check-input">
  • 만약에  th:field  를 사용할 때  th:object="${item}"  를 같이 사용한다면  ${item.open}  을  *{open} 으로 바꿀 수 있다.

 

 


 

 

멀티 체크 박스

 

🔍  @ModelAttribute 의 또 다른 사용법

@ModelAttribute("data")
public String data() {
    return "testData";
}
  • 요청 파라미터를 조회할 때 쓰이는 @ModelAttribute 애노테이션을 파라미터 레벨이 아닌 메서드 레벨에 적용시키면 @ModelAttribute 애노테이션의 name의 속성값이 Model 객체의 key로 값이고, 메서드의 반환 값이 Model 객체의 value 값으로 담기게 된다.
    • 예)  model("data", "testData"); 
  •  @ModelAttribute  애노테이션이 메서드 레벨에 적용되면 메서드 반환값이 해당 메서드를 가지고 있는 클래스인 컨트롤러의 모든 Model 객체에 담기게 된다.
  •  @ModelAttirbute  애노테이션이 메서드 레벨에 적용되면 컨트롤러가 호출될 때 마다 해당 메서드도 매번 같이 호출된다.

 

🔍 멀티 체크 박스 사용법

<!-- multi checkbox -->
<div>
    <div>등록 지역</div>
    <div th:each="region : ${regions}" class="form-check form-check-inline">
        <input type="checkbox" th:field="${item.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>
  • 컨트롤러에게 모델을 통해 반복 가능한 요소인 regions 값을 받는다.
  •  th:each 를 통해 regions 값들이 반복하면서 한 개씩 순차적으로 regions 값에 들어가게 된다.
  •  th:field 속성을 사용하여 th:value에 들어가는 region.key값과 비교해 값이 포함되어 있으면 HTML checked 속성이 추가된다.
  •  th:for="${#ids.prev('regions')}" 
    • 멀티 체크 박스는 같은 이름의 여러 체크박스가 생성된다.
    • name 속성명은 모두 같아도 되지만, id 명은 유일해야하기에 체크박스마다 달라야한다.
    • 따라서 타임리프에서는 편의목적으로 ids라는 프로퍼티와 메서드를 제공한다. ids는 반복하는 요소의 인덱스로 1, 2, 3 처럼 숫자다.
      •  ids.prev(...)  : 아이디 앞에 인수값을 붙여준다.
        • 예) data1, data2, data3, .....
      •  ids.next(...)  : 아이디 뒤에 인수값을 붙여준다.
        • 예) 1data, 2data, 3data, .....
      • 해당 메서드들을 통해 HTML id 값을 동적으로 만들 수 있다.

 

🧷 정리

  • 여러 개의 체크박스가 필요한 경우  th:each  를 이용해 요소를 반복 생성할 수 있다.
  •  th:field 와  th:value 를 같이 작성하면 타임리프가 자동으로 두 값을 비교해 checked 설정을 해준다.
  • 반복 생성되는 태그의 아이디를 모두 다르게 생성해줘야하기 때문에 타임리프에서 ids라는 편의 프로퍼티와 관련 메서드를 제공한다.

 

 


 

 

라디오 버튼

  • 라디오 버튼은 자바 Enum을 이용해서 만들 수 있다.

 

🔍 사용법

  •  th:each  속성을 이용해서 여러 라디오 버튼을 만든다.
  • 예제 코드
<!-- radio button -->
<div th:object="${item}">
    <div>상품 종류</div>
    <div th:each="type : ${반복요소}" class="form-check form-check-inline">
        <input type="radio" th:field="*{라디오요소}" th:value="${type.비교값}"
               class="form-check-input">
        <label th:for="${#ids.prev('itemType')}" th:text="${type.description}"
               class="form-check-label">
            BOOK
        </label>
    </div>
</div>
  • 반복요소의 count 만큼 내부 태그가 반복되어 생성된다.
  •  th:field 에 들어간  th:object 에서 선언한 item의 요소는  th:value 의 값과 비교해 checked가 자동으로 표현된다.
  • 멀티 체크박스와 동일하게  th:each  속성 내부에서 ids를 이용해 인덱스 접근 및 인수로 넘겨준 값과 문자열을 결합하여 고유한 아이디를 만들 수 있다.

 

더보기
<!-- radio button -->
<div th:object="${item}">
    <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를 통해 Enum 직접 사용하기

📌 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;
    }
}

 

📌 @ModelAttribute로 Enum 값 넣기

@ModelAttribute("itemTypes")
public ItemType[] itemTypes() {
    return ItemType.values();
}
  •  @ModelAttribute 를 메서드 레벨에 적용하여 해당 컨트롤러의 모든 모델에 Enum 값을 넣어줬다.
  • Enum 클래스는  values()  메서드를 사용하면 Enum의 모든 정보가 배열로 반환된다.

 

🔍 타임리프에서 Enum 직접 접근하기

<div th:each="type : ${T(hello.itemservice.domain.item.ItemType).values()}">
  • ${T(FQCN)}
    • FQCN : Fully Qualified Class Name
    • FQCN 자리에 해당 Enum 파일의 패키지명까지 다 적어줘야한다.
  •  ${T(hello.itemservice.domain.item.ItemType).values()}  처럼 스프링EL 문법으로 타임리프로 Enum을 직접 사용할 수 있다.
  • Enum 패키지 경로가 변경될 때 컴파일러가 타임리프까지 컴파일 오류를 잡아줄 수 없으므로 권장하지 않는다.

 

🧷 정리

  • 라디오버튼은 아무것도 선택하지 않을 경우 아무 값도 넘어가지 않아 Null이 된다.
  • 체크박스와 다르게 별도의 히든필드가 자동으로 생성되지 않는다.
    • 라디오버튼은 선택이 한 번 되면 항상 하나를 선택하도록 되어있어 히든필드가 필요하지 않다.

 

 


 

 

셀렉트 박스

 

🔍 사용법

  • 반복요소가 있는 셀렉트박스 역시  th:each  로 요소를 반복한다.
  • 수정페이지에서 기본으로 선택되어야 하는 값이 있다면  th:field  요소를 이용해 가능하다.
  •  select 태그 에 선언된  th:field  속성과  option 태그 에서 선언된  th:value 를 타임리프가 비교해 일치할 경우 자동으로 seleted 속성을 추가한다.

 

📌 Item 객체

public class Item {
    
    private String deliveryCode;
    ...

}

 

📌 DeliveryCode (배송상태)

/**
 * FAST : 빠른 배송
 * NORMAL : 일반 배송
 * SLOW : 느린 배송
 */
@Data
@AllArgsConstructor
public class DeliveryCode {

    private String code;
    private String displayName;
}
  • 배송상태를 나타내는 경우 지금처럼 클래스로 만들어도 되고 Enum으로 만들어도 된다.

📌 컨트롤러

@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가 메서드 레벨에 사용되어서 컨트롤러가 호출 될 때 마다 deliveryCodes() 메서드가 호출된다. 그래서 deliveryCodes 객체도 계속 생성된다.
  • 예제라서 이렇게 단순히 했다. 실무에서는 deliveryCodes를 미리 static으로 생성하고 생성된 것을 재사용하는 것이 더 효율적이다.

📌 HTML

<!-- SELECT -->
<div>
    <div>배송 방식</div>
    <select th:field="${item.deliveryCode}" class="form-select">
        <option value="">==배송 방식 선택==</option>
        <option th:each="deliveryCode : ${deliveryCodes}" th:value="${deliveryCode.code}"
                th:text="${deliveryCode.displayName}">FAST</option>
    </select>
</div>

 

🔍 결과

 

 


👀 참고자료

https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-mvc-2/dashboard

 

스프링 MVC 2편 - 백엔드 웹 개발 활용 기술 - 인프런 | 강의

웹 애플리케이션 개발에 필요한 모든 웹 기술을 기초부터 이해하고, 완성할 수 있습니다. MVC 2편에서는 MVC 1편의 핵심 원리와 구조 위에 실무 웹 개발에 필요한 모든 활용 기술들을 학습할 수 있

www.inflearn.com

 

https://catsbi.oopy.io/81398571-33b8-471f-8191-ca7415b86326

 

2. 타임리프 - 스프링 통합과 폼

목차

catsbi.oopy.io

 

 

728x90