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을 하지 않더라도 항상 존재한다고 가정해 프록시 객체를 만들수 있기 때문이다. 하지만 이 또한 관리의 복잡도가 증가하게 된다.
연관관계를 설정하는 기준
연관관계의 주의점을 작성하며 오히려 과도한 편의성이 설계 복잡도를 높일 수 있다는 것을 알 수 있었다.
이를 토대로 코드를 작성할 때 다음과 같이 작성하면 좋을 것 같다.
- 단방향 ManyToOne으로 연관관계를 설정한다.
- 두 테이블의 생명주기가 완전히 같으면 One-To-Many / One-To-One을 고민한다. (되도록이면 사용하지 않는다)
- 되도록 양방향 연관관계를 설정하지 않는다
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