Web Framework) Simple eCommerce Application

Jay Kim·2022년 1월 17일
0

Web Framework

목록 보기
9/10
post-thumbnail

1. 개요

Product와 Category를 관리하는 Rest API를 구현한다.

2. 가정

1) Product는 다수개의 Category에 속할 수 있다. 즉, 전동 칫솔은 "Electronics" 와
"Beauty & Personal Care" 카테고리로 분류될 수 있다.
2) Category는 다수개의 SubCategory를 가질 수 있다. 예를 들어 “Electronics”
category는 'Audio & Video Components', 'Camera & Photo', ‘Computers’
SubCategory를 가질 수 있다.

3. Rest API

Products:

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를 삭제한다

Categories:

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에서 제
거한다

4. 데이터 베이스 초기화

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)컨트롤러 파일

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");
  }

}

<Rest API 테이블에 있는 모든 URL을 실행시켰을 때 postman 스크린샷>

(실행 결과 분량이 많은 것은 한 화면에 나오는 것만 스크린샷 하였습니다.)

1.Products:

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


2.Category:

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

0개의 댓글

관련 채용 정보