자바 웹 개발 워크북 - 예스24
아래내용은 ‘자바 웹 개발 워크북(구멍가게 코딩단)’ 책의 pp. 432 ~ 508 (5.3. Spring Data JPA ~ 5.4. 게시물 관리 완성하기)의 내용을 토대로 작성되었습니다.
JPQL(Java Persistence Query Language)은 엔티티 객체를 대상으로 쿼리를 작성하기 위한 객체 지향 쿼리 언어이다. JPQL은 엔티티 객체와 필드를 대상으로 SQL과 유사한 구문을 사용하여 데이터베이스에서 데이터를 가져올 수 있도록 해준다.
QueryDSL은 JPQL을 편하게 작성하도록 도와주는 빌더 클래스 모음으로 비표준 오픈소스 프레임워크이다. 타입 기반으로 코드를 이용해 JPQL 쿼리를 생성하고 실행하는 것이 목적이다. 기존의 JPA나 JPQL만으로는 어노테이션이 고정되어 있어 정적으로 쿼리를 처리할 수 밖에 없다. 하지만 QueryDsl은 자바 코드를 이용해 동적 쿼리가 가능하게 해준다.
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을 연동한다.
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);
}
QuerydslRepositorySupport
를 extends하고, 위의 인터페이스를 implements한다.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;
}
}
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도메인은 해당 엔티티의 메타 모델 정보(엔티티의 필드, 테이블, JPQL에서 사용되는 엔티티 속성)라고 할 수 있다. 이는 QueryDSL을 사용하여 컴파일 할 때 엔티티 속성을 안전하게 참조할 수 있도록 해준다. Q도메인은 build 할 때, 지정된 build 폴더의 엔티티가 있는 곳에 자동 생성된다. 따라서 import하여 사용할 때 해당 엔티티의 위치와 동일하게 작성해주면 된다.
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());
}
}
[Setting - Build, Execution, Deployment - Build Tools - Gradle] 에 들어가서 ‘Download external annotations for dependencies’에 체크, Gradle의 Distribution을 Wrapper로 설정한다.
[Setting - Build, Execution, Deployment -Compiler - Annotaion Processors] 에서 “Enable Annotaion Processing”에 체크가 되어있는지 확인하기
설정 후에 [build - Reubuild Project] 하기
JPQLQuery는 Java를 이용해 쿼리를 작성하게 해주는 인터페이스이다.
from(테이블)
: JPQLQuery에 사용할 엔티티를 지정where(조건)
: 조건을 지정fetch()
: 쿼리를 실행하고 해당 쿼리에 해당되는 결과를 가져옴fetchCount()
: 해당 쿼리에 대한 결과의 개수를 반환@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 조건문 생성@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());
}
안녕하세요
천재 it 교육 관련해서 질문이 있는데 혹시 지금 드릴 수 있을까요?