Item 카테고리별 분류

목표

아이템 별로 카테고리를 가지며, 상품 주문시에 카테고리를 선택하면, 그 카테고리에 해당하는 아이템들만 목록에 나타난다.

Category

Category 기능을 구현하기 위한 코드를 작성했다.

@Entity
@Getter @Setter
public class Category {


    @Id @GeneratedValue
    @Column(name = "category_id")
    private Long id;

    private String name;

    @OneToMany(mappedBy = "category")
    private List<Item> items = new ArrayList<>();

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "parent_id")
    private Category parent;

    @OneToMany(mappedBy = "parent",cascade = CascadeType.ALL)
    private List<Category> child = new ArrayList<>();

    public void addChild(Category category){
        this.child.add(category);
        category.setParent(this);
    }

    protected Category(){
    }
    
    public Category(String name) {
        this.name = name;
    }
    
    public Category(String name,Category categoryParent) {
        this.name = name;
        this.parent = categoryParent;
    }
}

설명

다른 방법이 떠오르지 않았기 때문에 Category 엔티티를 새로 생성했다.

코드의 복잡성을 줄이기 위해 하나의 아이템은 하나의 카테고리를 가진다고 가정했다.

@ManyToOne
    @JoinColumn(name = "category_id")
    private Category category;

Item 객체와 양방향 맵핑을 설정했다.

  • FetchType.LAZY

FetchType에는 EAGER 즉시로딩, LAZY 지연로딩이 있는데, 지연로딩을 사용해야만 한다.
즉시로딩의 경우는 엔티티를 조회할 때 관련된 엔티티를 모두 조회한다.
따라서 필요없는 엔티티도 모두 데이터 베이스에서 불러오는데, 이게 심각한 성능저하를 초래한다.

각 연관관계의 default 속성은 다음과 같다.
@OneToMany: LAZY
@ManyToOne: EAGER
@ManyToMany: LAZY
@OneToOne: EAGER

default가 EAGER인 연관관계는 꼭 LAZY로 설정해야 한다.

  • CascadeType.ALL

    CascadeType.ALL 이 붙은 속성은 해당 엔티티와 라이프 사이클을 함께하게 된다. 생성과 소멸을 엔티티와 함께 하기 때문에 라이프 사이클이 같은 경우에만 사용해야 한다.
  • addChild Method - 생성자 편의 메소드

    양방향 매핑에서 FK가 있는 쪽이 아닌 곳에서 값을 설정했을 때도 다른 쪽의 값을 수정하기 위한 편의 메소드이다.
    만약 그냥 Child.add로 child 를 추가하면 Parent Category 에 Child를 추가해도 Child Category 에는 Parent 가 설정되지 않는데 , 이 메소드를 통해 설정하게 되면 양쪽 모두 값이 설정된다.

  • Category() 생성자

- 오버로딩
name만 추가하는 생성자, name과 parent 를 요구하는 생성자로 매개변수를 다르게 하여 오버로딩을 써봤다.

- protected
이 기본 생성자는 Category 를 이름 없이 생성하는 것을 방지하면서, jpa에서는 기본생성자를 이용할 수 있게 해준다. jpa는 protected까진 접근 가능하기 때문이다.

Category Repository

@Repository
@Transactional
public class CategoryRepository {

    @PersistenceContext
    private EntityManager em;

    public void save(Category category){
        em.persist(category);
    }


    public Category findOne(Long id){
        return em.find(Category.class,id);
    }

    public List<Category> findChildCategory(){
        return em.createQuery("select c from Category c where c.parent is not null", Category.class)
                .getResultList();
    }
}

설명

  • findChildCategory()

    일단은
    Man
    top,trouser,shoe

Women
top,trouser,shoe
이렇게 두 단계로만 구성할 생각이라 createQuery문에서 parent 속성이 null이 아닌 경우에만 찾아오도록 쿼리를 작성하였다.
JPQL이기 때문에 c.parent와 같이 객체대상으로 쿼리를 작성할 수 있다.

프론트(View)

프론트는 수업에서 thymleaf 템플릿 엔진을 이용해서 제작했는데, 내 생각대로 처음부터 구현할 수 있을 정도로 thymeleaf를 공부하는 건 당장은 힘들 것 같았다.
그래서 기존에 진행했던 코드를 수정하고 추가해가면서 View 를 구성할 것이다.
Order에서 사용했던 view 코드를 가져와서 Category 선택 창을 만들었다.

@GetMapping("/items/new")
    public String createItem(Model model) {
        model.addAttribute("form",new ItemForm());

        List<Category> categories = categoryRepository.findChildCategory();
        model.addAttribute("categories", categories);

        return "items/newItem";
    }

model 에 child Category 만 담어서 view에 넘겨준다.

  • 실제 화면


    - 코드
<div th:replace="fragments/bodyHeader :: bodyHeader"/>
    <form th:action="@{/items/new}" th:object="${form}" method="post">
        <div class="form-group">
            <label for="category">카테고리</label>
            <select name="categoryId" id="category" class="form-control">
                <option value="">카테고리선택</option>
                <option th:each="category : ${categories}"
                        th:value="${category.id}"
                        th:text="${category.name}" />
            </select>
        </div>

좋은 웹페이지 즐겨찾기