도메인
@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;
}
}
리포지토리
public interface ItemRepository {
Item save(Item item);
void update(Long itemId, ItemUpdateDto updateParam);
Optional<Item> findById(Long id);
List<Item> findAll(ItemSearchCond cond);
}
인터페이스,
이 리포지토리를 기반으로 JPA, JDBC, 메모리 등
따로 검색이나 업데이트를 위한 객체를 따로 만듦. 정말 딱 거기에 필요한 정보들만 가지고 수행하게끔.
@Data
public class ItemSearchCond {
private String itemName;
private Integer maxPrice;
public ItemSearchCond() {
}
public ItemSearchCond(String itemName, Integer maxPrice) {
this.itemName = itemName;
this.maxPrice = maxPrice;
}
}
딱 아이템 이름 혹은 가격정도만 기준으로 검색하게끔.
@Data
public class ItemUpdateDto {
private String itemName;
private Integer price;
private Integer quantity;
public ItemUpdateDto() {
}
public ItemUpdateDto(String itemName, Integer price, Integer quantity) {
this.itemName = itemName;
this.price = price;
this.quantity = quantity;
}
}
DB상 순서를 위해 시스템이 자체적으로 부여해주는 id는 필요 없으므로, 그 외의 것만. 업데이트 시에 반영하도록.
Dto는 data transfer object 로 직역은 데이터 전송 객체인데, 데이터 전달을 주 용도로 사용되는 객체를 뜻한다.
그 다음 실제 리포지토리 구현체를 보자. 간단히 메모리 저장소 구현체로 시작하였다.
@Repository
public class MemoryItemRepository implements ItemRepository {
private static final Map<Long, Item> store = new HashMap<>(); //static
private static long sequence = 0L; //static
@Override
public Item save(Item item) {
item.setId(++sequence);
store.put(item.getId(), item);
return item;
}
@Override
public void update(Long itemId, ItemUpdateDto updateParam) {
Item findItem = findById(itemId).orElseThrow();
findItem.setItemName(updateParam.getItemName());
findItem.setPrice(updateParam.getPrice());
findItem.setQuantity(updateParam.getQuantity());
}
@Override
public Optional<Item> findById(Long id) {
return Optional.ofNullable(store.get(id));
}
@Override
public List<Item> findAll(ItemSearchCond cond) {
String itemName = cond.getItemName();
Integer maxPrice = cond.getMaxPrice();
return store.values().stream()
.filter(item -> {
if (ObjectUtils.isEmpty(itemName)) {
return true;
}
return item.getItemName().contains(itemName);
}).filter(item -> {
if (maxPrice == null) {
return true;
}
return item.getPrice() <= maxPrice;
})
.collect(Collectors.toList());
}
public void clearStore() {
store.clear();
}
}
Map을 이용해 그냥 메모리를 저장공간으로 이용하였다.
save는 sequence(db의 순서 혹은 구분을 위한 고유값을 위해 지정해 주기 위해)를 부여해주며 그냥 메모리에 있는 Map 컬렉션에 추가시켜 준다.
update()는 id와 변화시킬 데이터들의 모음인 객체(ItemDto)를 받는데 그냥 Map에서 인자로 받은 id를 기준으로 찾아서 ItemDto에 있는 것을 그대로 그것에 값을 set 해준다.
이거는 메모리 저장공간이기 때문에, 그냥 값을 set해주면 객체는 주소로 넘어오는 거라서 메모리의 Map에 있는 실제 데이터가 바뀌는 것이므로 이렇게 해도 상관 없다.
findById는 id를 기준으로 찾아오는 것이다.
근데 받은 id를 기준으로 메모리 저장소에 없을 수 있으므로 Optional로 한번 감싼(이렇게 하면 뭐랄까 Null이 들어와도 관리하기가 쉬움) item을 반환한다.
다음은 findAll인데,
Map을 뒤져가며 필터링을 거치며,
itemName이 비어 있으면 return true를 해서 무조건 참으로 한다. 필터의 조건이 true면 현재 바라보고 있는 데이터가 비어있었던 리스트 같은 곳에 담긴다.
근데 만약 비어 있지 않다면, item의 이름 중 itemName이 포함되어 있는 것이 참이면 그 item을 비어있었던 리스트 같은 곳에 담긴다.
그 다음 한번 더 필터를 거치는데,
마찬가지로 maxPrice가 null이면 다 true로 하고,
뭔가 값이 있다면 그 가격보다 같거나 낱을 경우에만 바라보고 있는 데이터가 빈 리스트 같은 곳에 담긴다.
그 다음 collact(콜렉션종류(필터된값들의리스트)) 해서 여튼 특정 콜렉션 종류로 모아서 바꿔서 반환해 준다.
필터는 주어진 기준이 참이면 현재 바라보고 있는 데이터를 반환하기 위해 새로 만든 비어있었던 리스트에다 추가시켜 주는 거임.
clear()는 테스트 위해 아예 그 메모리 저장소의 데이터들을 싹 지워주는 용도
다음 서비스 쪽
public interface ItemService {
Item save(Item item);
void update(Long itemId, ItemUpdateDto updateParam);
Optional<Item> findById(Long id);
List<Item> findItems(ItemSearchCond itemSearch);
}
서비스는 비즈니스 로직이기에 거의 바꾸면 안되기 때문에, 사실 서비스에 인터페이스를 도입하는 경우는 별로 없다.
뭐 여튼, 보면 정말 뭐랄까 애초에 학습을 위해 만든 프로젝트라 그렇게 많은 기능들을 기획하지 않아서 별 기능이 없다.
그래서 보면 사실 거의 리포지토리의 기능 그대로 이다.
@Service
@RequiredArgsConstructor
public class ItemServiceV1 implements ItemService {
private final ItemRepository itemRepository;
@Override
public Item save(Item item) {
return itemRepository.save(item);
}
@Override
public void update(Long itemId, ItemUpdateDto updateParam) {
itemRepository.update(itemId, updateParam);
}
@Override
public Optional<Item> findById(Long id) {
return itemRepository.findById(id);
}
@Override
public List<Item> findItems(ItemSearchCond cond) {
return itemRepository.findAll(cond);
}
}
구현체.
컨트롤러
@Controller
@RequiredArgsConstructor
public class HomeController {
@RequestMapping("/")
public String home() {
return "redirect:/items";
}
}
그냥 홈페이지 딱 들어오면 /items로 보내버리는 거다.
@Controller
@RequestMapping("/items")
@RequiredArgsConstructor
public class ItemController {
private final ItemService itemService;
@GetMapping
public String items(@ModelAttribute("itemSearch") ItemSearchCond itemSearch, Model model) {
List<Item> items = itemService.findItems(itemSearch);
model.addAttribute("items", items);
return "items";
}
@GetMapping("/{itemId}")
public String item(@PathVariable long itemId, Model model) {
Item item = itemService.findById(itemId).get();
model.addAttribute("item", item);
return "item";
}
@GetMapping("/add")
public String addForm() {
return "addForm";
}
@PostMapping("/add")
public String addItem(@ModelAttribute Item item, RedirectAttributes redirectAttributes) {
Item savedItem = itemService.save(item);
redirectAttributes.addAttribute("itemId", savedItem.getId());
redirectAttributes.addAttribute("status", true);
return "redirect:/items/{itemId}";
}
@GetMapping("/{itemId}/edit")
public String editForm(@PathVariable Long itemId, Model model) {
Item item = itemService.findById(itemId).get();
model.addAttribute("item", item);
return "editForm";
}
@PostMapping("/{itemId}/edit")
public String edit(@PathVariable Long itemId, @ModelAttribute ItemUpdateDto updateParam) {
itemService.update(itemId, updateParam);
return "redirect:/items/{itemId}";
}
}
GetMapping은 그냥 보여주기용,
특정 폼을 보여주거나 데이터를 보여주기 위한 용도
같은 URL이라도 Post로 받으면 그것은 수정의 용도로,
Item을 추가하거나 수정한다.
대략 뭐 큰 그림으로는
이런 느낌
요청이 컨트롤러로 들어옴.
그리고 또 최종적으로 응답이 컨트롤러에서 클라이언트로 나가는 느낌(뭐 그 전에 프론트 서블릿을 거치긴 할거지만.)
'스프링 > 6. 스프링 DB-2' 카테고리의 다른 글
6. Jdbc 템플릿 (0) | 2023.10.08 |
---|---|
5. DB생성 (0) | 2023.10.08 |
4. 테스트 환경 (0) | 2023.10.08 |
3. 간이 프로젝트의 설정부 (0) | 2023.10.07 |
1. 데이터 접근 기술들 (0) | 2023.10.07 |