Lined Notebook

JPA 에서 Entity간 연관관계 맺기는 조심해야 한다

by Understand

회사에서 JPA를 사용하면서 그리고 여러 자료를 보면서 JPA에서 Entity 연관관계를 사용할 때 주의해야 함을 알게 되었다. 여러 연관관계를 사용할 때 어떤 문제가 있는지 정리하고, 내 나름대로 연관 관계를 거는 기준을 설정해보려고 한다.

 

주의해야할 연관관계

연관관계를 맺을 때, 양방향 연관관계, OneToMany 연관관계, OneToOne 연관관계는 사용할 때 있어 주의해야 한다.
그 이유를 알아보자

 

양방향 연관관계

양방향 연관관계는 두 Entity가 서로의 참조자를 갖도록 설정한 연관관계이다.
단방향 연관관계를 설정하면 두 Table간 연관관계 설정이 반영된다. 따라서 양방향 연관관계는 Table에는 영향을 주지 않는다. 양방향 연관관계는 주로 탐색 편의성을 높여 조금 더 객체지향적인 개발을 하기 위해 사용한다. 하지만 오히려 양방향 연관관계를 사용하면 오히려 설계가 불필요하게 복잡도가 올라가 개발 편의성을 떨어뜨리는 경우가 많다.

양방향 연관관계를 사용하게 되면 하나의 Entity의 CRUD를 개발할 때 나머지 Entity를 고려해야 한다. 그렇지 않으면 순환의존성에 의해 무한 루프가 발생하거나 두 Entity의 참조가 불일치할 수도 있기 때문이다. 이로 인해 두 Entity 관계의 설계/관리 복잡도가 높아지고 유연성이 떨어지게 된다.

 

One-To-Many 연관관계

One-To-Many 연관관계는 이름대로 one-to-many 관계의 두 Entity에서 one인 측이 Many 측 Entity의 복수개의 참조자를 갖도록 설정한 연관관계이다.

일반적으로 One-To-Many 연관관계를 사용하는 것보다 Many-To-One 연관관계를 사용하는 것을 권장한다. One-To-Many는 많은 문제를 야기할 수 있기 때문이다.

기본적으로 One-To-Many는 성능의 문제가 있다. Lazy loading을 사용하게 되면 N+1 문제가 발생한다. 그렇다고 Eager loading을 사용하게 되면 불필요한 데이터를 검색해 성능 문제가 발생한다. 이러한 문제는 Fetch 전략과 Cascade 설정을 사용해 완화할 수는 있지만 여전히 본질적인 문제는 해결하기 어렵다.
그 외에 pagination이 어렵거나, Join시 hibernate.bag 관련한 에러등으로 인한 관리의 어려움이 있다.

하지만 One-To-Many 연관관계는 one인 측이 참조자리스트를 가지 않고 many인 측이 참조자를 갖는 Many-To-One 연관관계로 치환하여 설정할 수 있다. 따라서 상대적으로 복잡도가 낮은 Many-To-One 연관관계를 사용하는 것을 권장한다.

 

One-To-One 연관관계

One-To-One 연관관계는 하나의 Entity가 다른 Entity의 하나의 참조자만을 갖도로 설정한 연관관계이다.

One-To-One 은 양방향 연관관계에서 연관관계의 주인이 아닌쪽의 Entity를 조회할 때 Lazy loading이 제대로 작동하지 않는다. 이로 인해 코드에는 FetchType을 lazy로 설정했지만 의도치 않게 N+1 문제가 발생할수 있다.

이 문제는 JPA 구현상의 문제이다. JPA에서는 Lazy loading을 위해 프록시를 만들어줘야 하고, Entity에 연관관계 참조필드에 null 또는 프록시 객체를 할당해야 한다. One-To-One 연관관계에서 연관관계의 주인은 참조할 대상이 존재하지는지 알기 때문에 없으면 참조필드에 null을, 있으면 프록시 객체를 할당하면 된다. 하지만 연관관계의 주인이 아닌 Entity는 참조할 대상이 존재하는지 알 수 없기 때문에 참조필드에 값에 정확한 값을 할당할 수 없다. 참조필드에 null을 넣으면 연관 Entity를 가져올수 없고, 프록시 객체를 할당했는데 연관 Entity가 없는 경우 null을 리턴할 수 없다. 따라서 조회하는 쿼리를 날려 존재여부를 확인하다. 즉, Eager loading을 하게 되는 것이다.

(One-To-Many는 초기화할때 Empty list로 초기화하기 때문에 이런 문제를 방지할 수 있다.)

두 Entity의 생명주기가 같은 경우 Optional=False로 설정해 lazy loading이 가능하게 만들 수 있다. 이 경우 굳이 eager loading을 하지 않더라도 항상 존재한다고 가정해 프록시 객체를 만들수 있기 때문이다. 하지만 이 또한 관리의 복잡도가 증가하게 된다.

 

연관관계를 설정하는 기준

연관관계의 주의점을 작성하며 오히려 과도한 편의성이 설계 복잡도를 높일 수 있다는 것을 알 수 있었다.
이를 토대로 코드를 작성할 때 다음과 같이 작성하면 좋을 것 같다.

  1. 단방향 ManyToOne으로 연관관계를 설정한다.
  2. 두 테이블의 생명주기가 완전히 같으면 One-To-Many / One-To-One을 고민한다. (되도록이면 사용하지 않는다)
  3. 되도록 양방향 연관관계를 설정하지 않는다

 

Reference

'공부' 카테고리의 다른 글

클린 코드를 싫어하는 사람들  (0) 2024.01.21
Inner Join Internally  (0) 2024.01.06
Python Dependency Injection  (0) 2023.12.24
FastAPI는 어떻게 타입을 검증할까?  (0) 2023.03.12

블로그의 정보

BookStoreDiary

Understand

활동하기