3차 세미나를 진행하면서 DB, JPA에 대해서 간단한 이론과 실습을 진행하면서 여러가지 의문점들을 느껴 찾아찾아 들어가다 보니 여기까지 왔네용...
일단 제가 느낀 "의문점"에 대해서 설명드리고자 합니다
그전에 JPA의 연관관계 기본 fetch 전략을 좀 보고 가시죠
fetch의 기본전략
각 jpa의 연관관계 어노테이션은 기본 fetch 전략을 가지고 있습니다. 이러한 default setting은 "어느쪽의 전략이 더 효율적인가" 에 초점이 맞춰져 있습니다.
객체끼리의 연관관계 설정은?
그렇다면 이러한 연관관계는 어떻게 설정하면 되는걸까요? 연관된 양쪽 객체에 모두 이러한 어노테이션을 사용하면 될까요? 그러면 혹시라도 복잡한 관계에서 순환참조등이 일어나지는 않을까 다양하게 발생할수 있는 문제에 대해서 깊은 고민을 가질거 같습니다...(저라면?)
그래서 이러한 연관관계를 설정하는 방법은 부모-자식 입장에서 생각을 해보자는 겁니다. 저희가 한 내용인 blog, post 개념으로 생각을 해보죠.
blog객체는 부모, post 객체는 자식입니다. 하나의 blog에서 여러가지 post가 만들어질수 있기 때문이죠. 그렇다면 blog입장에서 post는 꼭 필요한 내용일까요? blog입장에서 생각을 해보면 post가 없는 blog가 만들어질수도 있겠죠? 하지만 post입장에서는 자신이 존재하기 위해서는 blog가 꼭 필요한 상태입니다.
즉 post를 생성하려면 blog가 꼭 필요하다! 따라서 post에서는 blog가 무조건적으로 들어가야 합니다. 따라서 이러한 연관관계를 바탕으로 아래그림처럼 post쪽에 @ManyToOne 방식으로 어노테이션을 붙여서 blog를 만들어주면 되는것이죠. 하지만 @ManyToOne 의 default fetch 전략은 EAGER 방식으로 모든 데이터를 가져오게 됩니다.
따라서 꼭 필요한 경우가 아니라면 fetch전략을 LAZY로 설정해 post 조회할때 blog의 정보는 굳이 가져오지 않아도 되게 설정할수도 있죠(저희가 세미나때 한방식입니다)
쿼리는 도대체 어떻게 날리는거야?
이거.. 그냥 이렇게 쓰면되는거야? 에 대한 의문점이죠..? ManyToOne 어노테이션을 사용해서 다른 db table과의 연관관계를 설정한다는 내용은 이해했습니다만 , 효율성 측면에서 생각해보면 문제가 일어나지 않을까?하는 생각으로 찾아보게 되었습니다.
첫번째로 Post 객체를 찾는 query를 날린다고 가정했을때 정상적으로 생각하면 1개의 query가 날아가는것이 맞다고 생각했습니다. 그에 대해서 여러가지 검색을 해본 결과 추가의 쿼리를 날릴수 있는 경우들이 발생할수 있다고 합니다.
Spring Data JPA의 findAll() 의 예를 한번 들어보겠습니다.
10 개의 Post 존재 , 5개의 Blog 존재
postRepository.findAll() => (다만 fetch.Lazy로 설정했을경우 다른 로직에서 blog를 찾는경우 실수발생경우입니다.)
- Post에 대한 전체조회 쿼리 1개 발생
- Post에 연결된 Blog id (ex=1) 조회 쿼리 , 즉 blog 1번에 대한 조회쿼리
- Post에 연결된 Blog id (ex=2) 조회 쿼리 , 즉 blog 2번에 대한 조회쿼리
- Post에 연결된 Blog id (ex=3) 조회 쿼리 , 즉 blog 3번에 대한 조회쿼리
- Post에 연결된 Blog id (ex=4) 조회 쿼리 , 즉 blog 4번에 대한 조회쿼리
- Post에 연결된 Blog id (ex=5) 조회 쿼리 , 즉 blog 5번에 대한 조회쿼리
이렇게 초록색 부분에 대한 쿼리가 추가로 나가게 됩니다. 이해되시나요? 이러한 문제를 1+N 문제라고 하고 JPA와 DB의 대한 이해 없이 사용할 경우 이러한 문제들이 발생해 query추가로 인한 서버 과부하 , 네트워크 latency등이 일어날수 있다고 합니다. 따라서 이러한 연관관계를 제거 하는 방법에 대해서 한번 찾아보도록 합시다.
이러한 추가 쿼리 발생 문제 해결 방법 3가지!!
- 똑똑한 LAZY 설정 방법
- 비즈니스 로직적으로 , 불필요한 연관관계 테이블 정보를 불러오는 부분을 제거하는 방법입니다.
- 가장 효율적이고 똑똑한 방법이죠..? 하지만 관계들이 복잡해질수록 어려워집니다. ㅠㅠ
- EAGER Fetch + Join JPQL
- 이러한 연관관계에서 추가 쿼리가 일어난다면 JOIN 쿼리를 직접 작성해주면 되겠죠?
- 예를 들어서 Spring 에서 사용할수 있는 방법에는 @Query 어노테이션으로 쿼리를 직접 작성하는 방식을 택하거나 querydsl , Jooq 같은 라이브러리를 통해서 JOIN 관계로 엮어줄수 있고 이를 통해서 영속성 컨텍스트에 알려줘 가져올수 있습니다. 그러나 join 쿼리로 가져오게 될 경우 데이터의 크기가 커지기 때문에 네트워크 비용, 속도 측면에서 효율적이 지 않을수 있으니 적절히 사용하는 방법이 중요합니다!!
- 나중에 querydsl와 jooq의 개념을 더 익힌후에 더 자세히 돌아오겠습니다~ 저도 아직은 잘모르겠어요 ㅎㅅㅎ
- 후속 쿼리를 in으로 묶어주기
- 1+N 문제를 N을 in으로 묶어주면서 1+1 문제로 해결이 가능합니다.
- query의 수가 줄어들기 때문에 DB에 접근하는 I/O 비용도 줄이수가 있다는 장점이 있죠
- 이는 위 방법들을 통해서 적용하기 어려운 경우에도 많이 사용하는 방법입니다.
- 어떻게 사용하나?
- 하이버네이트 프로퍼티부분을 조절해 사용이 가능합니다.
- 주로 100~1000사이 값을 사용하고 모든 쿼리에 적용되며 , 복잡한 도메인에서 join쿼리작성하기 어려울때 사용하는 방법중 하나입니다. 이렇게 설정하게 되면 위방식에서 POST에 대한 query때문에 5개가 추가로 발생했는데 100개 까지는 한번에 묶어서 1개의 query로 묶어버리는 방식입니다
- 이후 100개가 넘어갈경우 100 개 단위로 묶어서 100=> 1개 query로 나가는 방식입니다. 이해되셨죠?
요약
- 대부분의 연관관계설정에서 많은쪽 (자식)에서 부모쪽을 참조하는 FK를 가지는것이 효율성 측면에서 좋음
- ManyToMany연관관계는 mapping table(meta data)를 가지는 양쪽 id끼리의 mapping을 통해서 새로운 table 생성후 생성된 table을 통해 양쪽간의 연관관계를 조회하는것이 효율적임
- 예를 들어 "은행"은 여러 고객들을 가지고 "고객"은 여러 은행을 가질때 고객도 자신이 다니는 은행"들"을 알고있어야 하며 , 은행도 자신의 고객"들"을 알고있어야 하는 경우
- 고객 id : 은행 id, 의 새로운 table 생성을 통해 하나의 id로 찾을수있게 참조하는 것이좋음
- 추가 쿼리가 발생한다면 아직 JPQL의 fetch join을 짜지 못하는 경우는 hibernate 의 프로퍼티로 default_batch_size를 조절해서 사용한다면 추가적인 쿼리를 줄일수 있다.
'Spring' 카테고리의 다른 글
Spring 검색조회 필터링 구현 방법 [JPA Specification] (0) | 2024.07.31 |
---|---|
편리한 객체간 매핑을 위한 MapStruct 적용기 .feat 당근클론코딩 (1) | 2024.05.08 |
JPA - 단방향? 양방향? OneToMany? ManyToOne? (3) | 2024.04.29 |
SpringBoot와 Docker Container :Postgresql 연결하기! (2) | 2024.04.22 |
데이터베이스에 더미 데이터를 추가하는 방법 (5) | 2024.04.19 |