JPA - JPQL

최상민·2023년 11월 28일
0

Spring Boot

목록 보기
5/5

자바 웹 개발 워크북 - 예스24
아래내용은 ‘자바 웹 개발 워크북(구멍가게 코딩단)’ 책의 pp. 432 ~ 508 (5.3. Spring Data JPA ~ 5.4. 게시물 관리 완성하기)의 내용을 토대로 작성되었습니다.

JPQL

JPQL(Java Persistence Query Language)은 엔티티 객체를 대상으로 쿼리를 작성하기 위한 객체 지향 쿼리 언어이다. JPQL은 엔티티 객체와 필드를 대상으로 SQL과 유사한 구문을 사용하여 데이터베이스에서 데이터를 가져올 수 있도록 해준다.

QueryDSL 동적 쿼리

QueryDSL은 JPQL을 편하게 작성하도록 도와주는 빌더 클래스 모음으로 비표준 오픈소스 프레임워크이다. 타입 기반으로 코드를 이용해 JPQL 쿼리를 생성하고 실행하는 것이 목적이다. 기존의 JPA나 JPQL만으로는 어노테이션이 고정되어 있어 정적으로 쿼리를 처리할 수 밖에 없다. 하지만 QueryDsl은 자바 코드를 이용해 동적 쿼리가 가능하게 해준다.

QueryDSL 설정

  • build.gradle (위의 JPA 기본 설정에서 이미 설정했었다)
    buildscript {
        ext {
            queryDslVersion = "5.0.0"
        }
    }
    
    plugins {
        id "java"
        id "war"
        id "org.springframework.boot" version "2.7.17"
        id "io.spring.dependency-management" version "1.0.15.RELEASE"
    }
    
    group = "com.example"
    version = "0.0.1-SNAPSHOT"
    
    java {
        sourceCompatibility = "17"
    }
    
    configurations {
        compileOnly {
            extendsFrom annotationProcessor
        }
    }
    
    repositories {
        mavenCentral()
    }
    
    dependencies {
        implementation "org.springframework.boot:spring-boot-starter-data-jpa"
        implementation "org.springframework.boot:spring-boot-starter-thymeleaf"
        implementation "org.springframework.boot:spring-boot-starter-web"
    
        // querydsl 라이브러리
        implementation "com.querydsl:querydsl-jpa"
        implementation "com.querydsl:querydsl-apt" // apt(annotation processing tool)
        implementation "com.querydsl:querydsl-core"
        implementation "com.querydsl:querydsl-sql"
    
        // modelMapper: DTO와 엔티티 간 변환 처리해주는 라이브러리
        implementation "org.modelmapper:modelmapper:3.1.0"
    
        // 타임리프로 LocalDateTime format 을 temporals 로 맞추기 위해 필요
        implementation "org.thymeleaf.extras:thymeleaf-extras-java8time3"
    
        compileOnly("org.projectlombok:lombok", "com.querydsl:querydsl-apt:${queryDslVersion}:jpa")
        developmentOnly "org.springframework.boot:spring-boot-devtools"
        runtimeOnly "org.mariadb.jdbc:mariadb-java-client"
    
        // 기존 롬복 이외도, javax 와 querydsl 어노테이션 추가
        annotationProcessor(
                "org.projectlombok:lombok",
                "javax.persistence:javax.persistence-api",
                "javax.annotation:javax.annotation-api",
                "com.querydsl:querydsl-apt:${queryDslVersion}:jpa");
    
        providedRuntime "org.springframework.boot:spring-boot-starter-tomcat"
    
        testImplementation "org.springframework.boot:spring-boot-starter-test"
    
        // test junit 시에 lombok 추가
        testCompileOnly "org.projectlombok:lombok"
        testAnnotationProcessor "org.projectlombok:lombok"
    }
    
    tasks.named("bootBuildImage") {
        builder = "paketobuildpacks/builder-jammy-base:latest"
    }
    
    tasks.named("test") {
        useJUnitPlatform()
    }
    
    // 프로젝트의 소스 코드 및 리소스 디렉토리 구성
    // 개발 시 작성하는 java 파일의 위치 (src/main/java)와 Q도메인이 저장되는 위치(build/generated)를 명시
    //  기존 파일과 Q도메인이 gradle 빌드 시 자동 컴파일 되게 함
    sourceSets {
        main.java.srcDirs = ["$projectDir/src/main/java", "$projectDir/build/generated"]
    }

위의 설정이 끝났으면 기존 Repository와 QueryDSL을 연동한다.

  1. 먼저 QueryDSL을 이용하는 인터페이스를 선언한다.
    • BoardSearch
      package com.example.test08.repository;
      
      import com.example.test08.entity.Board;
      import org.springframework.data.domain.Page;
      import org.springframework.data.domain.Pageable;
      
      public interface BoardSearch {
          Page<Board> search(Pageable page);
      }
  2. 위 인터페이스의 구현 클래스 선언한다.
    1. 실제 구현 클래스는 반드시 ‘인터페이스 이름 + Impl’이어야 제대로 동작한다.
    2. 구현 클래스는 QuerydslRepositorySupport를 extends하고, 위의 인터페이스를 implements한다.
    3. 인터페이스의 함수 외에 생성자 함수를 만들어야 한다.
    • BoardSearchImpl
      package com.example.test08.repository;
      
      import com.example.test08.entity.Board;
      import org.springframework.data.domain.Page;
      import org.springframework.data.domain.Pageable;
      import org.springframework.data.jpa.repository.support.QuerydslRepositorySupport;
      
      public class BoardSearchImpl extends QuerydslRepositorySupport implements BoardSearch{
          public BoardSearchImpl(){
      				// QuerydslRepositorySuppor 의 생성자는 제네릭 타입을 사용함
      				// 따라서 이를 상속할 때 해당 제네릭 타입이 무엇인지 지정
      				// super로 부모 클래스의 생성자를 호출하며, 제네릭 타입으로 Board 를 넘겨줌
              super(Board.class);
          }
          @Override
          public Page<Board> search(Pageable page) {
              return null;
          }
      }
  3. 기존 Repository의 부모 인터페이스로 QueryDSL을 이용하는 인터페이스 지정
    • BoardRepository
      package com.example.test08.repository;
      
      import com.example.test08.entity.Board;
      import org.springframework.data.jpa.repository.JpaRepository;
      import org.springframework.data.jpa.repository.Query;
      
      public interface BoardRepository extends JpaRepository<Board, Long>, BoardSearch {
      }

Q도메인

Q도메인은 해당 엔티티의 메타 모델 정보(엔티티의 필드, 테이블, JPQL에서 사용되는 엔티티 속성)라고 할 수 있다. 이는 QueryDSL을 사용하여 컴파일 할 때 엔티티 속성을 안전하게 참조할 수 있도록 해준다. Q도메인은 build 할 때, 지정된 build 폴더의 엔티티가 있는 곳에 자동 생성된다. 따라서 import하여 사용할 때 해당 엔티티의 위치와 동일하게 작성해주면 된다.

  • BoardSearchImpl
    package com.springdata.repository;
    
    import com.querydsl.core.BooleanBuilder;
    import com.querydsl.jpa.JPQLQuery;
    import com.springdata.dto.PageDTO;
    import com.springdata.entity.Board;
    // 처음에는 빨간 줄이 뜰 수 있다.
    **import com.springdata.entity.QBoard;**
    import org.springframework.data.domain.Page;
    import org.springframework.data.domain.PageImpl;
    import org.springframework.data.domain.Pageable;
    import org.springframework.data.jpa.repository.support.QuerydslRepositorySupport;
    
    import java.util.List;
    
    public class BoardSearchImpl extends QuerydslRepositorySupport implements BoardSearch {
        public BoardSearchImpl() {
            super(Board.class);
        }
    
        @Override
        public Page<Board> searchPage(Pageable pageable, PageDTO pageDTO) {
            QBoard board = QBoard.board;
    
            JPQLQuery<Board> query = from(board);
    
            String[] types = pageDTO.getTypes();
            String keyword = pageDTO.getKeyword();
    
            if(types!=null && keyword!=null && !keyword.isEmpty()){
                BooleanBuilder booleanBuilder = new BooleanBuilder();
                for(String type: types){
                    System.out.println(type);
                    switch(type){
                        case "title":
                            booleanBuilder.or(board.title.contains(keyword));
                            break;
                        case "content":
                            booleanBuilder.or(board.content.contains(keyword));
                            break;
                        case "author":
                            booleanBuilder.or(board.author.contains(keyword));
                            break;
                    }
                }
                query.where(booleanBuilder);
            }
    
            this.getQuerydsl().applyPagination(pageable, query);
            List<Board> list = query.fetch();
    
            return new PageImpl<>(list, pageable, query.fetchCount());
        }
    }

인텔리제이에서 Q도메인 설정

  1. [Setting - Build, Execution, Deployment - Build Tools - Gradle] 에 들어가서 ‘Download external annotations for dependencies’에 체크, Gradle의 Distribution을 Wrapper로 설정한다.
    [Setting - Build, Execution, Deployment - Build Tools - Gradle] 에 들어가서 ‘Download external annotations for dependencies’에 체크, Gradle의 Distribution을 Wrapper로 설정한다.

  2. [Setting - Build, Execution, Deployment -Compiler - Annotaion Processors] 에서 “Enable Annotaion Processing”에 체크가 되어있는지 확인하기
    [Setting - Build, Execution, Deployment -Compiler - Annotaion Processors] 에서 “Enable Annotaion Processing”에 체크가 되어있는지 확인하기

  3. 설정 후에 [build - Reubuild Project] 하기
    설정 후에 [build - Reubuild Project] 하기

JPQLQuery

JPQLQuery는 Java를 이용해 쿼리를 작성하게 해주는 인터페이스이다.

기본 메소드

  • from(테이블): JPQLQuery에 사용할 엔티티를 지정
  • where(조건): 조건을 지정
  • fetch(): 쿼리를 실행하고 해당 쿼리에 해당되는 결과를 가져옴
  • fetchCount(): 해당 쿼리에 대한 결과의 개수를 반환
  • BoardSearchImpl
    @Override
    public List<Board> searchTitle(Pageable pageable) {
        QBoard board = QBoard.board; // Q도메인을 사용하기 위한 인스턴스 생성
        JPQLQuery<Board> query = from(board);
        query.where(board.title.contains("1"));
    		// SELECT FROM board WHERE title LIKE '%1%';
    
    		this.getQuerydsl().applyPagination(pageable, query); 
    		// SELECT FROM board WHERE title LIKE '%1%' LIMIT p.COUNT, p.SIZE;
    		List<Board> boardList = query.fetch(); // 결과를 리스트로 가져옴
    		long count = query.fetchCount(); // 검색된 결과의 전체 개수
    		System.out.println("count: "+count);
    		return boardList;
    }

조건 쿼리 메소드

BooleanBuilder 클래스는 Querydsl에서 동적인 쿼리를 작성할 때 조건을 조합하고 논리적인 연산을 수행하기 위한 도구.

  • and(조건): and 조건문 생성
  • or(조건): or 조건문 생성
  • BoardSearchImpl
@Override
    public Page<Board> searchPage(Pageable pageable, PageDTO pageDTO) {
        QBoard board = QBoard.board;

        JPQLQuery<Board> query = from(board);

        String[] types = pageDTO.getTypes();
        String keyword = pageDTO.getKeyword();

        if(types!=null && keyword!=null && !keyword.isEmpty()){
            BooleanBuilder booleanBuilder = new BooleanBuilder();
            for(String type: types){
                System.out.println(type);
                switch(type){
                    case "title":
                        booleanBuilder.or(board.title.contains(keyword));
                        break;
                    case "content":
                        booleanBuilder.or(board.content.contains(keyword));
                        break;
                    case "author":
                        booleanBuilder.or(board.author.contains(keyword));
                        break;
                }
            }
            query.where(booleanBuilder);
        }

        // 부모 클래스인 QuerydslRepositorySupport 를 상속하면서 BoardSearchImpl(this)에  querydsl 필드가 있음
        this.getQuerydsl().applyPagination(pageable, query);
        List<Board> list = query.fetch();

        return new PageImpl<>(list, pageable, query.fetchCount());
    }
profile
상민

1개의 댓글

comment-user-thumbnail
2024년 1월 17일

안녕하세요
천재 it 교육 관련해서 질문이 있는데 혹시 지금 드릴 수 있을까요?

답글 달기