[코드로 배우는 스프링부트 웹 프로젝트] - 프로젝트 구조 만들기(2): Querydsl 적용 및 테스트

Jongwon·2022년 12월 26일
1

Querydsl

동적 쿼리 작성을 위해 Querydsl을 추가합니다.

plugins {
	id 'java'
	id 'war'
	id 'org.springframework.boot' version '3.0.0'
	id 'io.spring.dependency-management' version '1.1.0'
}

group = 'org.zerock'
version = '0.0.1-SNAPSHOT'
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'
	compileOnly 'org.projectlombok:lombok'
	developmentOnly 'org.springframework.boot:spring-boot-devtools'
	annotationProcessor 'org.projectlombok:lombok'
	providedRuntime 'org.springframework.boot:spring-boot-starter-tomcat'
	testImplementation('org.springframework.boot:spring-boot-starter-test')
	testImplementation('org.springframework.boot:spring-boot-starter-test') {
		exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
	}
	// https://mvnrepository.com/artifact/org.mariadb.jdbc/mariadb-java-client
	implementation group: 'org.mariadb.jdbc', name: 'mariadb-java-client', version: '3.1.0'


	implementation 'com.querydsl:querydsl-jpa:5.0.0:jakarta'
	annotationProcessor "com.querydsl:querydsl-apt:${dependencyManagement.importedProperties['querydsl.version']}:jakarta"
	annotationProcessor "jakarta.annotation:jakarta.annotation-api"
	annotationProcessor "jakarta.persistence:jakarta.persistence-api"
}

tasks.named('test') {
	useJUnitPlatform()
}

def generated = 'src/main/generated'

// querydsl QClass 파일 생성 위치를 지정
tasks.withType(JavaCompile) {
	options.getGeneratedSourceOutputDirectory().set(file(generated))
}

// java source set 에 querydsl QClass 위치 추가
sourceSets {
	main.java.srcDirs += [ generated ]
}

// gradle clean 시에 QClass 디렉토리 삭제
clean {
	delete file(generated)
}

위에 build.gradle은 다른 자료(JPA강의나 현재 교재)에 적혀있는 내용과는 많이 달라졌습니다. Spring Boot가 버전 3.0으로 업그레이드됨에 따라 @Entity가 담겨있는 클래스 등이 javax.persistence에서 jakarta.persistence로 import문이 완전히 변경되었기 때문입니다.
https://stackoverflow.com/questions/60021815/why-has-javax-persistence-api-been-replaced-by-jakarta-persistence-api-in-spring

오라클의 JAVA 저작권때문에 이름이 변경된 것인데, 이것때문에 javax.persistence.entity를 찾지 못하는 에러가 발생했고, 스프링부트의 버전업이 비교적 최근이라 자료도 많이 나오지않아 찾는데 정말 오래걸렸습니다.

java.lang.NoClassDefFoundError: javax/persistence/Entity 에러

해당 코드 수정은 인프런 김영한님 강의 커뮤니티에서 QueryDsl SpringBoot 3.0의 gradle 설정을 공유합니다querydsl 설정 후 테스트 코드 실행 시 QHello 에러를 참고하여 진행하였습니다.

정상적으로 수행이 완료되면 아래와 같이 generated 폴더에 Querydsl에 맞는 엔티티가 생성됩니다.

아래 사진과 같이 Querydsl의 엔티티에는 모든 애트리뷰트값이 변수명으로 선언되어 있음을 확인할 수 있습니다.

GuestBookRepository에 QuerydslPredicateExecutor<GuestBook>을 추가로 상속합니다.

public interface GuestbookRepository extends JpaRepository<Guestbook, Long>, QuerydslPredicateExecutor<Guestbook> {

}

다음으론 Guestbook 엔티티에 제목과 내용을 수정하는 코드를 입력하겠습니다.

package org.zerock.guestbook.entity;

import jakarta.persistence.*;
import lombok.*;

@Entity
@Getter
@Builder
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class Guestbook extends BaseEntity {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long gno;

    @Column(length=100, nullable = false)
    private String title;

    @Column(length=1500, nullable = false)
    private String content;

    @Column(length=50, nullable = false)
    private String writer;

//추가
    public void changeTitle(String title) {
        this.title = title;
    }
    public void changeContent(String content) {
        this.content = content;
    }
}

엔티티는 setter가 없는 방향으로 개발하는 것이 권장되지만, 연습을 위해 추가하겠습니다.

@Test
    public void updateTest() {
        Optional<Guestbook> result = guestbookRepository.findById(300L);

        if(result.isPresent()) {
            Guestbook guestbook = result.get();

            guestbook.changeTitle("Changed Title...");
            guestbook.changeContent("Changed Content...");

            guestbookRepository.save(guestbook);
        }
    }

위의 테스트를 실행시키고 데이터베이스를 확인해보면 300번째 엔티티가 수정되었음을 확인할 수 있습니다. 또한 modDate역시 엔티티 수정을 자동으로 감지하여 수정해주기 때문에 regDate와 다름을 확인할 수 있습니다.

테스트

  • 단일 항목(제목, 내용, 작성자 중 단 하나만 이용) 검색
    Title에 1이 들어간 엔티티 검색
//Pageable을 import할 때 print가 아닌 data.domain을 import해야 합니다.
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;

<생략>
@Test
    public void testQuery1() {
        Pageable pageable = PageRequest.of(0, 10, Sort.by("gno").descending());

        //엔티티 클래스의 필드를 변수로 사용가능하므로 컴파일 시 쿼리문 검사가능
        QGuestbook qGuestbook = QGuestbook.guestbook;

        String keyword = "1";

        //쿼리문의 Where절에 들어갈 조건을 넣는 컨테이너
        BooleanBuilder builder = new BooleanBuilder();

        //BooleanBuilder엔 com.querydsl.core.types.Predicate 타입만 들어갈 수 있음
        BooleanExpression expression = qGuestbook.title.contains(keyword);

        //Where절의 And구문
        builder.and(expression);

        //레포지토리가 상속받는 QuerydslPredicateExecutor 인터페이스의 findAll을 통해 where절 탐색
        Page<Guestbook> result = guestbookRepository.findAll(builder, pageable);

        result.stream().forEach(guestbook -> {
            System.out.println(guestbook);
        });
    }


쿼리문의 Where절을 보면 정상적으로 질의처리를 하고 있음을 확인할 수 있습니다.

그리고 결과의 첫 10개(1페이지)가 출력되는 것 역시 확인할 수 있습니다.

  • 다중 항목(여러 조건 결합) 검색
    제목이나 내용에 1이 포함되어 있고, gno가 0보다 큰 엔티티 검색
@Test
    public void testQuery2() {
        Pageable pageable = PageRequest.of(0, 10, Sort.by("gno").descending());

        QGuestbook qGuestbook = QGuestbook.guestbook;

        String keyword = "1";

        BooleanBuilder builder = new BooleanBuilder();

        BooleanExpression exTitle = qGuestbook.title.contains(keyword);
        BooleanExpression exContent = qGuestbook.content.contains(keyword);

        BooleanExpression exAll = exTitle.or(exContent);

        builder.and(exAll);

        builder.and(qGuestbook.gno.gt(0L));
        
        Page<Guestbook> result = guestbookRepository.findAll(builder, pageable);
        
        result.stream().forEach(guestbook -> {
            System.out.println(guestbook);
        });
    }


위와 같이 where절에서 and와 or로 적절한 질의처리가 진행되고 있음을 볼 수 있습니다.

profile
Backend Engineer

0개의 댓글