(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/

 

How to Join 3 Tables (or More) in SQL

Have you ever wondered how to join three tables in SQL? It's easy when you know the basics. Joining three tables can be as easy as joining two tables.

learnsql.com

 

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

 

Join multiple tables with one JPQL query

I got this sql-query I want to create as query in JPQL but I cannot get it right. I got a manytoone relationship between QuestionAnswers and QuizQuestions: SQL: SELECT quizName, question, answer ...

stackoverflow.com

 

 

- 끝 -

+ Recent posts