Spring(6)

9mond·2023년 9월 27일
0
post-thumbnail

1. 입력 폼 처리

  • 타임리프가 제공하는 입력 폼 기능을 적용해서 기존 프로젝트의 폼 코드를 타임리프가 지원하는 기능을 사용해서 효율적으로 개선한다.

  • th:object="${item}" : form에 객체가 연결되는 것을 커맨드 객체라고 한다.

  • th:field="${item.itemName}" :
    -> id="itemName" name="itemName" 1차 생략 가능

  • th:field="*{itemName}" :
    -> th:object 소속이라는 뜻의 *을 넣어주면 item도 생략 가능하다.
    -> id, name, value 삭제 가능

2. enum 타입

2-1. enum 타입이란?

  • Enumeration
  • 연관된 상수들을 하나의 타입으로 지정할 수 있도록 하는 클래스
  • 데이터 중에서는 요일(일, 월, 화, 수, 목, 금, 토), 계절(봄, 여름, 가을, 겨울) 등과 같이 몇 가지 한정된 값을 갖는 경우가 있다.
  • 이러한 데이터를 열거 타입에 들어갈 수 있는 열거 상수라고 한다.

2-2. enum의 장점

  • 코드가 단순해지며 가독성이 좋아진다.
  • 인스턴스 생성과 상속을 방지(객체 생성이 안됨)하여 상수값의 안정성이 보장됨.
  • 상수 자료형을 정의함으로써 해당 자료형 이외의 상수 값은 저장되지 못하게 함.
  • enum 예약어를 사용하므로 열거 의도를 분명히 함.

2-3. Enum과 메모리 구조

  • java에서 열거 상수는 상수 각각을 내부적으로 public static final 필드이면서 객체로 제공되도록 한다.
  • static이 붙어있기 때문에 각각의 상수는 클래스변수로 클래스로더가 로드 시점에 JVM Method 영역에 해당 클래스 변수들을 항상 상주시켜 프로그램이 종료되기 전에는 언제든지 가져다 쓸 수 있는 주소 공간을 확보한다.

2-4. 열거 상수를 다른 값과 연결하기

  • 열거 상수 각각이 열거 객체이므로 열거 객체에 생성자를 사용해서 다음과 같이 열거 상수에 다른 값을 할당할 수 있다.
  • 상수("연결문자"), 상수(값)과 같은 형태로 작성
  • 상수들을 열거한 끝에 ; 작성
  • 해당 자료형에 맞는 private 생성자 필요
  • 연결한 값을 반환해줄 getter 메서드 필요

3. 히든 필드

  • 스프링 MVC에서는 약간의 트릭을 사용하는데, 히든 필드를 하나 만들어서 _open 처럼 기존 체크 박스 이름 앞에 언더스코어( _ )를 붙여서 전송하면 체크를 해제했다고 인식할 수 있다.
  • 히든 필드는 항상 전송된다. 따라서 체크를 해제한 경우 여기에서 open은 전송되지 않고 _open만 전송되는데, 이 경우 스프링 MVC는 체크를 해제했다고 판단한다.
  • 벨리데이션 체크를 조금 더 수월하게 해주지만 조금 번거롭다.
    -> th:field="*{open}"를 사용하면 히든 필드를 thymeleaf가 만들어줌

4. radio

  • th:value="${type.name()}" : enum의 name return
  • th:text="${type.description}" : enum의 getDescription을 return

5. bindingResult

  • 스프링이 제공하는 검증 오류를 보관하는 객체이다.
  • 검증 오류가 발생하면 여기에 보관하면 된다.
  • BindingResult가 있으면 @ModelAttribute에 데이터 바인딩 시 오류가 발생하도록 컨트롤러가 호출된다.

    주의사항 : 검증할 대상 바로 다음에 와야 한다. 순서가 중요!
    @ModelAttribute Item item, 바로 다음에 BingdingResult가 와야 한다.
    BindingResult는 Model에 자동으로 포함된다.

6. error

  • th:errors
    -> 해당 필드에 오류가 있는 경우에 태그를 출력한다.
    -> th:if의 편의 버전이다.

  • th:errorclass="field-error"
    -> th:field에서 지정한 필드에 오류가 있으면 class 정보를 추가한다.

7. FieldError 생성자

  • 파라미터 목록
    -> objectname : 오류가 발생한 객체 이름
    -> field : 오류 필드
    -> rejectedValue : 사용자가 입력한 값(거절된 값)
    -> bindingFailure : 타입 오류 같은 바인딩 실패인지, 검증 실패인지 구분(바인딩 자체가 오류가 아니므로 false)
    -> codes : 메시지 코드
    -> arguments : 메시지에서 사용하는 인자
    -> defaultMessage : 기본 오류 메시지

💊오류해결

딱히 문제는 안되지만 여기서 에러가 났었다.

HTML을 해제해주면 에러가 사라진다.

💻코드


controller

BasicItemController.java

package com.codingbox.item.domain.web.controller;

import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.util.StringUtils;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;

import com.codingbox.item.domain.web.dto.DeliveryCode;
import com.codingbox.item.domain.web.dto.Item;
import com.codingbox.item.domain.web.dto.ItemType;
import com.codingbox.item.domain.web.repository.ItemRepository;

import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
import lombok.RequiredArgsConstructor;

@Controller
@RequestMapping("/basic/items")
@RequiredArgsConstructor
// @RequiredArgsConstructor : 
// final이 붙은 멤버변수만 사용해서 생성자를 자동으로 만들어준다.
public class BasicItemController {
	
	private final ItemRepository itemRepository;
	
	/*
	 *  @ModelAttribute : 
	 *  Controller를 호출할 때 (어떤 메서드가 호출되던지 간에)
	 *  model에 자동으로 해당 내용이 담기도록 보장된다.
	 */
	@ModelAttribute("regions")
	public Map<String, String> regions(){
		// 기존 HashMap : 순서가 보장되지 않는다.
		// LinkedHashMap : 순서가 보장된다.
		Map<String, String> regions = new LinkedHashMap<>();
		regions.put("SEOUL", "서울");
		regions.put("BUSAN", "부산");
		regions.put("JEJU", "제주");
		//model.addAttribute("regions", regions);
		return regions;
	}
	
	@ModelAttribute("itemTypes")
	public ItemType[] itemTypes() {
		
		// enum에 있는 값을 배열로 넘겨준다.
		return ItemType.values();
		
	}
	
	@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;
	}
	
//	@Autowired
	// 이렇게 생성자가 딱 1개만 있으면 스프링이 해당 생성자에게
	// @Autowired로 의존관계(DI)를 주입해준다.
//	public BasicItemController(ItemRepository itemRepository) {
//		this.itemRepository = itemRepository;
//	}
	
	@GetMapping
	public String items(Model model) {
		List<Item> items = itemRepository.findAll();
		model.addAttribute("items", items);
		return "basic/items";
	}
	
	@GetMapping("/{itemId}")
	// {itemId} : 경로변수, @PathVariable : 경로변수의 값을 추출하려면 이거 사용
	public String item(@PathVariable long itemId, Model model) {
		
		Item item = itemRepository.findById(itemId);
		model.addAttribute("item", item);
		return "basic/item";
	}
	
	@GetMapping("/add")
	public String addForm(Model model) {
		model.addAttribute("item", new Item());
		return "basic/addForm";
	}
	
	/*
	 * post방식
	 * /basic/items/add url mapping
	 * 파라미터 받아오기
	 * save()
	 * 상세페이지 조회 -> basic/item.html
	 */
//	@PostMapping("/add")
	public String saveItemV1(Model model, 
			@RequestParam String itemName,
			@RequestParam int price,
			@RequestParam Integer quantity) {
		Item item = new Item();
		item.setItemName(itemName);
		item.setPrice(price);
		item.setQuantity(quantity);
		
		itemRepository.save(item);
		
		model.addAttribute("item", item);
		return "basic/item";
	}
	
//	@PostMapping("/add")
	public String saveItemV2(Model model, 
			@ModelAttribute("item") Item item) {
		// @ModelAttribute가 대신 해주는 역할
//		Item item = new Item();
//		item.setItemName(itemName);
//		item.setPrice(price);
//		item.setQuantity(quantity);
		
		itemRepository.save(item);
		
		model.addAttribute("item", item);
		return "basic/item";
	}
	
	/*
	 * @ModelAttribute의 name도 생략 가능
	 * 즉, model.addAttribute("item", item); 생략 가능
	 * 생략 시 model에 저장되는 name은 클래스명 첫 글자만 소문자로 등록된다. Item -> item
	 * ----------------------------------------------------------------
	 * @ModelAttribute("hello") Item item
	 * 	-> model.addAttribute("hello", item);
	 * 	-> ("hello")도 생략 가능
	 * Model model도 생략 가능
	 */
//	@PostMapping("/add")
	public String saveItemV3( // Model model, 
			@ModelAttribute Item item) {
		itemRepository.save(item);
//		model.addAttribute("item", item);
		return "basic/item";
	}
	
	/*
	 * @ModelAttribute 자체 생략 가능하나 권장하진 않음
	 */
//	@PostMapping("/add")
	public String saveItemV4(Item item) {
		itemRepository.save(item);
		return "basic/item";
	}
	
//	@PostMapping("/add")
	public String saveItemV5(Item item) {
		itemRepository.save(item);
		return "redirect:/basic/items/" + item.getId();
	}
	
//	@PostMapping("/add")
	public String saveItemV6(Item item, RedirectAttributes redirectAttributes) {
		
		System.out.println("Item.open : " + item.getOpen());
		System.out.println("Item.regions : " + item.getRegions());
		System.out.println("Item.itemType : " + item.getItemType());
		
		Item savedItem = itemRepository.save(item);
//		redirectAttributes.addAttribute("itemId", savedItem);
		redirectAttributes.addAttribute("status", true);
		return "redirect:/basic/items/" + item.getId();
	}
	
	/*
	 * BindingResult : Item 객체에 값이 잘 담기지 않을 때 BindingResult 객체에 값이 담기게 된다.
	 * StringUtils  : 값이 있을 경우에는 true 반환하고, 공백이나 Null이 들어올 경우에는 false를 반환하게 된다.
	 */
//	@PostMapping("/add")
	public String saveItemV7(Item item, BindingResult bindingResult,
			RedirectAttributes redirectAttributes) {		
		
		if( !StringUtils.hasText( item.getItemName() ) ) {
			bindingResult.addError(
				// FieldError : field 단위의 error는 spring에서 제공해주는 객체
				new FieldError("item", "itemName", "상품 이름은 필수입니다."));
		}
		
		if( item.getPrice() == null || 
			item.getPrice() < 1000 ||
			item.getPrice() > 1000000 ) {
			bindingResult.addError(
				new FieldError("item", "price", "가격은 1,000,000까지 허용합니다."));
		}
		
		if( item.getQuantity() == null ||
			item.getQuantity() > 10000) {
			bindingResult.addError(
				new FieldError("item", "quantity", "수량은 최대 9,999까지 허용됩니다."));
			
		}
		
		// 검증에 실패한다면 다시 입력 폼
		if( bindingResult.hasErrors() ) {
			System.out.println("error : " + bindingResult);
			return "basic/addForm";
		}
		
		Item savedItem = itemRepository.save(item);
		redirectAttributes.addAttribute("status", true);
		return "redirect:/basic/items/" + item.getId();
	}
	
//	@PostMapping("/add")
	public String saveItemV8(Item item, BindingResult bindingResult,
			RedirectAttributes redirectAttributes) {		
		
		if( !StringUtils.hasText( item.getItemName() ) ) {
			bindingResult.addError(
					new FieldError("item", "itemName", item.getItemName(),
							false, null, null, "상품 이름은 필수입니다."));
			
		}
		if( item.getPrice() == null || 
				item.getPrice() < 1000 ||
				item.getPrice() > 1000000 ) {
			bindingResult.addError(
					new FieldError("item", "price", item.getPrice(),
							true, null, null, "가격은 1000 ~ 1,000,000까지 허용합니다."));
		}
		
		if( item.getQuantity() == null ||
				item.getQuantity() > 10000) {
			bindingResult.addError(
					new FieldError("item", "quantity", item.getQuantity(),
							false, null, null, "수량은 최대 9,999까지 허용됩니다."));
			
		}
		
		// 검증에 실패한다면 다시 입력 폼
		if( bindingResult.hasErrors() ) {
			System.out.println("error : " + bindingResult);
			return "basic/addForm";
		}
		
		Item savedItem = itemRepository.save(item);
		redirectAttributes.addAttribute("status", true);
		return "redirect:/basic/items/" + item.getId();
	}
	
	@PostMapping("/add")
	public String saveItemV9(Item item, BindingResult bindingResult,
			RedirectAttributes redirectAttributes) {		
		
		if( !StringUtils.hasText( item.getItemName() ) ) {
			bindingResult.addError(
					new FieldError("item", "itemName", item.getItemName(),
							false, new String[]{"required.item.itemName"}, null, null));
			
		}
		if( item.getPrice() == null || 
				item.getPrice() < 1000 ||
				item.getPrice() > 1000000 ) {
			bindingResult.addError(
					new FieldError("item", "price", item.getPrice(),
							false, new String[] {"range.item.price"},
							new Object[] {1000, 1000000}, null));
		}
		
		if( item.getQuantity() == null ||
				item.getQuantity() > 10000) {
			bindingResult.addError(
					new FieldError("item", "quantity", item.getQuantity(),
							false, new String[] {"max.item.quantity"},
							new Object[] {9999}, null));
			
		}
		
		// 검증에 실패한다면 다시 입력 폼
		if( bindingResult.hasErrors() ) {
			System.out.println("error : " + bindingResult);
			return "basic/addForm";
		}
		
		Item savedItem = itemRepository.save(item);
		redirectAttributes.addAttribute("status", true);
		return "redirect:/basic/items/" + item.getId();
	}
	
	@GetMapping("/{itemId}/edit")
	public String editForm(@PathVariable Long itemId, Model model) {
		// 조회
		// model에 담아서 editForm.html에 요청 보내기
		Item item = itemRepository.findById(itemId);
		model.addAttribute("item", item);
		return "basic/editForm";
	}
	
	/*
	 * update
	 * update 후에는 상세페이지로 이동
	 */
	@PostMapping("/{itemId}/edit")
	public String edit(@PathVariable Long itemId, @ModelAttribute Item item) {
		
		System.out.println("item : " + item.getOpen());
		
		itemRepository.update(itemId, item);
		return "redirect:/basic/items/{itemId}";	// forward 방식이 아니고 redirect로
	}
	
	// 테스트 데이터 추가
	@PostConstruct
	public void init() {
		System.out.println("초기화 메서드 실행");
		itemRepository.save(new Item("testA", 10000, 10));
		itemRepository.save(new Item("testB", 20000, 20));
	}
	
	// 종료 메서드
	@PreDestroy
	public void destroy() {
		System.out.println("종료 메서드 호출");
	}
}

error.properties

required.default=기본 오류 메시지입니다.
#required.item.itemName=상품 이름은 필수입니다.
range.item.price=가격은 {0} ~ {1} 까지 허용합니다.
max.item.quantity=수량은 최대 {0} 까지 허용합니다.

repository

ItemRepository.java

package com.codingbox.item.domain.web.repository;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.springframework.stereotype.Repository;

import com.codingbox.item.domain.web.dto.Item;

@Repository
public class ItemRepository {
	private static final Map<Long, Item> store = new HashMap<>();
	private static long sequence = 0L;
	
	// 저장
	public Item save(Item item) {
		item.setId(++sequence);
		store.put(item.getId(), item);
		return item;
	}
	
	// 아이템 하나 검색
	public Item findById(Long id) {
		return store.get(id);
	}
	
	// 전체항목
	public List<Item> findAll(){
		return new ArrayList<Item>(store.values());
	}
	
	// 아이템 수정
	public void update(Long itemId, Item updateParam) {
		
		//item 먼저 찾는다.
		Item findItem = findById(itemId);
		findItem.setItemName(updateParam.getItemName());
		findItem.setPrice(updateParam.getPrice());
		findItem.setQuantity(updateParam.getQuantity());
		
		findItem.setOpen(updateParam.getOpen());
		findItem.setRegions(updateParam.getRegions());
		findItem.setItemType(updateParam.getItemType());
		findItem.setDeliveryCode(updateParam.getDeliveryCode());
		
	}
	
	// store clear
	public void clearStore() {
		store.clear();
	}
}

dto

DeliveryCode.java

package com.codingbox.item.domain.web.dto;

import lombok.AllArgsConstructor;
import lombok.Data;

// @AllArgsConstructor	: 모든 필드 값을 파라미터로 받는 생성자를 생성
// @NoArgsConstructor	: 파라미터가 없는 기본 생성자를 생성

@Data
@AllArgsConstructor
public class DeliveryCode {
	/*
	 * FAST : 빠른 배송
	 * NORMAL : 일반 배송
	 * SLOW : 느린 배송
	 */
	
	private String code;
	private String displayName;
}

item.java

package com.codingbox.item.domain.web.dto;

import java.util.List;

import lombok.Getter;
import lombok.Setter;

@Getter @Setter
public class Item {
	private Long id;
	private String itemName;
	private Integer price;		// null일 수 있기 때문에 Integer
	private Integer quantity;	// null일 수 있기 때문에 Integer
	
	// 기능 추가
	private Boolean open;			// 판매여부
	private List<String> regions;	// 등록 지역 
	private ItemType itemType;		// 상품 종류
	private String deliveryCode;	// 배송 방식
	
	public Item() {
		super();
	}
	
	public Item(String itemName, Integer price, Integer quantity) {
		super();
		this.itemName = itemName;
		this.price = price;
		this.quantity = quantity;
	}
}

itemType.java

package com.codingbox.item.domain.web.dto;

public enum ItemType {
	BOOK("도서"), FOOD("식품"),ETC("기타");
	
	private final String description;
	
	private ItemType(String description){
		this.description = description;
	}
	public String getDescription() {
		return description;
	}
}

templates.basic

addForm.html

<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="utf-8">
<link href="../css/bootstrap.min.css" 
	th:href="@{/css/bootstrap.min.css}"
	rel="stylesheet">
<style>
.container {
	max-width: 560px;
}
.field-error {
   border-color: #dc3545;
   color: #dc3545;
}
</style>
</head>
<body>
	<div class="container">
		<div class="py-5 text-center">
			<h2>상품 등록 폼</h2>
		</div>
		<h4 class="mb-3">상품 입력</h4>
      	<!-- th:object="${item}" : 커멘드 객체 -->
		<form action="item.html"
			th:object="${item}" 
			th:action method="post">	<!--/ 동일한 url을 가지고 있을 경우 ="@{/basic/items/add}" 생략 가능-->
			
			<div>
				<label for="itemName">상품명</label>
              	<!-- id="itemName" 이것도 생략 가능하나 label의 for="itemName" 와 연결되기 때문에 남겨둔다. -->
				<input type="text" id="itemName" th:field="*{itemName}"
					th:errorclass="field-error" class="form-control" 
					placeholder="이름을 입력하세요">
				<div class="field-error" th:errors="*{itemName}">상품명 오류</div>
			</div>
			<div>
				<label for="price">가격</label> 
				<input type="text" id="price" th:field="*{price}"
					th:errorclass="field-error" class="form-control" 
					placeholder="가격을 입력하세요">
				<div class="field-error" th:errors="*{price}">가격 오류</div>
			</div>
			<div>
				<label for="quantity">수량</label> 
				<input type="text" id="quantity" th:field="*{quantity}"
					th:errorclass="field-error" class="form-control" 
					placeholder="수량을 입력하세요">
				<div class="field-error" th:errors="*{quantity}">수량 오류</div>
			</div>
			<hr class="my-4">
			
			<!--순수 html-->
<!--			<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>-->

			<!-- thymeleaf -->
			<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>
			
	        <!-- 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>
	        
	        <!-- 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"></label>
	            </div>
	        </div>
	        
	        <hr class="my-4">
			 
		 	<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>
			 
			 
			<div class="row">
				<div class="col">
					<button class="w-100 btn btn-primary btn-lg" type="submit">
						상품등록
					</button>
				</div>
				<div class="col">
					<button class="w-100 btn btn-secondary btn-lg" 
						onclick="location.href='items.html'" 
						th:onclick="|location.href='@{/basic/items}'|"
						type="button">
						취소
					</button>
				</div>
			</div>
		</form>
	</div>
	<!-- /container -->
	<script>
		function val(){
			if(document.getElementById("itemName").value == ""){
				alert("item을 넣어주세요");
				return false;
			}
		}	
	</script>
</body>
</html>

editForm.html

<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="utf-8">
<link href="../css/bootstrap.min.css" 
	th:href="@{/css/bootstrap.min.css}"
	rel="stylesheet">
<style>
.container {
	max-width: 560px;
}
</style>
</head>
<body>
	<div class="container">
		<div class="py-5 text-center">
			<h2>상품 수정 폼</h2>
		</div>
		<form action="item.html"
			th:object="${item}"
			th:action method="post">	<!--th:action 메서드 방식만 변경-->
			<div>
				<label for="id">상품 ID</label> 
				<input type="text" id="id" th:field="*{id}"
					class="form-control" value="1" th:value="|${item.id}|" readonly>
			</div>
			<div>
				<label for="itemName">상품명</label> 
				<input type="text" id="itemName" th:field="*{itemName}"
					class="form-control" value="상품A" th:value="|${item.itemName}|">
			</div>
			<div>
				<label for="price">가격</label> 
				<input type="text" id="price" th:field="*{price}"
					class="form-control" value="10000" th:value="|${item.price}|">
			</div>
			<div>
				<label for="quantity">수량</label> 
				<input type="text" id="quantity" th:field="*{quantity}"
					class="form-control" value="10" th:value="|${item.quantity}|">
			</div>
			<hr class="my-4">
			
			<!-- thymeleaf -->
			<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>
	        
	        <!-- 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>
	        
	        <!-- 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"></label>
	            </div>
	        </div>
	        
			<hr class="my-4">
			 
		 	<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>
			
			<div class="row">
				<div class="col">
					<button class="w-100 btn btn-primary btn-lg" type="submit">
						저장
					</button>
				</div>
				<div class="col">
					<button class="w-100 btn btn-secondary btn-lg" 
						onclick="location.href='items.html'" 
						th:onclick="|location.href='@{/basic/items}'|"
						type="button">
						취소
					</button>
				</div>
			</div>
		</form>
	</div>
	<!-- /container -->
</body>
</html>

item.html

<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="utf-8">
<link href="../css/bootstrap.min.css"
	th:href="@{/css/bootstrap.min.css}"
	rel="stylesheet">
<style>
	.container {
		max-width: 560px;
	}
</style>
</head>
<body>
	<div class="container">
		<div class="py-5 text-center">
			<h2>상품 상세</h2>
		</div>
		
		<!--추가-->
		<h2 th:if="${param.status}" th:text="'저장 성공!'"></h2>
		<div>
			<label for="itemId">상품 ID</label> 
			<input type="text" id="itemId" name="itemId" class="form-control" value="1"
			th:value="${item.id}" readonly>
		</div>
		<div>
			<label for="itemName">상품명</label> 
			<input type="text" id="itemName" name="itemName" class="form-control" value="상품A"
			th:value="${item.itemName}" readonly>
		</div>
		<div>
			<label for="price">가격</label> 
			<input type="text" id="price" name="price" class="form-control" value="10000"
			th:value="${item.price}" readonly>
		</div>
		<div>
			<label for="quantity">수량</label> 
			<input type="text" id="quantity" name="quantity" class="form-control" value="10"
			th:value="${item.quantity}" readonly>
		</div>
		
		<hr class="my-4">
		
		<!-- thymeleaf -->
		<div>판매 여부</div>
        <div>
            <div class="form-check">
               <input type="checkbox" id="open" th:field="${item.open}" class="form-check-input" disabled> 
                  <label for="open" class="form-check-label">판매 오픈</label>
            </div>
        </div>
		
        <!-- 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" disabled>
               <label th:for="${#ids.prev('regions')}" th:text="${region.value}" 
               	class="form-check-label">서울</label>
            </div>
        </div>	
        
        <!-- 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"></label>
            </div>
        </div>
		
        <hr class="my-4">
		 
	 	<div>
        	<div>배송 방식</div>
            <select th:field="${item.deliveryCode}" class="form-select" disabled>
               <option value="">==배송 방식 선택==</option><!-- 아무것도 선택 안할때 -->
               <option th:each="deliveryCode : ${deliveryCodes}" 
                  th:value="${deliveryCode.code}" th:text="${deliveryCode.displayName}">
                  FAST
               </option>
            </select>
         </div>
		
		<div class="row">
			<div class="col">
				<button class="w-100 btn btn-primary btn-lg"
				onclick="location.href='editForm.html'" 
				th:onclick="|location.href='@{/basic/items/{itemId}/edit(itemId=${item.id})}'|"
				type="button">
					상품수정
				</button>
			</div>
			<div class="col">
				<button class="w-100 btn btn-secondary btn-lg" 
				onclick="location.href='items.html'" 
				th:onclick="|location.href='@{/basic/items}'|"
				type="button">
					목록으로
				</button>
			</div>
		</div>
	</div>
	<!-- /container -->
	<script th:inline="javascript">
	if([[${param.status}]] != null){	
		if([[${param.status}]]){
			alert("저장완료");
		}else{
			alert("저장실패");
		}
		}
	</script>
</body>
</html>

items.html

<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="utf-8">
<link href="../css/bootstrap.min.css" 
	th:href="@{/css/bootstrap.min.css}"
	rel="stylesheet">
</head>
<body>
	<div class="container" style="max-width: 600px">
		<div class="py-5 text-center">
			<h2>상품 목록</h2>
		</div>
		<div class="row">
			<div class="col">
				<button class="btn btn-primary float-end" 
					onclick="location.href='addForm.html'"
					th:onclick="|location.href='@{/basic/items/add}'|"
					type="button">
					상품 등록
				</button>
			</div>
		</div>
		<hr class="my-4">
		<div>
			<table class="table">
				<thead>
					<tr>
						<th>ID</th>
						<th>상품명</th>
						<th>가격</th>
						<th>수량</th>
					</tr>
				</thead>
				<tbody>
					<tr th:each="item : ${items}">
						<td>
							<a href="item.html"
							th:href="@{/basic/items/{itemId}(itemId=${item.id})}"
							th:text="${item.id}">아이템id</a>
						</td>
						<td>
							<a href="item.html"
							th:href="@{|/basic/items/${item.id}|}"
							th:text="${item.itemName}">상품명</a>
						</td>
						<td th:text="${item.price}">1000</td>
						<td th:text="${item.quantity}">10</td>
					</tr>
					
				</tbody>
			</table>
		</div>
	</div>
	<!-- /container -->
</body>
</html>

enums

Week.java

package com.codingbox.item.test.enums;

public enum Week {
	MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY;
	
	// enum 타입은 일반 메서드를 가질 수 있다. 
	public void dayInfo() {
		System.out.println("dayInfo enum");
	}
}

Main01.java

package com.codingbox.item.test.enums;

public class Main01 {

	public static void main(String[] args) {
		Week today = Week.MONDAY;
		System.out.println(today);
		
		Week.MONDAY.dayInfo();
		Week.FRIDAY.dayInfo();
	}
}

Season.java

package com.codingbox.item.test.enums;

public enum Season {
	SPRING, SUMMER, FALL, WINTER;
}

Main02.java

package com.codingbox.item.test.enums;

public class Main02 {

	public static void main(String[] args) {
		Season season = Season.SPRING;
		
		// name() : 열거 객체의 문자열 리턴
		String name = season.name();
		System.out.println(name);
		System.out.println("--------------------");
		
		// ordinal() : 열거 객체가 몇 번째인지를 리턴
		int ordinal = season.ordinal();
		System.out.println(ordinal);
		System.out.println("--------------------");
		
		// values() : 열거 타입의 모든 열거 객체들을 배열로 만들어 리턴
		Season[] season1 = Season.values();
		for(Season s : season1) {
			System.out.println(s);
		}
	}
}

Type.java

package com.codingbox.item.test.enums;

public enum Type {
	WALKING("워킹화"),
	RUNNING("런닝화"),
	TRACKING("트래킹화"),
	HIKING("등산화");
	
	final private String name;
	
	private Type(String name) {
		this.name = name;
	}
	public String getName() {
		return name;
	}
}

Main03.java

package com.codingbox.item.test.enums;

public class Main03 {

	public static void main(String[] args) {
		for( Type type : Type.values() ) {
			System.out.println(type.getName());
		}
	}
}
profile
개발자

0개의 댓글

관련 채용 정보