Product와 Category를 관리하는 Rest API를 구현한다.
1) Product는 다수개의 Category에 속할 수 있다. 즉, 전동 칫솔은 "Electronics" 와
"Beauty & Personal Care" 카테고리로 분류될 수 있다.
2) Category는 다수개의 SubCategory를 가질 수 있다. 예를 들어 “Electronics”
category는 'Audio & Video Components', 'Camera & Photo', ‘Computers’
SubCategory를 가질 수 있다.
1) Product CRUD ( URL: /api/products), ProductController.java
URL 설명
GET, /api/products 모든 product를 조회한다
GET, /api/products/{id} 특정 id를 가진 product를 조회한다
POST, /api/products
Body { "name": "P1", "price": 100.00 }
하나의 product을 생성한다
PUT, /api/products/{id}
Body { "name": "P1", "price": 100.00 }
하나의 product을 수정한다.
DELETE, /api/products/{id} 특정 id를 가진 product를 삭제한다
1) category CRUD ( URL: /api/categories), CategoryController.java
URL 설명
GET, /api/categories 모든 category를 조회한다
GET, /api/categories /{id} 특정 id를 가진 category를 조회한다
POST, /api/categories
Body { "name": "C1" }
하나의 category를 생성한다
PUT, /api/categories /{id}
Body { "name": "C1" }
하나의 category를 수정한다.
DELETE, /api/categories/{id} 특정 id를 가진 category를 삭제한다
2) Add / Remove child categories CategorySubcategoriesController.java
URL 설명
GET,
/api/categories/{parentid}/subcategories
특정 parentid를 가진 카테고리에
속한 자식카테고리를 조회한다
POST,
/api/categories/{parentid}/subcategories/{childid}
Parent category와 child category를
연결한다
DELETE,
/api/categories/{parentid}/subcategories/{childid}
Parent category에서 child category
를 제거한다
3) Link / Unlink products, CategoryProductsController.java
URL 설명
GET, /api/categories/{categoryid}/products 특정 id를 가진 category에
속한 모든 product를 조회한
다
POST,
/api/categories/{categoryid}/products/{productid}
Product를 category에 넣는
다
DELETE,
/api/categories/{categoryid}/products/{productid}
Product를 category에서 제
거한다
1) 데이터베이스를 생성한다: 이름은 ecommerce
2) 데이터베이스에 초기 데이터를 세팅한다.
data.sql 이 있는 폴더에서 아래 실행한 후, Mysql Workbench에서 확인
$ mysql –u root –p
$ use ecommerce;
$ source data.sql;
<설정파일>
1-1)dao-context.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:jpa="http://www.springframework.org/schema/data/jpa"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd
http://www.springframework.org/schema/data/jpa http://www.springframework.org/schema/data/jpa/spring-jpa.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.3.xsd">
<bean id="dataSource"
class="org.apache.commons.dbcp2.BasicDataSource"
destroy-method="close">
<property name="driverClassName"
value="${jdbc.driverClassName}" />
<property name="url" value="${jdbc.url}" />
<property name="username" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
</bean>
<context:annotation-config></context:annotation-config>
<context:property-placeholder
location="/WEB-INF/props/jdbc.properties" />
<bean id="sessionFactory"
class="org.springframework.orm.hibernate5.LocalSessionFactoryBean">
<property name="dataSource" ref="dataSource"></property>
<property name="packagesToScan">
<list>
<value>kr.ac.hansung.entity</value>
</list>
</property>
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">org.hibernate.dialect.MySQL8Dialect</prop>
<prop key="hibernate.hbm2ddl.auto">update</prop>
<prop key="hibernate.show_sql">true</prop>
<prop key="hibernate.format_sql">false</prop>
</props>
</property>
</bean>
<tx:annotation-driven
transaction-manager="transactionManager" />
<bean id="transactionManager"
class="org.springframework.orm.hibernate5.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory"></property>
</bean>
<context:component-scan
base-package="kr.ac.hansung.dao">
</context:component-scan>
</beans>
1-2)service-context.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd">
<context:component-scan base-package="kr.ac.hansung.service" />
<context:annotation-config></context:annotation-config>
</beans>
1-3)servlet-context.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/mvc"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:beans="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd
http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<!-- DispatcherServlet Context: defines this servlet's request-processing infrastructure -->
<!-- Enables the Spring MVC @Controller programming model -->
<annotation-driven />
<!-- Handles HTTP GET requests for /resources/** by efficiently serving up static resources in the ${webappRoot}/resources directory -->
<resources mapping="/resources/**" location="/resources/" />
<!-- Resolves views selected for rendering by @Controllers to .jsp resources in the /WEB-INF/views directory -->
<beans:bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<beans:property name="prefix" value="/WEB-INF/views/" />
<beans:property name="suffix" value=".jsp" />
</beans:bean>
<context:component-scan base-package="kr.ac.hansung.controller" />
</beans:beans>
2-1)ProductController.java
package kr.ac.hansung.controller;
import java.util.List;
import javax.validation.Valid;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import kr.ac.hansung.entity.Product;
import kr.ac.hansung.exception.NotFoundException;
import kr.ac.hansung.service.ProductService;
import lombok.Getter;
import lombok.Setter;
@RestController
@RequestMapping(path = "/api/products")
public class ProductController {
private final ProductService productService;
public ProductController(ProductService productService) {
this.productService = productService;
}
@RequestMapping(method = RequestMethod.GET)
public ResponseEntity<?> retrieveAllProducts() {
final List<Product> products = productService.getAllProducts();
if (products.isEmpty()) {
return new ResponseEntity<>(HttpStatus.NO_CONTENT);
}
return new ResponseEntity<List<Product>>(products, HttpStatus.OK);
}
/*
* 특정 id를 가진 product 를 조회한다
* */
@RequestMapping(path = "/{id}", method = RequestMethod.GET)
public ResponseEntity<Product> retrieveProduct(@PathVariable Long id) {
Product product = productService.getProductById(id);
if (product == null) {
throw new NotFoundException(id);
}
return ResponseEntity.ok(product);
}
@RequestMapping(method = RequestMethod.POST)
public ResponseEntity<Product> createProduct(@RequestBody @Valid ProductDto request) {
Product product = productService.createProduct(request.getName(), request.getPrice());
return new ResponseEntity<Product>(product, HttpStatus.CREATED);
}
/*
* 제품 업데이트
* 각 HTTP 메소드의 역할에 맞게
* PUT 메소드는 수정만 진행하고, 수정된 내용을 조회하려면 GET 메소드를 호출해서 확인하면 된다.
* */
@RequestMapping(path = "/{id}", method = RequestMethod.PUT)
public ResponseEntity<?> updateProduct(@PathVariable Long id, @RequestBody @Valid ProductDto request) {
Product product = productService.getProductById(id);
if (product == null) {
throw new NotFoundException(id);
}
product.setName(request.getName());
product.setPrice(request.getPrice());
productService.updateProduct(product);
return ResponseEntity.ok("update product success");
}
@RequestMapping(path = "/{id}", method = RequestMethod.DELETE)
public ResponseEntity<Void> deleteProduct(@PathVariable Long id) {
final Product product = productService.getProductById(id);
if (product == null) {
throw new NotFoundException(id);
}
productService.deleteProduct(product);
return new ResponseEntity<>(HttpStatus.NO_CONTENT);
}
@Getter
@Setter
static class ProductDto {
@NotNull(message = "name is required")
@Size(message = "name must be equal to or lower than 300", min = 1, max = 300)
private String name;
@NotNull(message = "name is required")
@Min(0)
private Double price;
}
}
2-2) CategoryControler.java
package kr.ac.hansung.controller;
import java.util.List;
import javax.validation.Valid;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import kr.ac.hansung.entity.Category;
import kr.ac.hansung.exception.NotFoundException;
import kr.ac.hansung.service.CategoryService;
@RestController
@RequestMapping(path = "/api/categories")
public class CategoryController {
private final CategoryService categoryService;
public CategoryController(CategoryService categoryService) {
this.categoryService = categoryService;
}
@RequestMapping(method = RequestMethod.GET)
public ResponseEntity<?> retrieveAllCategories() {
final List<Category> categories = categoryService.getAllCategories();
if (categories.isEmpty()) {
return new ResponseEntity<>(HttpStatus.NO_CONTENT);
}
return ResponseEntity.ok(categories);
}
/*
* 카테고리 개별 조회
* */
@RequestMapping(path = "/{id}", method = RequestMethod.GET)
public ResponseEntity<?> retrieveCategory(@PathVariable Long id) {
Category category = categoryService.getCategoryById(id);
if (category == null) {
throw new NotFoundException(id);
}
return ResponseEntity.ok(category);
}
@RequestMapping(method = RequestMethod.POST)
public ResponseEntity<?> createCategory(@RequestBody @Valid CategoryDto request) {
final Category category = categoryService.createCategory(request.getName());
return ResponseEntity.status(HttpStatus.CREATED).body(category);
}
/*
* 카테고리 업데이트
* */
@RequestMapping(path = "/{id}", method = RequestMethod.PUT)
public ResponseEntity<?> updateCategory(@PathVariable Long id, @RequestBody @Valid CategoryDto request) {
Category category = categoryService.getCategoryById(id);
if (category == null) {
throw new NotFoundException(id);
}
category.setName(request.getName());
categoryService.updateCategory(category);
return ResponseEntity.ok(category);
}
@RequestMapping(path = "/{id}", method = RequestMethod.DELETE)
public ResponseEntity<?> deleteCategory(@PathVariable Long id) {
final Category category = categoryService.getCategoryById(id);
if (category == null) {
throw new NotFoundException(id);
}
categoryService.deleteCategory(category);
return ResponseEntity.noContent().build();
}
static class CategoryDto {
@NotNull(message = "name is required")
@Size(message = "name must be equal to or lower than 100", min = 1, max = 100)
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
}
2-3)CategorySubcategoriesController.java
package kr.ac.hansung.controller;
import java.util.Set;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import kr.ac.hansung.entity.Category;
import kr.ac.hansung.exception.NotFoundException;
import kr.ac.hansung.service.CategoryService;
@RestController
@RequestMapping(path = "/api/categories/{parentid}/subcategories")
public class CategorySubcategoriesController {
private final CategoryService categoryService;
public CategorySubcategoriesController(CategoryService categoryService) {
this.categoryService = categoryService;
}
@RequestMapping(method = RequestMethod.GET)
public ResponseEntity<?> retrieveAllSubcategories(@PathVariable Long parentid) {
final Category parent = categoryService.getCategoryById(parentid);
if (parent == null) {
throw new NotFoundException(parentid);
}
final Set<Category> subcategories = parent.getChildCategories();
return ResponseEntity.ok(subcategories);
}
/*
* 자식 카테테고리에 부모 카테고리 정보를 연결시켜준다.
* app_product_category: 하이라키(계층) 구조 테이블
* */
@RequestMapping(path = "/{childid}", method = RequestMethod.POST)
public ResponseEntity<?> addSubcategory(@PathVariable Long parentid, @PathVariable Long childid) {
Category parentCategory = categoryService.getCategoryById(parentid);
if(parentCategory == null){
throw new NotFoundException(parentid);
}
Category childCategory = categoryService.getCategoryById(childid);
if(childCategory == null){
throw new NotFoundException(parentid);
}
categoryService.addChildCategory(childCategory, parentCategory);
return ResponseEntity.ok("add sub category success");
}
@RequestMapping(path = "/{childid}", method = RequestMethod.DELETE)
public ResponseEntity<?> removeSubcategory(@PathVariable Long parentid, @PathVariable Long childid) {
final Category parent = categoryService.getCategoryById(parentid);
if (parent == null) {
throw new NotFoundException(parentid);
}
final Category child = categoryService.getCategoryById(childid);
if (child == null) {
throw new NotFoundException(childid);
}
if (!categoryService.isChildCategory(child, parent)) {
throw new IllegalArgumentException("category " + parent.getId() + " does not contain subcategory " + child.getId());
}
categoryService.removeChildCategory(child, parent);
return ResponseEntity.noContent().build();
}
}
2-4)CategoryProductsController.java
package kr.ac.hansung.controller;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import kr.ac.hansung.entity.Category;
import kr.ac.hansung.entity.Product;
import kr.ac.hansung.exception.NotFoundException;
import kr.ac.hansung.service.CategoryService;
import kr.ac.hansung.service.ProductService;
@RestController
@RequestMapping(path = "/api/categories/{categoryid}/products")
public class CategoryProductsController {
private final CategoryService categoryService;
private final ProductService productService;
public CategoryProductsController(CategoryService categoryService, ProductService productService) {
this.categoryService = categoryService;
this.productService = productService;
}
/*
* 카테고리 + 카테고리에 속한 제품 목록 조회
* */
@RequestMapping(method = RequestMethod.GET)
public ResponseEntity<?> retrieveAllProducts(@PathVariable Long categoryid) {
Category category = categoryService.getCategoryById(categoryid);
if (category == null) {
throw new NotFoundException(categoryid);
}
return ResponseEntity.ok(category);
}
@RequestMapping(path = "/{productid}", method = RequestMethod.POST)
public ResponseEntity<?> addProduct(@PathVariable Long categoryid, @PathVariable Long productid) {
final Category category = categoryService.getCategoryById(categoryid);
if (category == null) {
throw new NotFoundException(categoryid);
}
final Product product = productService.getProductById(productid);
if (product == null) {
throw new NotFoundException(productid);
}
if (productService.hasCategory(product, category)) {
throw new IllegalArgumentException(
"product " + product.getId() + " already contains category " + category.getId());
}
productService.addCategory(product, category);
return ResponseEntity.status(HttpStatus.CREATED).body(product);
}
/*
* 카테고리에 속한 제품 제거
* */
@RequestMapping(path = "/{productid}", method = RequestMethod.DELETE)
public ResponseEntity<?> removeProduct(@PathVariable Long categoryid, @PathVariable Long productid) {
Category category = categoryService.getCategoryById(categoryid);
if (category == null) {
throw new NotFoundException(productid);
}
Product product = productService.getProductById(productid);
if (product == null) {
throw new NotFoundException(productid);
}
productService.removeCategory(product, category);
return ResponseEntity.ok("remove success");
}
}
(실행 결과 분량이 많은 것은 한 화면에 나오는 것만 스크린샷 하였습니다.)
1) Get, http://localhost:8080/ecommerce/api/products
2) Get, http://localhost:8080/ecommerce/api/products/1
3) Post, http://localhost:8080/ecommerce/api/products, id 37에 생성되는지 확인
4)Put, http://localhost:8080/ecommerce/api/products/37
5)Delete, http://localhost:8080/ecommerce/api/products/37
1-1)Get, http://localhost:8080/ecommerce/api/categories
1-2)Get, http://localhost:8080/ecommerce/api/categories/1
1-3) Post, http://localhost:8080/ecommerce/api/categories, id 18 카테고리 생성됨
1-4) Put, http://localhost:8080/ecommerce/api/categories/18
1-5) Delete, http://localhost:8080/ecommerce/api/categories/18
2-1) Get, http://localhost:8080/ecommerce/api/categories/1/subcategories
2-2) subcategory를 생성한 후(id=19), category(id=1)에 연결한다
Post, http://localhost:8080/ecommerce/api/categories
Post, http://localhost:8080/ecommerce/api/categories/1/subcategories/19
2-3) Delete, http://localhost:8080/ecommerce/api/categories/1/subcategories/19
3-1) Get, http://localhost:8080/ecommerce/api/categories/8/products 먼저 “Computer” 카테고리에 존재하는 Product조회한다
3-2) Product( id=39)를 생성한 후, “Computer” 카테고리에 저장한다.
(저는 id=39로 지정하였습니다.)
Post, http://localhost:8080/ecommerce/api/products
Post, http://localhost:8080/ecommerce/api/categories/8/products/39
3-3) Delete, http://localhost:8080/ecommerce/api/categories/8/products/39