JPA 사용한 카테고리 구현 (infinite depth) - 01

Joshua_Kim·2021년 7월 24일
3
post-thumbnail

"민재씨 무한뎁스로 카테고리 로직 구현해봐요"

입사 후 공부하며 기술 스텍을 쌓던 내게 던져진 첫 실무 과제.

stack : Springboot 2.3.6 RELEASE, gradle, JPA, mysql ...


회사에 처음 들어와서 JPA와 Spring boot를 접한 나에게 실전 과제가 주어졌다 두둥.🤔
우선 전체적인 구조를 잡고 하나하나 클래스를 만들어 볼 생각이다.

#1. 요구조건 및 개발 순서 정리

  1. Entity 구현

    • 하나의 테이블에서 구현해야 하기 때문에 self join 사용하여 무한 뎁스 구현
    • level 필드를 구현하여 depth에 따라 level별 분류하도록 구현
      - @setter를 개발 초기에는 구현하지만, 후에 refactor 통해 없애기

  2. Repository 인터페이스 구현

    • Spring data JPA 활용 위해 JpaRepository를 extends

  3. DTO 구현

    • Entity는 순수하게 DB를 생성하고 DB하고만 소통하게 하기 위해 DTO구현
    • DTO는 비지니스 로직에서 사용하기 위해 구현
    • Entity에서 list로 담은 children을 DTO에서는 Map으로 구현 (stream, Collectors 사용)

  4. Service 구현

    • 비지니스 로직 !
    • save 메소드
      - branch의 상위 카테고리 첫 생성시 ROOT 생성
      • 상위 카테고리, 하위 카테고리 검색 후 각각의 상황에 맞게 save 구현
    • delete 메소드
      - 상위 카테고리, 하위 카테고리 검색 후 상황에 맞게 delete 구현
      • 하위 카테고리가 있는 상위 카테고리 삭제 시 name만 변경 하여 구분
    • get 메소드
      • branch로만 검색하여 하위 카테고리 모두 반환
  5. Service 메소드별 테스트 구현

    • 1)given 2)when 3)then 의 순으로 테스트 코드 구현
  6. Controller 구현

    • api 통신을 통해 각각의 반환 로직 구현

#2. 카테고리 Entity 구현

DB에 구현할 Category entity를 구현해보즈앗 !  😁


@Getter
@Setter
@NoArgsConstructor
@Entity
public class Category {

    @Id @GeneratedValue (strategy = GenerationType.IDENTITY)
    private Long id;

    @Column (nullable = false)
    private String branch;

    private String code;

    private String name;

    @ManyToOne (fetch = FetchType.LAZY)
    @JoinColumn (name ="parent_cagegory_id")
    private Category parentCategory;

    @OneToMany (mappedBy = "parentCategory", cascade = CascadeType.ALL)
    private List<Category> subCategory = new ArrayList<>();
   
    private Integer level;

    

    @Builder
    public Category(String branch, String code, String name, Integer level,Category parentCategory) {
        this.branch = branch;
        this.code = code;
        this.name = name;
        this.level = level;
        this.parentCategory = parentCategory;
    }
}

JPA를 사용한 Self join 생각해보면 간단하다.

key points

1. @ManyToOne (fetch = FetchType.LAZY)

  • JPA연관관계에서 'xToOne' (ManyToOne, OneToOne)은 fetchType.EAGER가 default다.

  • 지연 참조를 사용해줘야 n+1등 jpa 사용에 지장이 없다.

  • 습관적으로 fetch에 모두 LAZY를 발라주자 !! 😆

2. parentCategory와 subCategory

  • 서로 물고 물고 물고... 하기 위해 이 두 컬럼끼리 연관관계를 맺어준다.
  • mappedBy 속성을 통해 parentCategory를 연관관계의 owner로 설정해준다.

#3. Repository 인터페이스 구현

public interface CategoryRepository extends JpaRepository<Category, Long> {

    Optional<Category> findByName (String name);
    Optional<Category> findByBranchAndName (String branch, String name);

    Boolean existsByBranchAndName(String branch, String name);
}

key points

1. JpaRepository를 상속 받자!

  • 이건 뭐... spring data jpa를 사용하기 위해 필수니깐 🤔

2. findByBranchAndName

  • 진짜 spring data jpa는 감동인게 이렇게 메소드 이름명으로 검색 조건을 만들 수 있다.
  • parameter에 값만 제대로 넣어주면 직관적으로 find해준다.
  • detail> find + by + entity의 필드값 + and + entity의 필드값

3. existsByBranchAndName

  • boolean 타입으로 return 받는다.

  • DB를 탐색하여 있는지 없는지 요녀석을 통해 반환 받아서 로직을 짤 예정이다.

  • 이렇게 쓰는 법은 팀장님이 꿀팁으로 알려주심 팀장님 짱

다음 편에서는 DTO구현과 Service (save메소드) 구현을 포스팅하겠슴다 😊

profile
인문학 하는 개발자 💻

2개의 댓글

comment-user-thumbnail
2022년 4월 4일

딱 하고있던 건데! 감사해요 'ㅂ'

답글 달기
comment-user-thumbnail
2022년 10월 20일

상품이랑 카테고리와의 관계는 어떻게 해야되나요?

답글 달기