본문 바로가기
개발서/ToyProject-Smart

[Toy - Smart] CategoryItem 리팩토링

by 사서T 2023. 2. 24.

CategoryItem 살펴보기

하위 카테고리는 맨투맨, 후드 티셔츠 등과 같이 하위 분류에 대한 정보를 저장하기 위한 클래스이다. 특징은 다음과 같다.

 

- Entity 객체이며 Category와 연관관계를 가지고 있다.

- 매개변수가 없는 생성자를 가진다.

- setCategory 메서드가 구현되어 있다.

 

@Entity
@Getter
@NoArgsConstructor
public class CategoryItem {

    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long categoryItemId;
    private String name;
    private String code;
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "category_id")
    private Category category;

    @Builder
    public CategoryItem(String name, String code) {
        this.name = name;
        this.code = code;
    }

    public void setCategory(Category category) {
        this.category = category;
    }
}

CategoryItem 분석하기

1. Anntation

 

- @Entity

기능 : 데이터 모델링의 객체로 DB 테이블 생성시 해당 클래스를 참조한다.

분석 : 해당 클래스는 Entity 객체로 사용하기 위해 구현되었기 때문에 적합하다.

 

- @Getter

기능 : 클래스에 선언된 필드에 대해 get 메서드를 자동 생성한다.

분석 : Entity 클래스의 경우 필드에 대한 접근 제한을 위해 private로 선언되었다. 따라서 필드 값에 접근하기 위한 별도의 방법이 필요하며 @Getter를 사용하는 방식을 채택하였다.

 

- @NoArgsConstructor

기능 : 매개변수가 없는 생성자를 자동 생성한다.

분석 : 클래스에서 따로 생성자를 정의하지 않은 경우 디폴트 생성자가 만들어진다. 따라서 굳이 선언할 필요가 없다. 하지만 JPA에서 필요로 하는 기본 생성자를 위해 선언한 경우 access 옵션을 PROTECTED로 설정하여 해당 Entity의 무분별한 생성을 방지할 수 있다.

 

- @Id

기능 : 해당 필드를 테이블의 기본키로 사용한다.

분석 : 기본키는 테이블 내에서 데이터를 구분할 수 있는 유일한 값으로 필수적으로 정의되어야 한다.

 

- @GeneratedValue(strategy = GenerationType.IDENTITY)

기능 : 기본키 생성 방식을 결정하는 Annotation으로 현재 IDENTITY 방식을 사용하고 있다.

분석 : IDENTITY 방식은 Entity를 DB에 저장 후 기본키를 생성하는 방식으로 트랜잭션의 쓰기 지연 기능을 사용할 수 없다. 즉, 영속성 컨텍스트로 등록하기 위해선 insert가 실행되어야 한다. SEQUENCE 방식은 시퀀스를 미리 메모리에 할당하고 DB에 접근없이 할당된 시퀀스를 기본키로 할당하는 방식이다. 이 경우 쓰기 지연 기능을 사용할 수 있지만 메모리를 차지한다는 단점이 존재한다. 각 방식마다 장단점이 존재하며 현재 서비스 상태에선 크게 고려할 점은 아니라고 판단하여 AUTO로 전략을 수정해두는 것이 적합하다고 판단한다.

 

- @ManyToOne(fetch = FetchType.LAZY) - category

기능 : 연관관계를 설정하여 repository를 통할 필요없이 해당 객체에 접근하는 것으로 데이터를 조회할 수 있다. EAGER로 설정한 경우 접근시가 아니라 category 조회시 join을 이용해 조회한다.

분석 : 현재 상태에선 CategoryItem을 조회하며 Category를 호출하는 경우는 거의 없다고 판단하였다. 따라서 EAGER보단 LAZY 설정을 유지하고 이후 기능 추가에 따라 fetch join 등의 다른 방법을 적용하는 것을 고려해는 것이 적절하다.

 

- @JoinColumn(name = "category_id")

기능 : join시 사용할 외래키 컬럼명을 지정하고 필드로 추가한다.

분석 : 연관관계 설정시 선언하는 Annotation으로 이후 구현 방식이 변경됨에 따라 제거될 가능성이 있다.

 

- @Builder

기능 : 클래스에 선언하는 경우 모든 필드를 포함하는 builder를 생성하며 매개변수가 선언된 메서드에 선언하면 해당하는 매개변수만을 초기화할 수 있는 builder를 생성한다. 매개변수 순서에 상관없이 필드의 초기화를 진행할 수 있다.

분석 : 클래스에 선언하는 경우 categoryItemId에 대한 접근이 가능해지기 때문에 위험하다. 따라서 초기화 가능한 필드를 매개변수로 받는 생성자를 구현후 해당 메서드에 선언해야 한다. @Builder로 선언한 메서드는 private로 지정하여 인스턴스 생성 방식을 제한해야 한다.


2. Field

 

- private Long categoryItemId

기능 : 기본키로 사용하기 위한 필드이다.

분석 : private는 필드에 대한 접근 제한을 위해 사용하였고, null을 확실히 구분하기 위해 Wrapper Long 사용하였다.

 

- private String name

기능 : 하위 카테고리 이름을 저장하기 위한 필드이다.

분석 : 필수적으로 입력해야 하는 정보이며 null과 blank가 들어올 수 없다. 해당 유효성 검증은 controller에서 진행되어야 하며, Entity에 @NotEmpty 등과 같은 유효성 검증을 넣어야 하는지 고민할 필요가 있다.

 

- private String code

기능 : 하위 카테고리 분류코드를 저장하기 위한 필드이다.

분석 : 필수적으로 입력해야 하는 정보이며 null과 blank가 들어올 수 없다. 해당 유효성 검증은 controller에서 진행되어야 하며, Entity에 @NotEmpty 등과 같은 유효성 검증을 넣어야 하는지 고민할 필요가 있다.

 

- private Category category

기능 : 카테고리와 연관관계에 있는 하위 카테고리를 저장하기 위한 필드이다.

분석 : 하위 카테고리와 카테고리는 밀접한 관련이 있기에 연관관계를 적용하는 것이 적합하다고 판단하였다.


3. Method

 

- public CategoryItem(String name, String code)

기능 : 매개변수로 받은 값을 이용해 생성할 CategoryItem 인스턴스의 필드 값을 초기화한다.

분석 : 해당 생성자는 @Builder가 선언된 생성자로 현재 접근 제어자가 public되어 있어 builder를 이용한 생성뿐아니라 new 키워드를 이용한 생성 또한 가능한 상태이다. 인스턴스 생성에 있어 통일성을 주기위해 private로 선언해야 한다.

 

- public void setCategory(Category category)

기능 : 매개변수로 받은 Category를 CategoryItem 상위 분류로 저장한다.

분석 : 해당 메서드는 Category에서 CategoryItem을 list에 저장할 때 호출된다. 따라서 접근 제한을 둘 필요가 있다고 판단하여 public 보단 default로 설정하는 것이 더 안전하다고 생각한다.


CategoryItem 리팩토링

수정된 경우  색, 추가된 경우  색, 삭제된 경우  색으로 표시하였다. 수정된 코드는 다음과 같다.

 

@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class CategoryItem {

    @Id @GeneratedValue
    private Long categoryItemId;
    private String name;
    private String code;
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "category_id")
    private Category category;

    @Builder
    private CategoryItem(String name, String code) {
        this.name = name;
        this.code = code;
    }

    void setCategory(Category category) {
        this.category = category;
    }
}

 

1. Annotaion

 

- @NoArgsConstructor(access = AccessLevel.PROTECTED)

access 옵션을 추가하여 CategoryItem의 생성을 제한하였다. 이제 JPA와 상속 대상만 사용이 가능하며, CategoryItem의 생성은 Builder를 통해서만 가능해졌다.

 

- @GeneratedValue

현재 상태에선 시퀀스 효율성에 대해서 배제하고 진행하기로 하였다. 따라서 strategy 옵션을 제거하였다.


2. Field


3. Method

 

- private CategoryItem(String code, String name)

이미 builder를 이용한 인스턴스 생성이 가능하기 때문에 해당 생성자를 이용한 인스턴스 생성은 필요없다고 판단되어 public 접근 제어자를 private로 변경하였다.

 

- void setCategory(Category category)

새로운 CategoryItem이 추가되는 경우 Category에서 addCategoryItem 메서드가 사용된다. 연관관계 설정 시점은 한 메서드에서 관리되어야 하며 이는 좀 더 상위 카테고리에서 수행되는 것이 적절하다고 판단하였다. 따라서 setCategory 메서드는 외부에서의 접근이 제한되어야 하며 같은 패키지에 존재하는 Category Entity에서는 접근이 가능해야 하기에 deafulat 접근 제어자를 설정하였다.

 

 

 

Link

댓글