UserGrammarStatus와 SiteUser는 N : 1 단방향 연관 관계를 맺고 있습니다.
UserGrammarStatus 엔티티에서 referencedColumnName = "user_name"
를 지정해주어 SiteUser 테이블의 userName 컬럼을 참조하게 했습니다.
//UserGrammarStatus
...
@ManyToOne
@JoinColumn(name = "user_name", referencedColumnName = "user_name")
private SiteUser user;
...
그리고 userName값으로 UserGrammarStatus를 조회하는 JPQL을 직접 작성했습니다.
//UserGrammarStatusRepository
...
@Query("select ugs from UserGrammarStatus ugs where ugs.user.userName = :userName")
List<UserGrammarStatus> findByUserName(@Param("userName") String userName);
...
위 메서드를 실행하면 아래와 같은 쿼리가 나갑니다.
Hibernate:
select
ugs1_0.id,
ugs1_0.last_try_time,
ugs1_0.user_name,
ugs1_0.wrong_grammar_id
from
user_grammar_status ugs1_0
where
ugs1_0.user_name=?
Hibernate:
select
su1_0.id,
su1_0.create_user_date,
su1_0.email,
su1_0.password,
su1_0.phone_number,
su1_0.point,
su1_0.role,
su1_0.token,
su1_0.user_name
from
site_user su1_0
where
su1_0.user_name=?
처음에는 SiteUser까지 조회해와서 성능이 떨어질 거라 판단했습니다.
그래서 UserGrammarStatus 엔티티의 referencedColumnName = "user_name"
를 없앴습니다.
그리고 userName으로 SiteUser엔티티의 id를 가져와서 이 id로 UserGrammarStatus를 조회하도록 수정해봤습니다.
//UserGrammarStatus
...
@ManyToOne
private SiteUser user;
...
//UserGrammarStatusRepository
...
Optional<List<UserGrammarStatus>> findByUserId(Long userId);
...
그러면 아래와 같은 쿼리가 나갑니다.
Hibernate:
select
su1_0.id,
su1_0.create_user_date,
su1_0.email,
su1_0.password,
su1_0.phone_number,
su1_0.point,
su1_0.role,
su1_0.token,
su1_0.user_name
from
site_user su1_0
where
su1_0.user_name=?
Hibernate:
select
ugs1_0.id,
ugs1_0.last_try_time,
ugs1_0.user_id,
ugs1_0.wrong_grammar_id
from
user_grammar_status ugs1_0
where
ugs1_0.user_id=?
userName을 FK로 지정했을때와 쿼리 순서가 바뀐걸 알 수 있습니다.
그 이유는 UserGrammarStatus는 SiteUser를 참조하고 있어 UserGrammarStatus를 조회할때마다 반드시 SiteUser를 불러오기 때문입니다.
FK로 userName을 참조할때는 조건 값인 userName으로 UserGrammarStatus를 먼저 조회 후, SiteUser를 불러옵니다.
FK로 SiteUser의 id(PK)를 참조할때 먼저 SiteUser를 userName으로 조회해서 id값을 가져오고, 이 값으로 UserGrammarStatus를 조회했습니다. 왜냐하면 UserGrammarStatus의 N : 1 단방향 연관 관계는 기본적으로 참조하는 엔티티의 PK를 FK로 갖기 때문입니다.
두 경우 모두 같은 userName값에 대해 10000번의 조회시 평균 실행 시간은 2ms였습니다. 성능에 별 차이가 없었습니다.
데이터베이스 관점에서 userName을 FK로 두기 보다는 PK값을 FK로 두는게 공간을 덜 소비할 것이라 생각해 SiteUser의 PK값을 참조하기로 했습니다.
'Springboot' 카테고리의 다른 글
[트러블 슈팅]LazyInitializationException 간단 해결법 (0) | 2024.02.24 |
---|---|
[트러블 슈팅]N : 1 양방향 연관 관계에서 주인이 아닌쪽에서 참조 시 null값이 포함되는 문제 (1) | 2024.02.21 |
[트러블 슈팅]fetch join 2번 이상 사용시 `MultipleBagFetchException`발생 (0) | 2024.02.21 |
스프링부트 테스트 시 리액트 앱 빌드 건너뛰기 (0) | 2024.02.03 |
React & Springboot 통합 빌드시 Whitelabel Error 해결 (0) | 2024.01.24 |