일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
- 스프링MVC
- 로그인
- Spring Data JPA
- 벌크 연산
- 값 타입 컬렉션
- 기본문법
- Bean Validation
- 김영한
- 불변 객체
- 실무활용
- API 개발 고급
- 프로젝트 환경설정
- JPQL
- 예제 도메인 모델
- JPA 활용 2
- 검증 애노테이션
- QueryDSL
- 일론머스크
- 컬렉션 조회 최적화
- JPA 활용2
- 임베디드 타입
- 타임리프 문법
- 스프링
- 트위터
- 스프링 데이터 JPA
- jpa 활용
- 스프링 mvc
- 타임리프
- JPA
- 페이징
- Today
- Total
RE-Heat 개발자 일지
스프링 MVC 2편 - [2] 스프링 통합과 폼 본문
https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-mvc-2/dashboard
인프런 김영한 님의 스프링 MVC 2편 강의를 토대로 정리한 내용입니다.
[1] 타임리프 스프링 통합
■ 스프링 통합으로 추가되는 기능들
- 스프링의 SpringEL 문법 통합
- ${@myBean.doSomething()}처럼 스프링 빈 호출 지원
- 편리한 폼 관리를 위한 추가 속성
th:object (기능 강화, 폼 커맨드 객체 선택)
th:field, th:errors, th:errorclass
- 폼 컴포넌트 기능
checkbox, radio button, List 등을 편리하게 사용할 수 있는 기능 지원
- 스프링의 메시지, 국제화 기능의 편리한 통합
- 스프링의 검증, 오류 처리 통합
- 스프링의 변환 서비스 통합(ConversionService)
■ 설정방법
타임리프 템플릿 엔진을 스프링 빈에 등록하고 타임리프용 뷰 리졸버를 따로 등록해야 하지만, 스프링 부트는 이런 부분을 자동으로 등록해 준다.
[2] 입력 폼 처리
■ 입력 폼 처리
th:object 커맨드 객체를 지정한다.
*{...} : 선택 변수 식. th:object에서 선택한 객체에 접근한다.
th:filed
HTML 태그의 id, name, value 속성을 자동으로 처리해 준다.
FormItemController - addform
@GetMapping("/add")
public String addForm(Model model) {
model.addAttribute("item", new Item());
return "form/addForm";
}
빈 item 객체를 model에 담아 넘긴다.
item.html
<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>
① th:object="${item}" : form에서 사용할 객체 지정
② th:filed="*{item}"
변수식 *{itemName}은 ${item.itemName}은 과 같다. th:object로 item을 지정해 줬으므로 선택 변수식을 적용할 수 있다.
실행화면
name="itemName"을 지워도 th:field="*{itemName}"값을 적용하면 자동으로 name값을 준다. id나 value도 자동으로 주어진다.
추가로 만일 th:filed에 th:field="*{itemNamexxxx}" 같이 값을 잘못 입력하면 오류 메시지가 떠서 개발할 때 편리하다.
IntelliJ 유료 버전은 오류 메시지를 더 자세하게 제공한다.
[3] 요구사항 추가
타임리프를 사용해 폼에서 체크박스·라디오 버튼·셀렉트 박스를 사용하는 방법을 학습하자.
이 챕터에선 그 전에 판매여부, 등록지역, 상품 종류 등 요구사항을 추가하고 이에 맞는 상품종류, 배송방식, 상품 클래스를 추가한다.
예시 이미지
ItemType - 상품종류
public enum ItemType {
BOOK("도서"), FOOD("음식"), ETC("기타");
private final String description;
ItemType(String description) {
this.description = description;
}
}
참고] 데이터 중 한정된 값만 갖는 데이터 타입이 열거 타입(enumeration type)이다.
ex) 월화수목금토일, 봄·여름·가을·겨울 등
DeliveryCode - 배송 방식
/*
* FAST:빠른배송
* NORMAL:일반배송
* SLOW:느린배송
* */
@Data
@AllArgsConstructor
public class DeliveryCode {
private String code; //시스템 이름 FAST
private String displayName; //빠른배송
}
Item - 상품 Data
@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;
}
}
참고] 롬복의 @Data는 @Getter/@Setter, @ToString, @EqualsAndHashCode @RequiredArgsConstructor 등을 합친 종합 선물세트.
[4] 체크 박스 - 단일 1
addForm.html
<hr class="my-4">
<!-- 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 - addItem 일부 발췌
@PostMapping("/add")
public String addItem(@ModelAttribute Item item, RedirectAttributes redirectAttributes) {
log.info("item.open={}", item.getOpen());
FormItemController 위에 @Slf4j를 추가한 뒤 로깅 작성.
실행로그
체크박스 선택 : item.open=true
체크박스 선택 X : item.open=null
HTML Form에서 체크박스를 체크하지 않으면 open값 자체가 넘어오지 않는다는 것을 확인할 수 있다.
■ HTML checkbox의 단점
HTML 체크박스는 선택이 되지 않으면 서버로 값 자체를 보내지 않는다. 그래서 사용자가 의도적으로 체크박스를 해제해도 값이 넘어온 지 판단할 수 없다는 게 문제점이다.
■ 해결책 - 히든 필드
스프링 MVC는 name="_open"처럼 기존 체크박스 이름에 _를 붙여서 전송하면 체크를 해제했다고 인식한다.
addForm.html - 히든 필드 추가
<!-- single checkbox -->
<div>판매 여부</div>
<div>
<div class="form-check">
<input type="checkbox" id="open" name="open" class="form-check-input">
<input type="hidden" name="_open" value="on"/> <!-- 히든 필드 추가 -->
<label for="open" class="form-check-label">판매 오픈</label>
</div>
</div>
체크 박스를 체크하면 open=on&_open=on
체크 박스를 해제하면 _open=on 만 전송돼 구분 가능
체크 박스 해제 시 html FormData에선 _open:on만 넘어가는 것을 확인할 수 있으며, 실행로그를 살펴보면 item.open은 null이 아닌 false인 것을 알 수 있다.
[5] 체크 박스 - 단일2
모든 input에 히든 필드를 일일이 추가하는 것은 상당히 번거로운 일이다. 그래서 타임리프는 이를 자동으로 처리하는 기능을 제공한다.
addForm.html - 타임리프 th:filed 추가
<!-- 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>
addForm.html - 렌더링 후
자동으로 hidden 필드가 추가된 것을 확인할 수 있다.
■ 타임리프의 체크 확인
html에선 체크 박스를 체크하면 checked="checked" 속성이 추가되지만, 체크박스를 해제하면 아예 생략돼 의도적으로 체크를 해제(수정)했는지 판별하기 어렵다. 그래서 타임리프에선 th:field를 사용하면 값이 true일 때 checked를 자동으로 처리해 주는 기능을 제공한다.
[6] 체크 박스 - 멀티
예시
■ @ModelAttribute의 특별한 사용법
위 사항을 충족하려면 각각의 컨트롤러에 model.attribute(...)를 사용해 체크박스를 구성하는 데이터를 반복해서 넣어주어야 한다.
중복을 방지하기 위해 스프링 MVC는 @ModelAttribute를 제공한다.
아래와 같은 코드를 추가하면 해당 컨트롤러를 요청할 때마다 regions에서 반환한 값이 자동으로 모델에 담기게 된다.
@ModelAttribute("regions")
public Map<String, String> regions(){
Map<String, String> regions = new LinkedHashMap<>(); //Hashmap을 쓰면 순서가 보장이 안되어서 LinkedHashMap으로 함.
regions.put("SEOUL", "서울");
regions.put("BUSAN", "부산");
regions.put("JEJU", "제주");
return regions;
}
참고] 이 값이 동적으로 변하지 않는다면 static 메소드화 해서 쓰는 게 바람직하나, @ModelAttribute를 쓴다고 성능이 유의미하게 저하되진 않는다.
addForm.html - multi checkbox 추가
<!-- 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')}"
th:each로 같은 이름(name)을 가진 여러 체크 박스를 만들 수 있으나, HTML 태그 속성에서 id는 중복되어선 안 된다. 그래서 타임리프는 each 루프 안에서 체크 박스를 반복해 만들 때 임의로 1, 2, 3 순서로 숫자를 붙여준다.
① id값에 regions1, regions2 식으로 숫자가 추가된 것을 확인할 수 있다.
② th:field로 히든 필드에 _regions가 전부 들어가는 것도 확인할 수 있다. 히든 필드가 체크박스 숫자만큼 생성될 필요는 없지만, 문제 되지 않으므로 무시하도록 하자.
실행화면 - 서울·제주 체크
[7] 라디오 버튼
라디오 버튼은 여러 선택지 중에 하나를 선택할 때 사용할 수 있다.
FormItemController - itemTypes()
@ModelAttribute("itemTypes")
public ItemType[] itemTypes() {
ItemType[] values = ItemType.values();
return values;
}
ItemType.values()로 Enum의 모든 정보를 배열로 반환
참고] Enum의 사용법
① values() : 열거된 모든 원소를 배열에 담아 순서대로 리턴
② ordinal() : 원소에 열거된 순서를 정수 값으로 리턴
ex) ItemType it = ItemType.BOOK;
System.out.println(it.ordinal()); => 결과값 : 1 (순서는 도서, 음식, 기타 순)
③ valueOf() : 매개변수로 주어진 String과 열거형에 일치하는 이름을 갖는 원소를 리턴
ex) ItemType it = ItemType.valueOf("BOOK")
System.out.println(it); => 결과값 : 1 (순서는 도서, 음식, 기타 순)
addForm.html - radio button
<!-- radio button -->
<div>
<div>상품 종류</div>
<div th:each="type : ${itemTypes}" class="form-check form-check-inline">
<input type="radio" th:field="${item.itemType}" th:value="${type.name()}" class="form-check-input" disabled>
<label th:for="${#ids.prev('itemType')}" th:text="${type.description}" class="form-check-label">
BOOK
</label>
</div>
</div>
① th:value = "${type.name()}" => BOOK, FOOD, ETC
② th:text= "${type.description}" => 도서, 음식, 기타
실행화면 - 소스보기
라디오 버튼은 한 번 체크하면 null값을 보낼 수 없다. 따라서 체크 박스와는 달리 별도의 히든 필드가 필요 없다.
■ 타임리프에서 ENUM 직접 접근
<!-- radio button -->
<div>
<div>상품 종류</div>
<div th:each="type : ${T(hello.itemservice.domain.item.ItemType).values()}"
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>
<div th:each="type : ${T(hello.itemservice.domain.item.ItemType).values()}">
스프링 EL문법으로 ENUM을 직접 사용할 수 있다. ENUM에 values()를 호출하면 해당 ENUM의 모든 정보가 배열로 반환된다. 하지만 이렇게 사용하면 ENUM의 패키지 위치가 변경되거나 할 때 자바 컴파일러가 타임리프 컴파일 오류까지 잡기 힘드므로 추천하진 않는다.
[8] 셀렉트 박스
셀렉트 박스는 여러 선택지 중 하나를 선택할 때 사용할 수 있다.
FormItemController - deliveryCodes()
@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;
}
addForm.html - SELECT
<!-- 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>
페이지 소스보기
타임리프가 옵션값을 차례대로 넣어주고, 느린 배송을 선택하면 selected="selected"까지 넣어주고 유지해 주는 것을 확인할 수 있다.
'백엔드 > 스프링' 카테고리의 다른 글
스프링 MVC 2편 - [4] 검증 1 - Validation(상편) (0) | 2023.07.14 |
---|---|
스프링 MVC 2편 - [3] 메시지·국제화 (0) | 2023.07.13 |
스프링 MVC 2편 - [1] 타임리프 - 기본기능(하편) (0) | 2023.07.09 |
스프링 MVC 2편 - [1] 타임리프 - 기본기능(상편) (0) | 2023.07.08 |
스프링 MVC 1편 - [7] 웹 페이지 만들기 [하편] (0) | 2023.07.06 |