JPA

QueryDSL 적용 및 예제 시리즈

빈코 2022. 10. 27. 17:50

이번 시리즈는 Query DSL 사용법에 대해 알아보려 합니다. 이전 JPA 시리즈에서는 Spring Data JPA까지만 알아보았는데, Query DSL을 사용하면 조금 더 효율적으로 JPA를 사용할 수 있다😀

 

로고
Query DSL

 

개요

Query DSL이란 Query Domain Specific Language 약자로 도메인+특화+언어라는 개념으로, 오픈소스 프로젝트이며 JPQL을 Java 코드로 작성할 수 있도록 하는 라이브러리다.

 

쉽게 말하면, 기존에 mybatis를 쓰던 JPQL을 쓰든 간에 쿼리를 SQL 문법 규칙대로 쿼리를 작성하지 않고 Java 코드로만 작성할 수 있게 도와주는 라이브러리라고 생각하면 쉬울 것 같다.

 

Criteria 기법을 사용하는 방법도 있지만, 사실상 실무에서 사용하기에는 복잡하고 공부 비용이 드는 것에 비해 효율적으로 많이 떨어지기 때문에 대부분 Query DSL을 사용한다.

 

이번 시리즈는 Query DSL을 적용하는 단계부터 기본 문법, 실무 활용까지 포스팅 할 예정이다. 김영한 님의 강의와 다른 블로그를 참고하며 작성할 예정이며 잘못된 정보가 있다면 언제든지 댓글로 달아주면 감사하겠습니다😄

 

Query DSL 적용하기📘

📌build.gradle

plugins {
   id 'org.springframework.boot' version '2.7.4'
   id 'io.spring.dependency-management' version '1.0.14.RELEASE'
   //querydsl 추가
   id "com.ewerk.gradle.plugins.querydsl" version "1.0.10"
   id 'java'
}

 

dependencies {
   implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
   implementation 'org.springframework.boot:spring-boot-starter-web'
   compileOnly 'org.projectlombok:lombok'
   runtimeOnly 'com.h2database:h2'
   //querydsl 추가
   implementation "com.querydsl:querydsl-jpa:${queryDslVersion}"
   implementation "com.querydsl:querydsl-apt:${queryDslVersion}"

   implementation 'com.github.gavlyukovskiy:p6spy-spring-boot-starter:1.5.8'


   annotationProcessor 'org.projectlombok:lombok'
   testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

 

//querydsl 추가 시작
def querydslDir = "$buildDir/generated/querydsl"

querydsl {
   jpa = true
   querydslSourcesDir = querydslDir
}
sourceSets {
   main.java.srcDir querydslDir
}
compileQuerydsl{
   options.annotationProcessorPath = configurations.querydsl
}
configurations {
   compileOnly {
      extendsFrom annotationProcessor
   }
   querydsl.extendsFrom compileClasspath
}
//querydsl 추가 끝

 

 

도메인 예제📒

엔티티ERD를 확인해보면 회원(Member)과 팀은 다대일 양방향 관계로 매핑을 맺었다. 객체(엔티티)와 테이블의 차이점은 객체 Team 클래스에는 리스트 형식으로 회원 리스트를 가지고 있지만, DB 테이블에서는 가지고 있지 않다.

 

짧게 설명하자면, 객체는 참조를 사용해서 관계를 맺고 테이블은 외래키로 관계를 맺는다. 객체에는 양방향 매핑이 없기 때문에 단방향 매핑 2개를 연결하여 양방향처럼 보이게 한다. 

 

테이블은 키 하나로 member -> team, team -> member 접근이 모두 가능하기 때문에 따로 List members를 가지고 있지 않아도 된다.

 

혹여 위 설명이 이해가 안 간다면 해당 링크를 클릭해서 꼭! 이해하고 오시길 바란다😄

 

엔티티
엔티티 클래스

 

도메인
ERD

 

Member Entity 설계📗

package study.querydsl.entity;

import lombok.*;

import javax.persistence.*;

@Entity
@Getter @Setter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@ToString(of = {"id","username","age"})
public class Member {

    @Id @GeneratedValue
    @Column(name = "member_id")
    private Long id;
    private String username;
    private int age;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "team_id")
    private Team team;

    public Member(String username) {
        this(username, 0, null);
    }

    public Member(String username, int age) {
        this(username, age, null);
    }

    public Member(String username, int age, Team team) {
        this.username = username;
        this.age = age;
        if (team != null) {
            changeTeam(team);
        }
    }

    public void changeTeam(Team team) {
        this.team = team;
        team.getMembers().add(this);
    }
}

Query DSL 포스팅이기 때문에 모든 어노테이션을 설명하지는 않을 예정입니다. 엔티티 관련 어노테이션 설명은 엔티티 매핑을 참고해주세요😅  

 

@Setter는 각 필드의 set을 설정해주는 롬복 라이브러리인데 사실 실무에서는 Entity에 사용하는 것은 옳지 않습니다. 다만, 공부 과정이기 때문에 편의를 위해 사용하였습니다. 실무에서는 실제 데이터를 가지고 오는 Entity에서 VO나 DTO로 변환을 한 뒤에 변환한 곳에서 @Setter를 자주 사용합니다. Entity로 가져온 데이터를 어디선가 계속해서 set을 통해 데이터가 바뀌면 추후 유지보수면에서 상당히 어렵기 때문입니다

 

@NoArgsConstructor(access = AccessLevel.PROTECTED) 는 기본 생성자를 막고 싶을 때 사용하는데, JPA 스펙상 PROTECTED로 열어두어야 합니다. 기본 생성자를 막는 이유 또한, 특정 위치를 모르는 곳에서 무분별한 생성을 막기 위함입니다.

 

@ToString은 가급적 연관관계가 없는 내부 필드에만 사용해야 합니다.

 

changeTeam() 메서드는 양방향 연관관계를 한 번에 처리하는 편의 메서드입니다. 자세한 설명은 링크를 참고해주세요😄


Team Entity 설계📔

package study.querydsl.entity;

import lombok.*;

import javax.persistence.*;
import java.util.ArrayList;
import java.util.List;

@Entity
@Getter @Setter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@ToString(of = {"id","name"})
public class Team {

    @Id
    @GeneratedValue
    private Long id;
    private String name;

    @OneToMany(mappedBy = "team")
    private List<Member> members = new ArrayList<>();

    public Team(String name){
        this.name = name;
    }
}

Team 객체 관점에서는 member 객체와 일대다 관계로 매핑이 됩니다. 연관관계의 주인은 多에 해당하는 회원 객체이므로 mappedBy를 사용하여 회원으로부터 매핑을 당했다. 즉, 연관관계의 주인은 Team이 아니라는 것을 명시해줍니다. 

 

📌 연관관계의 주인도 이전 포스팅 연관관계 매핑에 자세히 설명되어 있습니다😊

 

 

📌Gradle IntelliJ 사용법

  • Gradle -> Tasks -> bulid -> clean
  • Gradle -> Tasks -> other -> complieQuerydsl

 

📌Q 타입 생성 확인

  • build -> generated -> querydsl -> QType 파일 확인

 

QType 생성
QType 생성

 

QMember
QMember 화면

 


👨‍👩‍👦‍👦 오픈채팅방 운영

취업을 준비하는 예비 개발자분들을 위한 질문&답변할 수 있는 공간을 만들었습니다. 취업과 이직을 하기 위해서 어떤 걸 중점적으로 준비해야 하는지부터 포트폴리오&이력서 작성법 등 다양한 질문들을 받고 답변을 드립니다. 참여하셔서 다양한 정보 얻고 가시면 좋을 것 같네요😁

 

참여코드 : 456456

https://open.kakao.com/o/gVHZP8dg

 

비전공 개발자 취업 준비방(질문&답변)

#비전공 #개발자 #취업 #멘토링 #부트캠프 #국비지원 #백엔드 #프론트엔드 #중소기업 #중견기업 #자바 #Java #sql

open.kakao.com

 


👨‍💻 전자책 출간

아울러 제가  🌟비전공자에서 2년만에 보안 전문 중견기업으로 이직 한 방법들을 정리한 전자책을 출간 하게 되었습니다. 어떤 걸 공부해야 하는지, 이직을 위해서 무엇을 준비해야 하는지, 제가 받았던 기술 면접 리스트 등 다양한 목차로 구성되어 있습니다. 또한, 구매 시 1:1 채팅을 이용하여 포트폴리오 첨삭을 도와드리고 있습니다. 🐕전자책으로 얻은 모든 수익은 유기견 센터 '팅*벨 입양센터'에 후원될 예정입니다. 관심 있으신 분들은 아래 링크를 참고해주세요😁

https://kmong.com/gig/480954

 

비전공개발자 2년만에 중견기업 들어간 방법 | 14000원부터 시작 가능한 총 평점 0점의 전자책, 취

0개 총 작업 개수 완료한 총 평점 0점인 Binco의 전자책, 취업·이직 전자책 서비스를 0개의 리뷰와 함께 확인해 보세요. 전자책, 취업·이직 전자책 제공 등 14000원부터 시작 가능한 서비스

kmong.com


 

마치며

지금까지 Query DSL 설정 방법예제 도메인엔티티 설계를 진행하였다. 사실 이번 포스팅은 앞으로 나올 Query DSL 시리즈의 설정 부분이었다. 다시 한번 강조하지만 기존 JPA의 동작 방식을 모른다면 JPA 포스팅을 참고해서 기본부터 익히기를 권한다. 다음 포스팅은 기존 JPQL 방법과 QueryDSL의 조회 로직 차이점을 살펴볼 것이다😄

 

QueryDSL Series

1. QueryDSL 적용 및 예제 시리즈 시작(현재)

2. QueryDSL 조회 예제

3. QueryDSL 정렬 & 페이징 & 집합

4. QueryDSL 조인 예제

5. QueryDSL Projection

6. QueryDSL 동적 쿼리 & Bulk 연산

7. QueryDSL 실무 활용

 

Reference

실전! Querydsl

 

 

 

반응형