2. thymeleaf - 스프링 통합과 폼

ys·2024년 1월 5일

Spring-mvc2

목록 보기
2/10

김영한 강사님의 스프링 MVC 2편 - 백엔드 웹 개발 활용 기술을 듣고 정리한 내용입니다. 자세한 내용은 강의를 참고해주세요

통합으로 추가되는 기능들

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

저번 스프링 mvc1에서 했던 예제를 추가하는 형식으로 실습을 진행한다
Spring mvc- 웹 페이지 만들기

입력 폼 처리

  • th:object : 커맨드 객체를 지정한다, view에 연결되는 객체이다
  • *{} : 선택 변수 식, th:object에서 선택한 객체를 접근하는 식이다
  • th:field : html 태그의 id,name,value속성을 자동으로 처리해준다
  • 기존의 form/addForm.html을 부르는 컨트롤러에는 Model을 부르지 않았다
    하지만,,,
@GetMapping("/add")
public String addForm(Model model) {
	model.addAttribute("item", new Item());
	return "form/addForm";
}
  • view에서 th:object를 사용하기 위해, 빈 객체를 컨트롤러에서 하나 생성 한다
  • Model을 파라미터로 받고 addAttribute()를 이용해 Model에 item객체를 생성한다
  • 이 item객체를 view에서 이용한다
  • 이런 방법은 검증에서 큰 효과를 내고, 이렇게 추가해도 메모리에 영향이 거의 없다

  • 전체 form태그에서 th:object ="${item}"으로 커맨드 객체를 지정한다
  • 커맨드 객체는 form 태그 안에서만 사용할 수 있다
  • 선택 변수 식을 이용해서 th:field ="*{itemName}" 이렇게 접근할 수 있다
  • 만약 선택 변수 식을 이용하지 않는다면, th:field="${item.itemName}"이렇게 접근할 수 있다
  • th:field 는 id , name , value 속성을 모두 자동으로 만들어준다.
    • id : th:field 에서 지정한 변수 이름과 같다. id="itemName"
    • name : th:field 에서 지정한 변수 이름과 같다. name="itemName"
    • value : th:field 에서 지정한 변수의 값을 사용한다. value=""
  • 이렇게 보면 th:object th:field 덕분에 폼을 개발할 때 약간의 편리함을 얻었다
  • 하지만 이것들의 진가는 검증단계에서 나온다!
  • 객체를 접근해서 메서드를 프로퍼티 방법으로 실행하는 거니까 이름으로 찾는거보다 편리할듯!!!

마찬가지로 editForm또한 수정해준다

요구사항 추가

  • 판매 여부

    • 판매 오픈 여부
    • 체크 박스로 선택할 수 있다.
  • 등록 지역

    • 서울, 부산, 제주
    • 체크 박스로 다중 선택할 수 있다.
  • 상품 종류

    • 도서, 식품, 기타
    • 라디오 버튼으로 하나만 선택할 수 있다.
  • 배송 방식

    • 빠른 배송
    • 일반 배송
    • 느린 배송
    • 셀렉트 박스로 하나만 선택할 수 있다

  • 이는 각각 boolean으로 판매 여부
  • 등록 지역은 -> LinkedHashMap을 이용
  • 등록 지역은 enum이용
  • 상품 종류는 java코드를 이용
  • 이렇게 하나하나 다르게 예시를 보도록 하겠다
@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; // 배송 방식
  • 이렇게 item 도메인 클래스의 필드를 수정해주고, @Data를 이용해 여러 기능을 추가 해주었다.

체크 박스 - 단일1(판매 여부)

  • 클라이언트 -------------- 서버
  • 클라이언트가 서버에게 Get방식으로 add 페이지 요청
  • 서버는 Controller에서 GetMapping("/add")찾아서, view반환 -> 랜더링해서 페이지 확인
  • 이때 뷰에서 th:object, th:field=*{}을 사용해서 우리가 반환할 객체의 필드를 이름으로 선정해, Form 을 채우기시 시작함
  • 이때 Form에서 받은 데이터를 이용해 POST방식으로 다시 서버에 전송
  • 이때 체크박스를 만들었는데, item 객체의 필드 이름이 open이므로 체크박스 또한 이름을 open을 만든다
  • 그러면 체크박스에 입력된 값이, ${item.open}이렇게 프로퍼티 방식으로 필드값에 채워진다
  • 근데 체크하면 on으로 보내고
  • 체크 안하면 open이라는 필드 자체를 서버로 전송하지 않음
  • POST 페이지로 이동함
  • 입력받은 데이터 로직에 맞게 저장하고 저장된 페이지 보여줌
  • 이때 Spring이 체크박스에 대해서는 on은 True로 반환함
  • 근데 체크를 안하면 값을 안보내니까 이런 부분은 나중에 수정부분에서 문제가 생길 수 있음
  • 그래서 _open이라는 이름의 히든 필드를 추가해서 보냄
  • 이때 체크박스가 체크되면 _open은 무시됨
  • 체크박스가 체크되지 않으면 _open만 있는 것을 보고 item.getOpen을 해보면 null이 아니라 false로 저장됨


    위 사진처럼 일어나서, hidden 필드를 추가한다
<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>

체크 박스 - 단일2(판매 여부)

  • 개발할 때 마다 이렇게 히든 필드를 추가하는 것은 상당히 번거롭다.
  • 타임리프가 제공하는 폼 기능을 사용하면 이런 부분을 자동으로 처리할 수 있다.
<div class="form-check">
 <input type="checkbox" id="open" th:field="*{open}" class="form-checkinput">
 <label for="open" class="form-check-label">판매 오픈</label>
 </div>
  • 이렇게 타임리프 기능을 적용하면 th:field="*{open}"
  • thymeleaf가 자동으로 hidden 필드 _open을 추가해준다
  • 타임리프의 체크 확인 checked="checked"
    • 체크 박스에서 판매 여부를 선택해서 저장하면, 조회시에 checked 속성이 추가된 것을 확인할 수 있다.
    • 이런 부분을개발자가 직접 처리하려면 상당히 번거롭다. 타임리프의 th:field 를 사용하면, 값이 true 인 경우 체크를 자동으로 처리해준다.
  • 이제 상품 수정과, 상품에도 모두 단일박스를 적용해준다

체크 박스 - 멀티

  • 등록 지역
    서울, 부산, 제주
    체크 박스로 다중 선택할 수 있다
@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 : 컨트롤러가 호출될때마다, 자동으로 @ModelAttribute("region")부분이 실행되고 ->이는 "region"이름으로 Model에 return값을 넣는다.
  • 자동으로 컨트롤러에서 Model에다가 값을 넣어서 view에서 이용할 수 있게 해준다
  • 하지만 컨트롤러가 실행할때마다 추가되는 단점이 있다
  • 미리 static영역에 넣어놓고 불러 쓰는게 최적화에는 더 좋다
  • 하지만 넣는 데이터가 많지 않는 이상 그렇게 큰 영향을 미치지는 않는다

  • 다중 선택이 가능하기 때문에 th:each="region : "${regions}"를 이용한다
  • regions는 @ModelAttribute로 모델에 담은 것이다
  • th:field="*{regions}" = th:field = "${item.fidle}이다
  • value값은 region.key로 아까 @ModelAttribute에서 넣은 맵의 key값들중 클릭된 것들이다
  • 이때 checkbox의 ideach문으로 동적으로 region1, region2, region3이렇게 생성되므로
  • #ids.prev('region')은 그 동적으로 만든 id를 찾아주는 코드이다
  • ids.prev, ids.next 이런 방식이 있다
  • 이렇게 id가 th:field로 인해 동적으로 생성되고...
  • 이렇게 HTML이 생성된다
  • <label for="id 값"> 에 지정된 id 가 checkbox 에서 동적으로 생성된 regions1 , regions2 ,regions3 에 맞추어 순서대로 입력된 것을 확인할 수 있다
  • item.html 즉 상품 상세 확인 정보에서는 th:object를 사용하지 않았으므로 th:fieldth:field="${item.region}"이렇게 넣어줘야 한다
  • disabled를 추가해서 상품 상세에서는 체크 박스가 선택되지 않게 해야한다
  • editForm.html도 추가해준다

라디오 버튼

  • 라디오 버튼은 여러 선택지 중에 하나를 선택할 때 사용할 수 있다.
  • 이번시간에는 라디오 버튼을 자바 ENUM을 활용해보자
public enum ItemType {
	BOOK("도서"), FOOD("식품"), ETC("기타");
	private final String description;
	ItemType(String description) {
	this.description = description;
 }
	public String getDescription() {
		return description;
	}
}
@ModelAttribute("itemTypes")
	public ItemType[] itemTypes() {
	return ItemType.values();
}
  • 이렇게 enum 클래스와, 컨트롤러를 만든다
<!-- radion 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>
  • item.itemType=FOOD: 값이 있을 때
  • item.itemType=null: 값이 없을 때
  • enum데이터가 Book("도서일때)
  • 멀티 체크박스와 비슷하고 ${type.name()} ->Book이 들어가고
  • "&{type.description}" -> getter프로퍼티를 이용한 도서가 들어간다
  • 마찬가지로 editForm과 items도 바꿔준다
  • 선택한 식품(Food)에 checked="checked"가 된다
  • thymeleaf의 기능이다!!! -> 원래는 개발자가 해야할 일임

셀렉트 박스

  • 셀렉트 박스는 여러 선택지 중에 하나를 선택할 때 사용할 수 있다
  • 배송 방식
    • 빠른 배송
    • 일반 배송
    • 느린 배송
    • 셀렉트 박스로 하나만 선택할 수 있다.
  • 이번엔 컨트롤러에 리스트로 만든 후에 하나하나 객체를 생성해보자
@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를 이용해 컨트롤러가 실행될 때, Model에 저장하는 특별한 사용법을 적용하였다
<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>
        <hr class="my-4">
  • 기존의 체크박스와 라디오 박스와 다르게 <Select>로 시작하고
  • 첫번째 <option>태그로 처음 제목을 넣고
  • 뒤에 <option>태그로 선택 박스를 둔다 -> 이때 th:each문법을 써서 반복문을 사용한다
  • deliveryCode는 필드값으로 code, displayName 두개를 가지고 있고
  • 이를 .code , .displayName이렇게 불러서 value값과, text으로 넣는다
  • value값은 실제 값이고
  • text값은 option부분에 보이는 값이다
  • 마찬가지로 edit.html과 item.html도 수정해서 화면을 보여준다
profile
개발 공부,정리

0개의 댓글