상품을 관리할 수 있는 서비스를 만들어보자.
상품 도메인 모델
* 상품 ID
* 상품명
* 가격
* 수량
상품 관리 기능
* 상품 목록
* 상품 상세
* 상품 등록
* 상품 수정
상품 도메인 개발
package hello.itemservice.domain.item;
import lombok.Data;
@Data
public class Item {
private Long id;
private String itemName;
private Integer price;
private Integer quantity;
public Item() {
}
public Item(String itemName, Integer price, Integer quantity) {
this.itemName = itemName;
this.price = price;
this.quantity = quantity;
}
}
ItemRepository - 상품 저장소
package hello.itemservice.domain.item;
import org.springframework.stereotype.Repository;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Repository
public class ItemRepository {
private static final Map<Long, Item> store = new HashMap<>(); // static
private static long sequence = 0L; // static
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<>(store.values());
}
public void update(Long itemId, Item updateParam) {
Item findItem = findById(itemId);
findItem.setItemName(updateParam.getItemName());
findItem.setPrice(updateParam.getPrice());
findItem.setQuantity(updateParam.getQuantity());
}
public void clearStore() {
store.clear();
}
}
ItemRepositoryTest - 상품 저장소 테스트
package hello.itemservice.domain.item;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import java.util.List;
import static org.assertj.core.api.Assertions.*;
class ItemRepositoryTest {
ItemRepository itemRepository = new ItemRepository();
@AfterEach
void afterEach() {
itemRepository.clearStore();
}
@Test
void save() {
//given
Item item = new Item("itemA", 10000, 10);
//when
Item savedItem = itemRepository.save(item);
//then
Item findItem = itemRepository.findById(item.getId());
assertThat(findItem).isEqualTo(savedItem);
}
@Test
void findAll() {
//given
Item item1 = new Item("item1", 10000, 10);
Item item2 = new Item("item2", 20000, 20);
itemRepository.save(item1);
itemRepository.save(item2);
//when
List<Item> result = itemRepository.findAll();
//then
assertThat(result.size()).isEqualTo(2);
assertThat(result).contains(item1, item2);
}
@Test
void updateItem() {
//given
Item item = new Item("item1", 10000, 10);
Item savedItem = itemRepository.save(item);
Long itemId = savedItem.getId();
//when
Item updateParam = new Item("item2", 20000, 30);
itemRepository.update(itemId, updateParam);
//then
Item findItem = itemRepository.findById(itemId);
assertThat(findItem.getItemName()).isEqualTo(updateParam.getItemName());
assertThat(findItem.getPrice()).isEqualTo(updateParam.getPrice());
}
}
상품 상세, 등록 폼, 수정 폼 HTML도 각각 추가한다.(HTML은 생략)
BasicItemController
package hello.itemservice.web.basic;
import hello.itemservice.domain.item.Item;
import hello.itemservice.domain.item.ItemRepository;
import jakarta.annotation.PostConstruct;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import java.util.List;
@Controller
@RequestMapping("/basic/items")
@RequiredArgsConstructor
public class BasicItemController {
private final ItemRepository itemRepository;
@GetMapping
public String items(Model model) {
List<Item> items = itemRepository.findAll();
model.addAttribute("items", items);
return "basic/items";
}
/**
* 테스트용 데이터 추가
*/
@PostConstruct
public void init() {
itemRepository.save(new Item("itemA", 10000, 10));
itemRepository.save(new Item("itemB", 20000, 20));
}
}
@RequiredArgsConstructor
: final이 붙은 멤버변수만 사용해서 생성자를 자동으로 만들어준다.(final을 빼면 의존관계 주입이 안된다)
상품 상세 - BasicItemController에 추가
@GetMapping("/{itemId}")
public String item(@PathVariable long itemId, Model model) {
Item item = itemRepository.findById(itemId);
model.addAttribute("item", item);
return "basic/item";
}
@PathVariable로 넘오온 상품ID로 상품을 조회하고, 모델에 담아둔다. 그리고 뷰 템플릿을 호출한다.
상품 등록 - BasicItemController에 추가
@GetMapping("/add")
public String addForm() {
return "basic/addForm";
}
상품 등록 처리(addItemV1) - BasicItemController에 추가
@PostMapping("/add")
public String addItemV1(@RequestParam String itemName,
@RequestParam Integer price,
@RequestParam Integer quantity,
Model model) {
Item item = new Item();
item.setItemName(itemName);
item.setPrice(price);
item.setQuantity(quantity);
itemRepository.save(item);
model.addAttribute("item", item);
return "basic/item";
}
요청 파라미터 형식을 처리해야 하므로 @RequestParam을 사용했다.
하나하나 setter로 넣어주는 것은 불편하다. @ModelAttribute를 사용해보자.
상품 등록 처리(addItemV2) - BasicItemController에 추가
@PostMapping("/add")
public String addItemV2(@ModelAttribute("item") Item item) {
itemRepository.save(item);
// model.addAttribute("item", item); // 자동 추가되기 때문에 생략 가능
return "basic/item";
}
ModelAttribute의 이름이 파라미터 이름과 같으면 생략 가능하다. 생략해보자.
상품 등록 처리(addItemV3) - BasicItemController에 추가
@PostMapping("/add")
public String addItemV3(@ModelAttribute Item item) {
itemRepository.save(item);
// 클래스명: Item의 첫글자를 소문자로 바꿔서 name으로 사용(Item -> item)
return "basic/item";
}
@ModelAttribute 자체도 생략 가능하다. 생략해보자.
상품 등록 처리(addItemV4) - BasicItemController에 추가
@PostMapping("/add")
public String addItemV4(Item item) {
itemRepository.save(item);
return "basic/item";
}
지금까지 진행한 상품 등록 처리 컨트롤러는 상품 등록을 완료 후, 새로 고침 버튼을 클릭해보면 상품이 계속 등록되는 것을 확인할 수 있다.
HTTP 섹션에서 공부했듯이, PRG(Post, Redirect, Get)로 해결해야한다.
상품 등록 처리(addItemV5) - BasicItemController에 추가
@PostMapping("/add")
public String addItemV5(Item item) {
itemRepository.save(item);
return "redirect:/basic/items/" + item.getId();
이 코드 중 redirect에서 + item.getId()처럼 URL에 변수를 더해서 사용하는 것은 URL 인코딩이 안되기 때문에 위험하다.
그때 RedirectAttributes를 사용해보자.
상품 등록 처리(addItemV6) - BasicItemController에 추가
@PostMapping("/add")
public String addItemV6(Item item, RedirectAttributes redirectAttributes) {
Item savedItem = itemRepository.save(item);
redirectAttributes.addAttribute("itemId", savedItem.getId());
redirectAttributes.addAttribute("status", true);
return "redirect:/basic/items/{itemId}";
}
'RedirectAttributes' 를 사용하면 URL 인코딩도 해주고, pathVariable, 쿼리 파라미터까지 처리해준다.
뷰 템플리셍서 status가 true이면 '저장 완료' 메시지를 나타나도록 했다.
상품 수정 폼 컨트롤러 - BasicItemController에 추가
* GET /items/{itemId}/edit : 상품 수정 폼
* POST /items/{itemId}/edit : 상품 수정 처리
@GetMapping("/{itemId}/edit")
public String editForm(@PathVariable Long itemId, Model model) {
Item item = itemRepository.findById(itemId);
model.addAttribute("item", item);
return "basic/editForm";
}
수정에 필요한 정보를 조회하고, 수정용 폼 뷰를 호출한다.
@PostMapping("/{itemId}/edit")
public String edit(@PathVariable Long itemId, @ModelAttribute Item item) {
itemRepository.update(itemId, item);
return "redirect:/basic/items/{itemId}";
}
마지막에 뷰 템플릿을 호출하는 대신 상품 상세 화면으로 이동하도록 리다이렉트를 호출한다.
타임리프를 이용한 뷰 템플릿은 나중에 다시 다뤄보도록 하겠고, 일단 상품 관리 프로그램 작성은 완료되었다!
'공부 > Spring' 카테고리의 다른 글
[Spring MVC](10) Bean Validator (0) | 2024.01.28 |
---|---|
[Spring MVC](9) 검증(Validation) (2) | 2024.01.28 |
[Spring MVC](7) 기본 기능 - 요청 데이터 2, 응답 (1) | 2024.01.22 |
[Spring MVC](6) 기본 기능 - 요청 매핑, 요청 데이터 1 (0) | 2024.01.22 |
[Spring MVC](5) 스프링 MVC - 구조 이해 (1) | 2024.01.22 |