Develop/java,spring

QueryDsl 설정부터 사용해보기

kudl 2020. 11. 12. 11:25

build.gradle 설정

plugin 적용

plugins {
    id "com.ewerk.gradle.plugins.querydsl" version "1.0.10"
}

dependencies 추가

compile("com.querydsl:querydsl-jpa:4.4.0")
compile("com.querydsl:querydsl-apt:4.4.0")

generate 경로 설정

def generatedSourcesDir = file("${buildDir}/generated/querydsl")

compileQuerydsl {
    options.annotationProcessorPath = configurations.querydsl
}

configurations {
    querydsl.extendsFrom compileClasspath
}

sourceSets {
    main {
        java {
            srcDir generatedSourcesDir
        }
    }
}

querydsl {
    querydslSourcesDir = relativePath(generatedSourcesDir)
    library = "com.querydsl:querydsl-apt"
    jpa = true
}

 

 

QueryDSL 사용 및 테스트 예제

테스트를 위해 Product, ProductItem 테이블을 생성해서 예제를 진행한다.

CREATE TABLE `product` (
  `id` bigint NOT NULL AUTO_INCREMENT,
  `contents` varchar(255) DEFAULT NULL,
  `created_at` datetime DEFAULT NULL,
  `hide` bit(1) DEFAULT NULL,
  `name` varchar(255) DEFAULT NULL,
  `updated_at` datetime DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci

create table product_item
(
    id bigint auto_increment
        primary key,
    product_id bigint not null,
    name varchar(255) not null,
    updated_at datetime not null,
    created_at datetime not null,
    constraint product_item_fk_01
        foreign key (product_id) references product (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

INSERT INTO product (contents, created_at, hide, name, updated_at) values ('Test Data1', now(), false, '111', now());
INSERT INTO product (contents, created_at, hide, name, updated_at) values ('Test Data2', now(), false, '222', now());

insert into product_item (product_id, name, updated_at, created_at) value (1, 'productItemName1', now(), now());
insert into product_item (product_id, name, updated_at, created_at) value (1, 'productItemName2', now(), now());
insert into product_item (product_id, name, updated_at, created_at) value (2, 'productItemName3', now(), now());
insert into product_item (product_id, name, updated_at, created_at) value (2, 'productItemName4', now(), now());

Entity, Repository 생성

Product, ProductItem 클래스 생성 후 JPA Repository 를 생성해준다.

QueryDSL 버전이 변경 되면서 Join 관계를 설정하지 않아도 Join문 작성이 가능한데 테스트 해보기 위해 아래 샘플 Entity 에서는 Join관계를 설정하지 않았다.

Product

import javax.persistence.*;
import java.time.LocalDateTime;

@Entity
public class Product {

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

    private String name;

    private String contents;

    private Boolean hide;

    @Column(name = "created_at", nullable = false)
    private LocalDateTime createdAt;

    @Column(name = "updated_at", nullable = false)
    private LocalDateTime updatedAt;
}

ProductItem

import javax.persistence.*;
import java.time.LocalDateTime;

@Entity
public class ProductItem {

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

    @Column(name = "product_id", nullable = false)
    private Long productId;

    @Column(name = "name")
    private String name;

    @Column(name = "created_at", nullable = false)
    private LocalDateTime createdAt;

    @Column(name = "updated_at", nullable = false)
    private LocalDateTime updatedAt;
}

ProductRepository

import com.xxx.boot.base.model.Product;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface ProductRepository extends JpaRepository<Product, Long> {
}

ProductItemRepository

import com.xxx.boot.base.model.ProductItem;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface ProductItemRepository extends JpaRepository<ProductItem, Long> {
}

 

Q클래스 생성 확인

Entity 생성 후 gradle > build 를 실행해보면 buildDir 경로 밑에 Q클래스 들이 생성된 것이 확인된다.

 

QueryDSL Repository, Select DTO 생성

QueryDSL에서 Join 결과를 ProductDetail 클래스를 사용하여 select를 할것 이다.

public class ProductDetail {

    private Long id;
    private String name;
    private String content;
    private String itemName;

    public ProductDetail(Long id, String name, String content, String itemName) {
        this.id = id;
        this.name = name;
        this.content = content;
        this.itemName = itemName;
    }
}

Custom Repository 를 JpaRepository 상속한 클래스에서 사용할 수 있는 기능이 있는데 이를 활용한다.

Spring Data 공식 문서에 자세한 설명이 있다.

import com.xxx.boot.base.model.ProductDetail;

import java.util.List;

public interface ProductRepositoryCustom {
    List<ProductDetail> findAllProductDetail();
}

 

Projections.constructor 를 사용하면 join 결과에서 특정 필드만 사용하여 클래스에 매핑시킬수 있다. 매핑시 ProductDetail 클래스의 필드 순서와 Select 필드 순서를 맞춰주어야 한다.

import java.util.List;

import static com.xxx.boot.base.model.QProduct.product;
import static com.xxx.boot.base.model.QProductItem.productItem;

public class ProductRepositoryImpl extends QuerydslRepositorySupport implements ProductRepositoryCustom {

    public ProductRepositoryImpl() {
        super(Product.class);
    }

    @Override
    public List<ProductDetail> findAllProductDetail() {

        return from(product)
                .leftJoin(productItem).on(product.id.eq(productItem.productId))
                .select(getProductDetailProjections())
                .fetch();
    }

    public ConstructorExpression<ProductDetail> getProductDetailProjections() {
        return Projections.constructor(ProductDetail.class,
                product.id, product.name, product.contents, productItem.name);
    }

}

위에서 작성한 ProductRepository 클래스를 ProductRepositoryCustom 상속 받도록 추가해준다.

import com.xxx.boot.base.model.Product;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface ProductRepository extends JpaRepository<Product, Long>, ProductRepositoryCustom {
}

 

Test 코드

import com.xxx.boot.base.model.Product;
import com.xxx.boot.base.model.ProductDetail;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import java.util.List;

import static org.assertj.core.api.Assertions.assertThat;

@SpringBootTest
public class ProductRepositoryTest {

	@Autowired
	private ProductRepository productRepository;

	@Test
	public void productFindTest() {
		List<Product> productList = productRepository.findAll();

		assertThat(productList.size()).isNotNull();
	}

	@Test
	public void findAllProductDetailTest() {
		List<ProductDetail> productDetails = productRepository.findAllProductDetail();

		assertThat(productDetails.size()).isNotNull();
	}
}

실행 결과

 

'Develop > java,spring' 카테고리의 다른 글

JPA 순환 참조 해결 방법  (0) 2020.11.17
Intellij 단축키  (0) 2020.11.12
Junit5 가이드  (0) 2020.11.05
application.properties 설정 목록  (0) 2020.11.04
MSA(Micro Service architecture)  (0) 2020.11.04