동적쿼리

김창모·2023년 6월 6일
0

SpringBoot

목록 보기
17/19

Intro

지난 시간에 단건 조회와 전체 조회에 대해 알아보았다.
이번시간엔 동적쿼리를 이용하여 원하는 조건에 따라 검색하는 방법을 알아보자.

SpringSecurityConfig 수정

JWT 로그인에 대해 직접 테스트를 해보았으니 개발단계 이기 때문에 조금더 편리하게 사용하기 위해
모든 URL 을 허용하도록 잠시 변경해두겠다.

.antMatchers("/**")
.permitAll()

동적쿼리란 ?

런타임 시점에 조건이나 정렬 등을 변경해야 하는 쿼리
검색 조건이 작성자 , 제목에 포함되는것 등등 일경우 해당 내용에 따라 다른 쿼리로 데이터를 조회해야한다.
모든 상황에 대해 if 문으로 처리를 할수도 있지만 코드가 길어지며 유지보수가 어렵다.
JPA 에서는 동적 쿼리의 해결방법중 하나로 querydsl 을 사용하는 방법이 있다.

Querydsl ?

하이버네이트 쿼리 언어의 쿼리를 타입에 안전하게 생성 및 관리해주는 프레임워크이다.

querydsl 의존성 추가

// QueryDsl
	// QueryDsl 의 핵심 모듈
	implementation 'com.querydsl:querydsl-core'
	// QueryDsl 과 JPA 를 함께 사용하기 위한 모듈
	implementation 'com.querydsl:querydsl-jpa'

	// QueryDsl 의 Annotation Processor 모듈이다.
	// 이 모듈은 컴파일 시점에 QueryDsl 어노테이션을 처리하여 쿼리타입(Q 타입) 클래스를 생성한다.
	annotationProcessor "com.querydsl:querydsl-apt:${dependencyManagement.importedProperties['querydsl.version']}:jpa"
	annotationProcessor "jakarta.persistence:jakarta.persistence-api"
	annotationProcessor "jakarta.annotation:jakarta.annotation-api"
	//

Q Class

Member , Post , BaseTimeEntity 에 대한 Q Class 가 생성되었다.

QueryDslConfig

package com.hello.hello.config;

import com.querydsl.jpa.impl.JPAQueryFactory;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class QueryDslConfig {

    @PersistenceContext
    private EntityManager entityManager;

    @Bean
    public JPAQueryFactory jpaQueryFactory(){
        return new JPAQueryFactory(entityManager);
    }

}

@PersistenceContext

JPA에서 영속성 컨텍스트를 관리하는 컨테이너에게 EntityMAnager 를 주입하도록 하는 어노테이션 이다.
EntityManager 는 JPA 에서 DB와의 상호작용과 엔티티의 영속성을 관리한다.
@Autowired 를 통해 주입하는 경우는 SpringDataJPA 를 사용할 때인데 이때는 EntityManager 를 자동으로 생성하고 관리해주기 때문에 Bean 주입 만으로 사용이 가능하다.

CustomRepository

CustomPostRepository

package com.hello.hello.repository;

import com.hello.hello.domain.entity.Post;
import java.util.List;
import org.springframework.stereotype.Repository;

@Repository
public interface CustomPostRepository {
    List<Post> findPostsByTitleAndContent(String title, String content);
}

CustomPostRepositoryImpl

CustomPostRepository interface 의 구현체이다.
QuerydslConfig 에서 빈으로 등록한 jpaQueryFactory 를 생성자로 주입받으며
쿼리 조건문을 설정하는 .where() 에서 BooleanExpression 타입의 메서드를 활용하여
title 과 content 가 공백이 아니고 null 이 아닐경우
title과 content 가 포함되어야 한다는 조건물을 추가하여
동적으로 다른 쿼리를 전송할수 있다.

package com.hello.hello.repository;

import static com.hello.hello.domain.entity.QPost.post;

import com.hello.hello.domain.entity.Post;
import com.querydsl.core.types.dsl.BooleanExpression;
import com.querydsl.jpa.impl.JPAQueryFactory;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Repository;

@Repository
@RequiredArgsConstructor
public class CustomPostRepositoryImpl implements CustomPostRepository{

    private final JPAQueryFactory jpaQueryFactory;


    public List<Post> findPostsByTitleAndContent(String title, String content) {
        return jpaQueryFactory.selectFrom(post)
                .where(
                        containsTitle(title),
                        containsContent(content)
                )
                .fetch();
    }

    private BooleanExpression containsTitle(String title) {
        if (title != null && !title.isEmpty()) {
            return post.title.contains(title);
        }

        return null;
    }

    private BooleanExpression containsContent(String content) {
        if (content != null && !content.isEmpty()) {
            return post.content.contains(content);
        }
        return null;
    }
}

Service

    public List<PostResponse> findPostsByTitleAndContent(String title,String content) {
        List<Post> postsByTitleAndContent = customMemberRepository.findPostsByTitleAndContent(title, content);

        return postsByTitleAndContent.stream().map(PostResponse::new).collect(Collectors.toList());
    }

Controller

기존 컨트롤러에선 모든 포스트를 조회하는 기능이 있었다
하지만 동적 쿼리를 이용할때 검색조건을 넣지 않으면 모든 포스트가 조회가 되고
검색 조건을 넣는다면 검색 조건에 맞는 포스트가 조회가 되기 때문에
모든 포스트를 조회하던 메서드를 수정해보자.

    @GetMapping("/posts")
    public ResponseEntity<List<PostResponse>> getPostsByTitleAndContent(@RequestParam(required = false) String title,@RequestParam(required = false) String content) {
        List<PostResponse> posts = postService.findPostsByTitleAndContent(title, content);

        return ResponseEntity.ok(posts);
    }

실행

title 조건검색

title 이 title0 인것만 조회.

content 조건

content 가 content1 인것만 조회.

검색조건 없음

모든 Post 조회

결론

  • 동적쿼리를 사용하면 다양한 조건에 따라 쿼리를 동적으로 생성할수 있어 다양한 검색 요구사항을 수용하고 쿼리의 유연성을 높일수 있다.
  • 동적 쿼리는 실행 시점에 필요한 쿼리만 생성하여 실행하므로 불필요한 조건이나 연산을 하지 않고 쿼리를 생성하여 실행속도를 향상시킬수 있다.
  • 복잡한 조건 논리를 코드로 표현하여 가독성을 높일수 있다
  • 쿼리의 변경이나 추가 조건의 처리등 유지보수성을 높일수 있다.

0개의 댓글