(24년 추가)
해당 글은 첫 취직 전 21년도에
프로젝트를 하면서 고민했던 부분에 대한 해결방법을 적은 것입니다.
그런데 막상 현업에 들어가보니, 애초에 join 을 여러번 하는 것 자체가 DB에 부하를 줄 수 있기 때문에
활발히 운영되는 Production 에서 이런 식의 코드는 지양되어야 하겠습니다.
쿼리를 여러번 날리던지 하고 서비스 코드 상으로 해결하는게 낫겠습니다.
JPA 책을 쓰신 김영한님의 책과 강의영상을 보면 실무에서 @ManyToMany 를 쓰지 말라는 조언을 강조하신다.
연결 테이블의 컬럼을 커스터마이징하지 못한다는 점이 실무에서 굉장히 크리티컬한 문제인가보다.
그런데 막상 연결테이블을 엔티티로 승격시켰더니 조회기능을 구현하기가 까다로웠다.
연결테이블 엔티티로 연결된 다대다 엔티티의 join 은 어떻게 하는걸까?
이를 알아보기 위해, 먼저 SQL 수준에서 다대다 join 문법을 알아보고, 이를 JPQL로 작성하는 방법을 알아보았다.
1. SQL 수준에서 다대다 테이블의 JOIN
최적의 SQL 문을 알아보기 위해서, 나는 JPA의 @ManyTo@Many 를 사용해보았다.
JPA가 @ManyToMany 에서의 조회연산을 어떻게 수행하는지 관찰하기 위해서이다.
나의 한미한 두뇌와 인터넷 서핑 실력으로는 최적의 SQL 문을 찾기 어려웠기에 JPA를 만드신 분들의 두뇌를 빌려본 셈?!
예제 : 다대다 단방향
User와 Tag가 다대다 관계이며, 다음과 같이 User에 List 필드를 넣었다.
@Entity
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false)
private String name;
...
@ManyToMany
@JoinTable(name = "user_tag", joinColumns = @JoinColumn(name = "user_id"), inverseJoinColumns = @JoinColumn(name = "tag_id"))
private List<Tag> tags = new ArrayList<>();
..
}
그리고 User 를 List와 함께 조회하는 패치 조인 연산을 수행해보았다.
String query = "SELECT user FROM User user LEFT JOIN FETCH user.tags "
+ "WHERE user.id = " + userId;
User result = entityManager.createQuery(query, User.class)
.getSingleResult();
그러자 다음과 같은 SQL이 실행되었다.
Hibernate:
select
user.id as id1_1_0_,
tag.id as id1_0_1_,
user.age as age2_1_0_,
user.name as name3_1_0_,
tag.text as text2_0_1_,
usertag.user_id as user_id1_1_0__,
usertag.tag_id as tag_id2_2_0__
from
User user
left outer join
user_tag usertag
on user.id=usertag.user_id
left outer join
Tag tag
on usertag.tag_id=tag.id
where
user.id=1
결과를 보면 join 이 두번 나오는 것을 확인할 수 있는데, 나는 이런 문법이 있다는걸 처음 알았다.
그래서 JOIN 을 두번 사용하는 문법에 대해서 공부할 필요가 있었다.
영어이긴 하지만 아래 사이트에서 친절하게 설명해주고 있다.
How to Join 3 Tables or More in SQL
https://learnsql.com/blog/how-to-join-3-tables-or-more-in-sql/
2. JPQL 작성하기
이제 SQL이 어떻게 돼야 하는지 알게되었다.
그렇다면 UserTag를 엔티티로 승격시켰을 때 이같은 SQL 문이 나가게 하려면 JPQL 을 어떻게 작성해야할까?
먼저 User 에서 List<UserTag> 를 갖도록 양방향 필드를 뚫어주어야 한다.
양방향은 주의해서 사용해야 한다고 하지만 그렇다고 이를 피할수는 없다. JPQL에서 조인을 하려면 연관 필드를 사용해야하기 때문이다.
@Entity
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false)
private String name;
@Column(nullable = true)
private Integer age;
@OneToMany(mappedBy = "user", fetch = FetchType.LAZY)
private List<UserTag> userTags = new ArrayList<>();
...
}
그런 다음 JPQL 은 다음과같이 작성하면 된다.
String query = "SELECT new practice.dto.UserWithTeamDto(user.id, user.name, tag.text) FROM User user "
+ "LEFT JOIN user.userTags usertag "
+ "LEFT JOIN usertag.tag tag "
+ "WHERE user.id = " + userId;
List<UserWithTeamDto> resultList = entityManager.createQuery(query, UserWithTeamDto.class)
.getResultList();
핵심은 FROM User user LEFT JOIN user.userTags usertag LEFT JOIN usertag.tag tag
이 부분이다.
이 JPQL 문을 알려준 스택오버플로우 링크를 첨부한다.
Join Multiple Tables with One JPQL Query
https://stackoverflow.com/questions/44144693/join-multiple-tables-with-one-jpql-query
- 끝 -
'JPA' 카테고리의 다른 글
[JPA] OneToMany join 데이터 중복 조회 문제 (0) | 2022.01.12 |
---|---|
[JPA] Casecade, orphanRemoval 에 대하여 (0) | 2021.08.05 |
[JPA] Gradle + H2 JPA 실습환경 셋팅 (1) | 2021.04.26 |