상품 수정하기
상품 수정 기능은 “어떤 상품을 수정할 것인가”와 “무엇으로 수정할 것인가”라는 두 가지 정보가 함께 필요하다.
여기서 어떤 상품을 수정할지는 URL 경로에 있는 상품 번호(id)로 결정하고,
실제 수정할 데이터는 요청 바디에 담긴 ProductDto로 받는 구조를 사용한다.
컨트롤러 코드는 다음과 같다.
@RequestMapping(value = "/products/{id}", method = RequestMethod.PUT)
public ProductDto updateProduct(
@PathVariable Long id,
@RequestBody ProductDto productDto
){
productDto.setId(id);
return simpleProductService.update(productDto);
}
여기서 중요한 부분은 productDto.setId(id); 이다.
클라이언트가 요청 바디에 id를 넣어서 보낼 수도 있지만, 아예 보내지 않을 수도 있다.
또는 URL의 id와 바디의 id가 다르게 들어오는 잘못된 요청도 있을 수 있다.
이런 상황에서 어떤 상품을 수정할지는 URL 경로의 id가 기준이 되어야 하므로,
컨트롤러에서 @PathVariable로 받은 id를 ProductDto에 다시 세팅해주는 것이다.
즉, 수정 대상은 /products/{id} 경로가 결정하고, 수정 내용은 요청 바디가 제공한다고 이해하면 된다.
서비스 계층에서는 DTO를 도메인 객체로 변환한 뒤 레포지토리에 전달하고,
수정된 결과를 다시 DTO로 바꿔서 반환한다.
public ProductDto update(ProductDto productDto){
Product product = modelMapper.map(productDto, Product.class);
Product updatedProduct = listProductRepository.update(product);
ProductDto updatedProductDto = modelMapper.map(updatedProduct, ProductDto.class);
return updatedProductDto;
}
이 구조는 상품 추가 기능과 비슷하다.
외부와 맞닿는 영역에서는 DTO를 사용하고,
실제 저장소 안으로 들어갈 때는 Product를 사용하는 흐름이다.
레포지토리에서 상품 수정은 아래 코드처럼 구현할 수 있다.
public Product update(Product product){
Integer indexToModify = products.indexOf(product);
products.set(indexToModify, product);
return product;
}
이 코드는 먼저 products.indexOf(product)를 사용해 리스트 안에서 수정 대상 상품의 위치를 찾고,
그 위치에 새로운 product를 다시 넣는 방식이다.
그런데 여기서 중요한 문제가 하나 있다.
리스트는 어떤 객체가 “같다”고 판단해야 index를 찾을 수 있는데,
자바에서 기본적으로 객체 비교는 같은 인스턴스인지 여부를 기준으로 동작하는 경우가 많다.
하지만 지금 수정 요청으로 들어온 product는 새로 만들어진 객체이기 때문에,
리스트 안에 원래 저장되어 있던 객체와 같은 인스턴스가 아니다.
즉, 그대로 두면 indexOf()가 원하는 상품을 찾지 못할 수 있다.
그래서 Product 클래스에서 id가 같으면 같은 상품으로 본다는 기준을 직접 정의해줘야 한다.
이를 위해 equals()와 hashCode()를 오버라이드한다.
@Override
public boolean equals(Object obj) {
if (this == obj){
return true;
}
if (obj == null || getClass() != obj.getClass()){
return false;
}
Product product = (Product)obj;
return Objects.equals(id, product.id);
}
@Override
public int hashCode() {
return Objects.hash(id);
}
이 코드는 Product 객체 두 개를 비교할 때, id 값이 같으면 같은 상품으로 간주하도록 만든다.
즉, 상품의 “동일성” 기준을 객체 자체가 아니라 상품 번호(id) 로 정한 것이다.
여기서 equals()만 오버라이드하는 것이 아니라 hashCode()도 함께 오버라이드하는 이유는,
자바에서 동등성 비교 규칙을 일관되게 유지하기 위해서다.
특히 나중에 HashSet, HashMap 같은 해시 기반 컬렉션을 쓸 경우, equals()와 hashCode()를 함께 맞춰주는 것이 매우 중요하다.
상품 삭제하기
상품 삭제 기능은 수정보다 흐름이 더 단순하다.
수정은 기존 데이터를 새로운 데이터로 바꿔야 했지만,
삭제는 특정 상품 하나를 찾아서 없애면 되기 때문이다.
컨트롤러에서는 삭제 대상의 id를 URL 경로에서 받는다.
@RequestMapping(value = "/products/{id}", method = RequestMethod.DELETE)
public void deleteProduct(@PathVariable Long id){
simpleProductService.delete(id);
}
여기서 HTTP 메서드로 DELETE를 사용하는 이유는,
이 요청이 특정 자원을 제거하는 의미를 가지기 때문이다.
서비스 계층에서는 아주 단순하게 레포지토리의 삭제 메서드를 호출한다.
public void delete(Long id){
listProductRepository.delete(id);
}
지금은 별도의 추가 비즈니스 로직이 없기 때문에 서비스 코드가 단순해 보일 수 있다.
하지만 나중에 삭제 전에 존재 여부를 검증하거나, 삭제 이력을 남기거나,
삭제 가능 조건을 검사하는 규칙이 생기면 이런 계층이 훨씬 중요해질 수 있다.
레포지토리에서는 먼저 id로 상품을 찾고, 그 상품을 리스트에서 제거한다.
public void delete(Long id){
Product product = this.findById(id);
products.remove(product);
}
이 방식의 장점은 이미 구현해둔 findById()를 재사용할 수 있다는 점이다.
즉, 삭제를 위해 별도의 탐색 로직을 다시 만들지 않고, 먼저 조회한 뒤 그 결과를 제거하면 된다.
또 이 구조를 사용하면 상품이 존재하지 않을 경우 findById() 단계에서 예외가 발생할 수 있으므로,
존재하지 않는 상품 삭제에 대한 처리도 자연스럽게 연결된다.
'공부 기록 > 이것이 백엔드 개발이다 With Java' 카테고리의 다른 글
| EP19. 상품 관리 애플리케이션에 유효성 검사 추가하기 2편 (0) | 2026.04.21 |
|---|---|
| EP18. 상품 관리 애플리케이션에 유효성 검사 추가하기 1편 (0) | 2026.04.20 |
| EP16. List를 이용한 상품 관리 애플리케이션 만들기 4편 (0) | 2026.04.10 |
| EP15. List를 이용한 상품 관리 애플리케이션 만들기 3편 (0) | 2026.04.09 |
| EP14. List를 이용한 상품 관리 애플리케이션 만들기 2편 (0) | 2026.04.08 |