ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • JPA QueryDSL 시작해보기
    Java/JPA 2019. 7. 11. 22:41
    반응형

     회사에서 Criteria를 사용하다 보니 그동안 jpa를 사용하면서 복잡한 쿼리는 Criteria를 이용하여 작성하였습니다. 하지만 많은 회사들이 QueryDSL을 사용하고 있고, Criteria보다 더 직관적인 코드를 작성할 수 있다기에 QueryDSL에 대해서 공부해보려고 합니다. :)


     

     

     우선 위와 같은 의존성을 설정하여 프로젝트를 생성하였습니다. 데이터베이스는 따로 구축하지 않고 편하게 테스트하기 위해 H2 Database를 이용하려고 합니다.

     

     

     

     

     


    application.yml

    spring:
      profiles:
        active: local # 기본 환경 선택
      datasource:
        data: classpath:data-h2.sql
    
    # local 환경
    ---
    spring:
      profiles: local
      jpa:
        show-sql: true
        hibernate:
          ddl-auto: create-drop
        database-platform: org.hibernate.dialect.H2Dialect
        properties:
          hibernate.format_sql: true
      h2:
        console:
          enabled: true
    logging:
      level:
        org:
          hibernate:
            type: trace

     jpa가 실행될 때, 어떤 SQL이 실행되는지 로그를 찍어주기 위해 application.yml 파일에 설정해주었습니다.

     data-h2.sql에는 인메모리 데이터베이스인 h2를 사용하기 때문에 테스트의 편의성을 위해서 데이터를 미리 넣어놓도록 쿼리를 작성해두었습니다. 바로 아래에 이어서 data-h2.sql 파일을 보도록 하겠습니다.

     

     

    data-h2.sql

    INSERT INTO TEAM(team_name, award_count) values ('서울', 18);
    INSERT INTO TEAM(team_name, award_count) values ('대전', 3);
    INSERT INTO TEAM(team_name, award_count) values ('부산', 10);
    INSERT INTO TEAM(team_name, award_count) values ('로스앤젤레스', 4);
    INSERT INTO TEAM(team_name, award_count) values ('워싱턴', 7);
    INSERT INTO TEAM(team_name, award_count) values ('뉴욕', 6);
    INSERT INTO TEAM(team_name, award_count) values ('라스베이거스', 9);
    INSERT INTO TEAM(team_name, award_count) values ('타이완', 3);
    INSERT INTO TEAM(team_name, award_count) values ('도쿄', 1);
    INSERT INTO TEAM(team_name, award_count) values ('상하이', 12);
    INSERT INTO TEAM(team_name, award_count) values ('홍콩', 9);
    INSERT INTO TEAM(team_name, award_count) values ('항저우', 5);
    INSERT INTO TEAM(team_name, award_count) values ('대구', 14);
    INSERT INTO TEAM(team_name, award_count) values ('속초', 11);
    INSERT INTO TEAM(team_name, award_count) values ('광주', 8);
    
    
    INSERT INTO MEMBER(name, age, team_id) values('아이유', 27, 1);
    INSERT INTO MEMBER(name, age, team_id) values('레드벨벳', 24, 1);
    INSERT INTO MEMBER(name, age, team_id) values('악동뮤지션', 21, 1);
    INSERT INTO MEMBER(name, age, team_id) values('윤하', 22, 1);
    INSERT INTO MEMBER(name, age, team_id) values('소녀시대', 12, 1);
    INSERT INTO MEMBER(name, age, team_id) values('제이플라', 15, 1);
    INSERT INTO MEMBER(name, age, team_id) values('비투비', 30, 2);
    INSERT INTO MEMBER(name, age, team_id) values('악동뮤지션', 18, 5);
    INSERT INTO MEMBER(name, age, team_id) values('씨야', 12, 4);
    INSERT INTO MEMBER(name, age, team_id) values('방탄소년단', 26, 13);
    INSERT INTO MEMBER(name, age, team_id) values('윤종신', 36, 12);
    INSERT INTO MEMBER(name, age, team_id) values('마마무', 31, 8);
    INSERT INTO MEMBER(name, age, team_id) values('트와이스', 19, 7);
    INSERT INTO MEMBER(name, age, team_id) values('엑소', 33, 9);
    INSERT INTO MEMBER(name, age, team_id) values('태연', 32, 9);
    INSERT INTO MEMBER(name, age, team_id) values('멜로망', 16, 10);
    INSERT INTO MEMBER(name, age, team_id) values('백아연', 32, 6);
    INSERT INTO MEMBER(name, age, team_id) values('정승환', 23, 6);
    INSERT INTO MEMBER(name, age, team_id) values('벤', 20, 7);
    INSERT INTO MEMBER(name, age, team_id) values('럼블피쉬', 35, 8);
    INSERT INTO MEMBER(name, age, team_id) values('이수', 15, 15);
    INSERT INTO MEMBER(name, age, team_id) values('임재현', 38, 14);

    엔티티로는 Team, Member가 있습니다. 하나의 Team에는 여러 Member가 속할 수 있습니다. 즉,

    Team, Member => OneToMany   |    Member, Team => ManyToOne 관계입니다.

    팀 이름이나 멤버 이름, 나이 등은 그냥 막 적어 넣었습니다. 실제로는 아무 관계없습니다 ㅎㅎ;

     

     

    Team Entity

    package com.edu.querydsl_training.domain;
    
    import lombok.Builder;
    import lombok.Getter;
    import lombok.NoArgsConstructor;
    
    import javax.persistence.*;
    
    @Getter
    @Entity
    @NoArgsConstructor
    public class Team {
    
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        private Long teamId;
    
        @Column(length = 20)
        private String teamName;
    
        @Column
        private Long awardCount;
    
        @Builder
        public Team(String teamName, Long awardCount) {
            this.teamName = teamName;
            this.awardCount = awardCount;
        }
    }

    Team 엔티티는 Long 타입의 기본키를 가지고 Auto Increament가 되도록 설정하였습니다. awardCount는 해당 팀의 수상 횟수입니다.

     

    Member Entity

    package com.edu.querydsl_training.domain;
    
    import lombok.Builder;
    import lombok.Getter;
    import lombok.NoArgsConstructor;
    
    import javax.persistence.*;
    
    @Getter
    @Entity
    @NoArgsConstructor
    public class Member {
    
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        private Long memberId;
    
        @Column(length = 10)
        private String name;
    
        @Column
        private Long age;
    
        @ManyToOne(fetch = FetchType.LAZY)
        @JoinColumn(name = "teamId", updatable = false)
        private Team team;
    
        @Builder
        public Member(String name, Long age, Team team) {
            this.name = name;
            this.age = age;
            this.team = team;
        }
    }

    Member 엔티티도 마찬가지로 Long 타입의 기본키를 가지며 Auto Increament를 통해 자동으로 증가하도록 하였습니다.

    ManyToOne 관계인 Team 엔티티에 대해서는 Lazy 로딩을 하도록 설정하였습니다.

     


     

    이제 QueryDSL 의존성 설정을 진행하도록 하겠습니다.

    		<!-- QueryDSL dependency-->
            <dependency>
                <groupId>com.querydsl</groupId>
                <artifactId>querydsl-jpa</artifactId>
            </dependency>
            <dependency>
                <groupId>com.querydsl</groupId>
                <artifactId>querydsl-apt</artifactId>
                <scope>provided</scope>
            </dependency>
            
            <plugin>
            	<groupId>com.mysema.maven</groupId>
            	<artifactId>apt-maven-plugin</artifactId>
            	<version>1.1.3</version>
            	<executions>
            		<execution>
            			<goals>
            				<goal>process</goal>
            			</goals>
            			<configuration>
            				<outputDirectory>target/generated-sources/java</outputDirectory>
            				<processor>com.querydsl.apt.jpa.JPAAnnotationProcessor</processor>
            			</configuration>
            		</execution>
            	</executions>
            </plugin>

    pom.xml에 위와 같이 dependency 설정을 해주고, 플러그인을 설정합니다. (플러그인은 아래쪽의 build 태그에서 plugins 안에 설정되어야 합니다.)

     

    그러고 나서 mvn compile을 입력하면 /target/generated-source 밑에 Q로 시작하는 객체들이 생성된 것을 확인할 수 있습니다.

     

     

     

     

     

     

     

     


     이제 기본적인 준비는 끝났으므로 테스트 코드로 쿼리를 작성해 보겠습니다. :)

     

     참고적으로 제가 참고하고 있는 < 자바 ORM 표준 JPA 프로그래밍 > 책에는 list를 조회시, list() 메서드를 이용하고 있지만 현재 QueryDSL에서는 list() 라는 메서드가 fetch() 메서드로 명칭이 변경되었습니다. Criteria에서는 fetch() 메서드를 FetchJoin으로 사용하기 때문에 Criteria를 주로 사용해왔던 저는 QueryDSL에서도 fetch()가 FetchJoin 메서드인 줄 알고 상당한 삽질을 했습니다. ㅠㅠ

     여러분들은 혼선이 없으시길 바랍니다. ㅎㅎ

     

    우선 Criteria로 간단하게 id 값으로 Member Entity 하나를 조회해보겠습니다.

    (JpaRepository를 상속한 인터페이스를 이용하여 findById() 메서드를 이용하면 되지만 예제를 위해 Criteria로 직접 구현해보았습니다.)

        @Test
        public void simpleCriteriaQuery() {
            CriteriaBuilder builder = em.getCriteriaBuilder();
            CriteriaQuery<Member> query = builder.createQuery(Member.class);
    
            Root<Member> root = query.from(Member.class);
            query.select(root).where(builder.equal(root.get("memberId"), 1L));
    
            Member member = em.createQuery(query).getSingleResult();
    
            log.info("member name : " + member.getName());
        }


     

     이어서 QueryDSL로 방금 작성한 쿼리와 같은 기능을 하는 코드를 작성하였습니다.

        @Test
        public void simpleQueryDSL() {
            JPAQueryFactory query = new JPAQueryFactory(em);
            QMember qMember = new QMember("m");
    
            Member member = query.selectFrom(qMember)
                    .where(qMember.memberId.eq(1L))
                    .fetchOne();
    
            log.info("member name : " + member.getName());
        }

     

     사실 두 쿼리가 워낙 간단해서 직관적으로 이해하기는 어렵지 않은 것 같습니다. 조건들을 조금 더 추가하여 살펴보겠습니다. :)

     


     

    Criteria

        @Test
        public void simpleCriteria_2() {
            CriteriaBuilder builder = em.getCriteriaBuilder();
            CriteriaQuery<Member> query = builder.createQuery(Member.class);
    
            Root<Member> root = query.from(Member.class);
            root.fetch("team", JoinType.INNER);
    
            query.select(root)
                    .where(builder.ge(root.get("age"), 30))
                    .orderBy(builder.asc(root.get("age")));
    
            List<Member> members = em.createQuery(query).getResultList();
    
            for (Member member : members) {
                log.info("member name : " + member.getName());
                log.info("member age : " + member.getAge());
                log.info("member team name : " + member.getTeam().getTeamName());
            }
        }

     

    QueryDSL

        @Test
        public void simpleQueryDSL_2() {
            JPAQueryFactory query = new JPAQueryFactory(em);
            QMember qMember = new QMember("m");
            QTeam qTeam = new QTeam("t");
    
            List<Member> members = query.selectFrom(qMember)
                    .innerJoin(qMember.team, qTeam).fetchJoin()
                    .where(qMember.age.goe(30))
                    .orderBy(qMember.age.asc())
                    .fetch();
    
            for (Member member : members) {
                log.info("member name : " + member.getName());
                log.info("member age : " + member.getAge());
                log.info("member team name : " + member.getTeam().getTeamName());
            }
        }

     

    Result

     

     방금 작성한 코드도 그리 복잡하지는 않지만 개인적으로는 확실히 Criteria보다 QueryDSL이 더 직관적으로 쉽게 작성할 수 있는 것 같습니다. QueryDSL이 작성하기 쉬우니 개인적으로 재미도 있는 것 같습니다.ㅎㅎ 앞으로 계속 QueryDSL에 대해서 조금씩 연습해봐야겠습니다.


     본 포스트는 김영한님의 저서인 - 자바 ORM 표준 JPA 프로그래밍 - 참고하여 작성하였습니다.

     

    반응형

    'Java > JPA' 카테고리의 다른 글

    [JPA] Auditing 설정 방법  (0) 2019.08.15
    QueryDSL 서브 쿼리 사용법  (0) 2019.07.14
    [QueryDSL] select 절에서 조회 대상 지정 방법  (0) 2019.07.13

    댓글

Designed by Tistory.