관리 메뉴

나구리의 개발공부기록

타임리프 - 스프링 통합과 폼, 프로젝트 설정, 타임리프 스프링 통합, 입력 폼 처리, 요구사항 추가 본문

인프런 - 스프링 완전정복 코스 로드맵/스프링 MVC 2편 - 백엔드 웹 개발 핵심 기술

타임리프 - 스프링 통합과 폼, 프로젝트 설정, 타임리프 스프링 통합, 입력 폼 처리, 요구사항 추가

소소한나구리 2024. 5. 27. 23:08

  출처 : 인프런 - 스프링 MVC 2편 - 백엔드 웹 개발 핵심 기술 (유료) / 김영한님  
  유료 강의이므로 정리에 초점을 두고 코드는 일부만 인용  

https://inf.run/GMo43


1. 프로젝트 설정

1) 프로젝트 설정

  • 강의에서 제공하는 프로젝트가 있으나, 버전업이 많이 된 관계로 새로 프로젝트를 생성

(1) 프로젝트

  • Gradle
  • Java 17
  • Spring Boot 3.3.0

(2) Metadata

  • Group: hello
  • Artifact: item-service
  • Package name: hello.itemservice
  • Packaging: Jar

(3) Dependencies

  • Spring Web
  • Thymeleaf
  • Lombok

(4) 추가 작업

  • 기존 프로젝트에 있는 클래스 파일들은 가져온 상태에서 진행
  • settings.gradle의 프로젝트 이름을 form으로 변경
  • hello하위 패키지 이름을 itemservice로 변경 후 Run/Debug 세팅의 경로도 hello.itemservice.ItemServiceApplication으로 변경

2. 타임리프 스프링 통합

1) 타임리프와 스프링

(1) 매뉴얼

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

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

(3) 설정 방법

(4) Build.gradle

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

3. 입력 폼 처리

1) 입력 폼 기능 적용

(1) 렌더링 전, 후 비교

  • th:object: 커맨드 객체를 지정
  • *{...}: 선택 변수식, th:object 에서 선택한 객체에 접근
  • th:field: HTML 태그의 id, name, value 속성을 자동으로 처리해 줌
<!-- 렌더링 전 -->
<input type="text" th:field="*{itemName}" />

<!-- 렌더링 후 -->
<input type="text" id="itemName" name="itemName" th:value="*{itemName}" />

2) 등록 폼

(2) FormItemController - addForm()수정

  • th:object를 적용하려면 먼저 오브젝트 정보를 넘겨주어야 함
  • 등록 폼이기 때문에 데이터가 비어있는 빈 오브젝트를 만들어서 뷰에 전달
// th:object를 적용하기 위해 먼저 오브젝트 정보를 넘겨주기 위해 model.addAttribute로 빈 객체를 생성하여 정보를 넘겨줌
@GetMapping("/add")
public String addForm(Model model) {
    model.addAttribute("item", new Item());
    return "form/addForm";
}

 

(2) addForm.html 수정

  • th:object="${item}": <form>태그 안의 범위에서 사용할 객체를 지정하고 선택 변수 식 *{...}을 적용할 수 있음
  • th:field="*{itemName}"
    - 선택 변수식은 &{item.itemName}과 동일하며 앞에서 th:object로 item을 선택했기 때문에 선택 변수식을 적용 할 수 있음
    - th:field는 id, name, value 속성을 모두 자동으로 만들어 줌
    - 즉, id 속성을 제거해도 th:field가 자동으로 만들어 주지만 IDE에서 에러가 난 것처럼 표시가 되어 해당 에러표시가 보기 싫으면 예제처럼 id를 유지해도 됨(선택 사항)
<!--    th:object="${item}" 추가-->
<form action="item.html" th:action th:object="${item}" method="post">
    <div>
        <label for="itemName">상품명</label>
<!--    th:object="${item}" 를 등록하여 id, name 을 모두 제거하고 th:field="*{label명}" 폼으로 id와 name을 자동으로 생성
        *{itemName}은 ${item.itemName}과 동일하며 앞서 th:object로 item을 선택했기 때문에 변수식 적용이 가능
        id도 삭제 가능하지만, IDE에서 에러가 발생하는 것처럼 보여서 그냥 유지 -->
<!--    <input type="text" id="itemName" name="itemName" class="form-control" placeholder="이름을 입력하세요">-->
        <input type="text" id="itemName" th:field="*{itemName}" class="form-control" placeholder="이름을 입력하세요">
    </div>
    <div>
        <label for="price">가격</label>
<!--    <input type="text" id="price" name="price" class="form-control" placeholder="가격을 입력하세요">-->
        <input type="text" id="price" th:field="*{price}" class="form-control" placeholder="가격을 입력하세요">
    </div>
    <div>
        <label for="quantity">수량</label>
<!--    <input type="text" id="quantity" name="quantity" class="form-control" placeholder="수량을 입력하세요">-->
        <input type="text" id="quantity" th:field="*{quantity}" class="form-control" placeholder="수량을 입력하세요">
    </div>

3) 수정 폼

  • controller의 코드는 수정할게 없으므로 생략(원래의 코드에서 Model을 넘기고 있음)

(1) editForm.html 수정

  • 길었던 코드가 id, name, value를 자동으로 생성해주는 th:field="*{...}"덕에 매우 간결해진 것을 확인할 수 있음
<!--    th:object="${item}" 추가-->
<form action="item.html" th:action th:object="${item}" method="post">
    <div>
        <label for="id">상품 ID</label>
<!-     addForm의 내용처럼 id, name value를 지우고 th:field="${label명}" 폼으로 대체할 수 있음-->
<!--    <input type="text" id="id" name="id" class="form-control" value="1" th:value="${item.id}" readonly>-->
        <input type="text" id="id" th:field="*{id}" class="form-control" readonly>
    </div>
    <div>
        <label for="itemName">상품명</label>
<!--    <input type="text" id="itemName" name="itemName" class="form-control" value="상품A" th:value="${item.itemName}">-->
        <input type="text" id="itemName" th:field="*{itemName}" class="form-control">
    </div>
    <div>
        <label for="price">가격</label>
<!--    <input type="text" id="price" name="price" class="form-control" value="10000" th:value="${item.price}">-->
        <input type="text" id="price" th:field="*{price}" class="form-control">
    </div>
    <div>
        <label for="quantity">수량</label>
<!--    <input type="text" id="quantity" name="quantity" class="form-control" value="10" th:value="${item.quantity}">-->
        <input type="text" id="quantity" th:field="*{quantity}" class="form-control">
    </div>

4. 요구사항 추가

1) 추가된 요구 사항

(1) 기존 상품 서비스에 요구사항이 추가 되었다고 가정

  • 판매 여부
    - 판매 오픈 여부
    - 체크 박스로 선택
  • 등록 지역
    - 서울, 부산, 제주
    - 체크 박스로 다중 선택
  • 상품 종류
    - 도서, 식품, 기타
    - 라디오 버튼으로 하나만 선택
  • 배송 방식
    - 빠른배송, 일반 배송, 느린 배송
    - 셀렉트 박스로 하나만 선택

2) 기능 추가

(1) ItemType - 상품종류

  • domain.item패키지에 Enum으로 생성
  • 설명을 위해 description필드를 추가
package hello.itemservice.domain.item;

public enum ItemType {

    BOOK("도서"), FOOD("음식"), ETC("기타");

    private final String description;     // 설명을 위한 description 필드 추가

    ItemType(String description) {
        this.description = description;
    }
    
  	public String getDescription() {
        return description;
    }    
}

 

(2) DeliveryCode - 배송방식

  • Class로 생성
  • code는 FAST, NORMAL 처럼 시스템에 전달하는 값
  • displayName은 빠른 배송, 일반 배송 처럼 고객에게 보여주는 값
package hello.itemservice.domain.item;

/**
 * FAST: 빠른 배송
 * NORMAL: 일반 배송
 * SLOW: 느린 배송
 */

@Data
@AllArgsConstructor
public class DeliveryCode {

    private String code;           // 시스템에서 전달하는 값
    private String displayName;    // 고객에게 보여주는 값

}

 

(3) Item 수정

package hello.itemservice.domain.item;

@Data
public class Item {
    // ... 기존 코드 생략
    
    private Boolean open;           // 판매 여부
    private List<String> regions;   // 등록 지역
    private ItemType itemType;      // 상품 종류
    private String deliveryCode;    // 배송 방식
    
    // ... 기존 코드 생략
}