개발일상/TIL

[230910] JPA에서 부모가 자식을 제한해서 가지는 경우

sechoi 2023. 9. 10. 17:33

✅ JPA에서 부모가 자식을 제한해서 가지는 경우

상황 설명

(제목 쓰는 것부터 헷갈리네...)

일반적으로 데이터베이스에서 fk로 부모와 자식의 관계를 맺는 경우, 자식이 해당 부모의 식별자를 가지고 있기만 하면 된다. 즉 부모가 가질 수 있는 자식의 수에는 제한이 없다. JPA에서도 마찬가지이다. 따라서 보통 @OneToMany로 부모 쪽에서 자식을 참조할 때는 List 등 크기 제한이 없는 컬렉션을 사용한다.

 

@Entity
public class Parent {
    
    @OneToMany
    private List<Child> childs = new ArrayList<>();
}

 

하지만 특정 기준에 따라 제한적으로 자식을 가지고 싶다면 어떻게 될까?

 

현재 내가 참여한 프로젝트에서는 '가계부 내역'과 '카테고리'가 존재한다. 가계부 내역 하나 당 카테고리를 설정할 수가 있는데, 단순히 하나의 카테고리를 지정하는 게 아니다. 카테고리는 대분류로 '수입', '지출', '이체'가 존재한다. 그리고 그것의 하위 분류로 사용자가 자유롭게 카테고리를 지정한다. 마지막으로 해당 내역의 '자산'을 설정한다.

 

예시 가계부 내역

즉 가계부 내역은 각 분류에 해당하는 카테고리만 (총 3개) 가져야 한다. 

 

물론 일반적인 방법을 그대로 따라 자식을 컬렉션으로 가져도 된다. ManyToOne 단방향 참조만 해도 된다. 하지만 이럴 경우 특정 가계부 내역의 카테고리를 조회 혹은 변경할 때 매번 유효성 검사가 필요하고, 조회도 번거롭다.

 

고안한 방법

 

따라서 우리 프로젝트에서는 다음과 같이 자식을 Map으로 관리한다.

 

@Entity
public class BookLine {

    @OneToMany(fetch = FetchType.LAZY)
    private Map<CategoryEnum, BookLineCategory> bookLineCategories = new EnumMap<>(CategoryEnum.class);
}
@Entity
public class BookLineCategory {

    @ManyToOne(fetch = FetchType.LAZY)
    private BookLine bookLine;
}

여기서 CategoryEnum에는 위에서 설명한 세 종류의 카테고리 분류가 존재한다.

 

그리고 가계부 내역에 카테고리를 알맞게 넣어서 데이터베이스에 저장하면

 

이렇게 하나의 가계부 내역에 대해 의도한대로 총 세 개의 카테고리가 추가된다.

 

key-value 관계의 행방

하지만 이것만 봐서는 자식을 List로 저장할 때와 다를바가 없다. 해당 BookLineCategory가 어떤 분류에 해당하는지, 이체가 어떤 분류에 해당하는 지 알 수 없다.

 

그 때 보이는 하나의 테이블... 🕵️‍♀️ 너 뉘기야

현재의 양방향 관계에서 mappedBy를 걸어주지 않았기 때문에 생겨난 테이블이다. 즉 두 테이블 간의 매핑 정보를 담은 테이블인데... 

 

찾았다!

book_line_categories_key 컬럼으로 book_line_category가 가지는 key 정보를 저장해놓았다. 그래서 BookLine을 JPA를 통해 조회하면 이 매핑 정보를 참고해 알아서 bookLineCategories Map에 자식들을 착착 넣어준다.

 

호기심 해결: mappedBy를 걸어주면...?

그럼 만약에 mappedBy를 걸어줘 추가 테이블이 없으면 어떻게 될까?

Parent와 Child라는 테스트용 엔티티를 만들고 Parent 안 자식 Map에 mappedBy 속성을 걸어주었다.

 

그랬더니 Child 테이블에 childs_key라는 새로운 컬럼이 생겼다!

key-value 매핑정보를 저장해야하는데, 부모 테이블에는 정보를 저장할 수 없으므로 자식 테이블에 컬럼으로 저장한 것이다.

 

느낀 점: 아는 것이 힘이다

처음에 이 사실을 모르고 세 개의 row로 구성된 카테고리들을 하나의 row로 묶기 위해 셀프 조인을 사용하려고 했다. 그런데 쿼리를 짜다보니 5중... 7중... 점점 조인하는 테이블의 개수가 늘어나서 이게 맞나? 싶었는데... 역시 아니었다. 아직도 모르는게 많다.