설명
데이터베이스와 연동하여 메뉴 구성을 진행 해보겠습니다.
스키마 생성 후 초기 데이터를 미리 생성 해두고 어플리케이션에서 데이터를 이용하여 메뉴 구성을 진행 합니다.
데이터베이스
스키마
이전 글을 참조해서 생성을 진행합니다.
데이터
초기 데이터를 아래의 SQL로 생성 합니다.
-- 의류
INSERT INTO category
(id, id_parent, title, icon, description, create_timestamp, update_timestamp)
VALUES(1, 0, '의류', 'fas fa-tshirt', '의류', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP);
INSERT INTO category
(id_parent, title, icon, description, create_timestamp, update_timestamp)
VALUES(1, '아우터', 'far fa-circle', '아우터', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP);
INSERT INTO category
(id_parent, title, icon, description, create_timestamp, update_timestamp)
VALUES(1, '티셔츠', 'far fa-circle', '티셔츠', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP);
INSERT INTO category
(id_parent, title, icon, description, create_timestamp, update_timestamp)
VALUES(1, '바지', 'far fa-circle', '바지', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP);
-- 신발
INSERT INTO category
(id, id_parent, title, icon, description, create_timestamp, update_timestamp)
VALUES(2, 0, '신발', 'fas fa-shoe-prints', '신발', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP);
INSERT INTO category
(id_parent, title, icon, description, create_timestamp, update_timestamp)
VALUES(2, '운동화', 'far fa-circle', '운동화', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP);
INSERT INTO category
(id_parent, title, icon, description, create_timestamp, update_timestamp)
VALUES(2, '로퍼', 'far fa-circle', '로퍼', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP);
INSERT INTO category
(id_parent, title, icon, description, create_timestamp, update_timestamp)
VALUES(2, '부츠', 'far fa-circle', '부츠', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP);
-- 악세사리
INSERT INTO category
(id, id_parent, title, icon, description, create_timestamp, update_timestamp)
VALUES(3, 0, '악세사리', 'fas fa-hat-wizard', '악세사리', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP);
INSERT INTO category
(id_parent, title, icon, description, create_timestamp, update_timestamp)
VALUES(3, '양말', 'far fa-circle', '양말', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP);
INSERT INTO category
(id_parent, title, icon, description, create_timestamp, update_timestamp)
VALUES(3, '모자', 'far fa-circle', '모자', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP);
INSERT INTO category
(id_parent, title, icon, description, create_timestamp, update_timestamp)
VALUES(3, '가방', 'far fa-circle', '가방', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP);
트리 구조로 생성되도록 합니다.
parent 가 되는 레코드는 id 값을 1~100 값을 갖도록 하고, id_parent 값은 0 값을 설정하여 부모 노드를 갖지 않도록 합니다.
child 가 되는 레코드는 id 값이 101 값부터 Auto Increment 하도록 하고, id_parent 값은 각각 카테고리 영역에 맞도록 부모 id 값을 지정합니다.
(최상위 메뉴는 100개까지 존재한다고 가정 하였습니다. 100개가 초과하는 경우 그 이상 갖도록 설정하셔도 좋습니다)
다음의 모습의 구조를 기대 합니다.
의류
├─아우터
├─티셔츠
└─바지
신발
├─운동화
├─로퍼
└─부츠
악세사리
├─양말
├─모자
└─가방
Back-End
Category.java
package happygram.ecommerce.jpa.domain;
import java.time.LocalDateTime;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import org.hibernate.annotations.CreationTimestamp;
import org.hibernate.annotations.UpdateTimestamp;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.ToString;
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@ToString
@Getter
@Entity
public class Category {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private Long idParent;
private String title;
private String icon;
private String description;
@CreationTimestamp
private LocalDateTime createTimestamp;
@UpdateTimestamp
private LocalDateTime updateTimestamp;
@Builder
public Category(Long idParent, String title, String icon, String description) {
this.idParent = idParent;
this.title = title;
this.icon = icon;
this.description = description;
}
}
데이터베이스 모델에 맞게 Entity 클래스를 작성합니다.
CategoryRepository.java
package happygram.ecommerce.jpa.repository;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import happygram.ecommerce.jpa.domain.Category;
@Repository
public interface CategoryRepository extends JpaRepository<Category, Long>{
}
JPA API 사용하여 기본적인 Repository 클래스를 작성합니다.
CategoryService.java
package happygram.ecommerce.service;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import happygram.ecommerce.constant.CategoryConstant;
import happygram.ecommerce.jpa.domain.Category;
import happygram.ecommerce.jpa.repository.CategoryRepository;
@Service
public class CategoryService {
@Autowired
private CategoryRepository categoryRepository;
public Map<Category, List<Category>> getCategory() {
List<Category> menuList = categoryRepository.findAll();
// Parent
List<Category> parentMenuList = menuList.stream()
.filter(category -> category.getIdParent() == CategoryConstant.PARENT_ROOT)
.collect(Collectors.toList())
;
// Child
Map<Long, List<Category>> childMenuMap = menuList.stream()
.filter(category -> category.getIdParent() != CategoryConstant.PARENT_ROOT)
.collect(Collectors.groupingBy(Category::getIdParent))
;
// TreeMenu
Map<Category, List<Category>> menuMap = new HashMap<>();
for(Category parent : parentMenuList){
Long id = parent.getId();
List<Category> child = childMenuMap.get(id);
menuMap.put(parent, child);
}
Map<Category, List<Category>> sortedMenuMap =
menuMap.entrySet().stream()
.sorted((e1, e2)-> e1.getKey().getId().compareTo(e2.getKey().getId()))
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (oldValue, newValue) -> oldValue, LinkedHashMap::new));
return sortedMenuMap;
}
}
Repository 에서 데이터를 가져와서 트리 구조로 만드는 로직을 구현 하였습니다.
현재는 2레벨 까지만 존재한다는 가정에서 로직을 구현 하였습니다. 썩 마음에 드는 로직은 아니지만, 현재는 이것이 최선이었네요.. ㅎㅎ
사실 로직 자체도 100% 마음에 들지는 않아서 나중에 해당 메소드는 다시 깔끔하게 구현할 예정입니다.
GlobalControllerAdvice.java
package happygram.ecommerce.config;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ModelAttribute;
import happygram.ecommerce.jpa.domain.Category;
import happygram.ecommerce.service.CategoryService;
@ControllerAdvice
public class GlobalControllerAdvice {
@Autowired
private CategoryService categoryService;
@ModelAttribute
public void handleRequest(HttpServletRequest request, Locale locale, Model model) {
String servletPath = request.getServletPath();
// view 페이지 호출할 때 메뉴 조립
if(!servletPath.isEmpty() &&
(
servletPath.equals("/") || servletPath.contains("/view/")
)
){
Map<Category, List<Category>> categoryList = categoryService.getCategory();
System.out.println(categoryList);
model.addAttribute("categories", categoryList);
}
}
}
모든 Rest API 호출 시 메뉴를 완성해서 제공하지 않을 것이기 때문에, 루트 패스 및 'view' 경로를 갖고 있는 경우에만 CategoryService 를 호출하도록 하였습니다.
Front-End
sidebar.html
<aside class="main-sidebar sidebar-dark-primary elevation-4">
<!-- Brand Logo -->
<a th:href="@{/}" class="brand-link text-center">
<span class="brand-text font-weight-light">Happygram</span>
</a>
<!-- Sidebar -->
<div class="sidebar">
<!-- Sidebar Menu -->
<nav class="mt-2">
<ul class="nav nav-pills nav-sidebar flex-column nav-child-indent" data-widget="treeview" role="menu" data-accordion="false">
<!-- Add icons to the links using the .nav-icon class
with font-awesome or any other icon font library -->
<li class="nav-item has-treeview" th:each="category : ${categories}">
<a href="#" class="nav-link">
<i th:class="nav-icon" th:classappend="${category.key.icon}"></i>
<p>
<span th:text="${category.key.title}" th:remove="tag"></span>
<i class="right fas fa-angle-left"></i>
</p>
</a>
<ul class="nav nav-treeview" th:each="child : ${category.value}">
<li class="nav-item">
<a href="@{/product/view/list}" class="nav-link">
<i class="nav-icon" th:classappend="${child.icon}"></i>
<p th:text="${child.title}"></p>
</a>
</li>
</ul>
</li>
</ul>
</nav>
<!-- /.sidebar-menu -->
</div>
<!-- /.sidebar -->
</aside>
ControllerAdvice 에서 전달받은 categories Key 를 반복문으로 처리하면서 메뉴를 완성하도록 하였습니다.
'IT 프로젝트 > 쇼핑몰 만들기' 카테고리의 다른 글
[Spring Boot] 스프링 부트 프로젝트/쇼핑몰 만들기 - 상품 상세 조회 (0) | 2020.02.09 |
---|---|
[Spring Boot] 스프링 부트 프로젝트/쇼핑몰 만들기 - 상품 목록 조회 (0) | 2020.01.25 |
[Spring Boot] 스프링 부트 프로젝트/쇼핑몰 만들기 - 화면 동적 로딩 (0) | 2020.01.07 |
[Spring Boot] 스프링 부트 프로젝트/쇼핑몰 만들기 - 화면 구성 (0) | 2020.01.05 |
[Spring Boot] 스프링 부트 프로젝트/쇼핑몰 만들기 - 개발 환경 구성 (Visual Studio Code) (2) | 2020.01.02 |