queryDsl

QueryDSL์ด๋ž€?

Querydsl ์ •์  ํƒ€์ž…์„ ์ด์šฉํ•ด์„œ SQL๊ณผ ๊ฐ™์€ ์ฟผ๋ฆฌ๋ฅผ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•ด ์ฃผ๋Š” ํ”„๋ ˆ์ž„์›Œํฌ๋‹ค.

Querydsl - ๋ ˆํผ๋Ÿฐ์Šค ๋ฌธ์„œ

QueryDSL ์™œ ์‚ฌ์šฉํ• ๊นŒ?

Querydsl์€ ํƒ€์ž…์— ์•ˆ์ „ํ•œ ๋ฐฉ์‹์œผ๋กœ HQL ์ฟผ๋ฆฌ๋ฅผ ์‹คํ–‰ํ•˜๊ธฐ ์œ„ํ•œ ๋ชฉ์ ์œผ๋กœ ๋งŒ๋“ค์–ด์กŒ๋‹ค. HQL ์ฟผ๋ฆฌ๋ฅผ ์ž‘์„ฑํ•˜๋‹ค๋ณด๋ฉด String ์—ฐ๊ฒฐ์„ ์ด์šฉํ•˜๊ฒŒ ๋˜๊ณ , ์ด๋Š” ๊ฒฐ๊ณผ์ ์œผ๋กœ ์ฝ๊ธฐ ์–ด๋ ค์šด ์ฝ”๋“œ๋ฅผ ๋งŒ๋“œ๋Š” ๋ฌธ์ œ๋ฅผ ์•ผ๊ธฐํ•œ๋‹ค. String์„ ์ด์šฉํ•ด์„œ ๋„๋ฉ”์ธ ํƒ€์ž…๊ณผ ํ”„๋กœํผํ‹ฐ๋ฅผ ์ฐธ์กฐํ•˜๋‹ค๋ณด๋ฉด ์˜คํƒ€ ๋“ฑ์œผ๋กœ ์ž˜๋ชป๋œ ์ฐธ์กฐ๋ฅผ ํ•˜๊ฒŒ ๋  ์ˆ˜ ์žˆ์œผ๋ฉฐ, ์ด๋Š” String์„ ์ด์šฉํ•ด์„œ HQL ์ž‘์„ฑํ•  ๋•Œ ๋ฐœ์ƒํ•˜๋Š” ๋˜ ๋‹ค๋ฅธ ๋ฌธ์ œ๋‹ค.

ํƒ€์ž…์— ์•ˆ์ „ํ•˜๋„๋ก ๋„๋ฉ”์ธ ๋ชจ๋ธ์„ ๋ณ€๊ฒฝํ•˜๋ฉด ์†Œํ”„ํŠธ์›จ์–ด ๊ฐœ๋ฐœ์—์„œ ํฐ ์ด๋“์„ ์–ป๊ฒŒ ๋œ๋‹ค. ๋„๋ฉ”์ธ์˜ ๋ณ€๊ฒฝ์ด ์ง์ ‘์ ์œผ๋กœ ์ฟผ๋ฆฌ์— ๋ฐ˜์˜๋˜๊ณ , ์ฟผ๋ฆฌ ์ž‘์„ฑ ๊ณผ์ •์—์„œ ์ฝ”๋“œ ์ž๋™์™„์„ฑ ๊ธฐ๋Šฅ์„ ์‚ฌ์šฉํ•จ์œผ๋กœ์จ ์ฟผ๋ฆฌ๋ฅผ ๋” ๋น ๋ฅด๊ณ  ์•ˆ์ „ํ•˜๊ฒŒ ๋งŒ๋“ค ์ˆ˜ ์žˆ๊ฒŒ ๋œ๋‹ค

Querydsl - ๋ ˆํผ๋Ÿฐ์Šค ๋ฌธ์„œ

QueryDSL ์‚ฌ์šฉ

gradle ์„ค์ •

plugins {
    // ...
    id "com.ewerk.gradle.plugins.querydsl" version "1.0.10" // ์ถ”๊ฐ€
    // ...
}

// ...

dependencies {
    // ...
    implementation 'com.querydsl:querydsl-jpa' // ์ถ”๊ฐ€
    // ...
}

// ...

// queryDSL์ด ์ƒ์„ฑํ•˜๋Š” QClass ๊ฒฝ๋กœ ์„ค์ •
def querydslDir = "$buildDir/generated/querydsl"

querydsl {
    jpa = true
    querydslSourcesDir = querydslDir
}

sourceSets {
    main.java.srcDir querydslDir
}

configurations {
    querydsl.extendsFrom compileClasspath
}

compileQuerydsl {
    options.annotationProcessorPath = configurations.querydsl
}

Qํด๋ž˜์Šค ๋งŒ๋“ค๊ธฐ

gradle ์„ค์ •์ด ๋๋‚ฌ์œผ๋ฉด Qํด๋ž˜์Šค๋ฅผ ๋งŒ๋“ค์–ด๋ณด์ž.

๋งŒ๋“œ๋Š” ๋ฐฉ๋ฒ•์€ ์œ„์˜ ๊ทธ๋ฆผ๊ณผ ๊ฐ™์ด ๋จผ์ € Gradle Project(View โ†’ Tool Windows โ†’ Gradle Project)๋ฅผ ์—ด๊ณ  Tasks โ†’ other โ†’ compileJava๋ฅผ ์‹คํ–‰์‹œํ‚ค๋ฉด build โ†’ generated์— Qํด๋ž˜์Šค๊ฐ€ ์ƒ์„ฑ๋œ๋‹ค.

Config ์„ค์ •

@Configuration
public class QueryDSLConfig {

    @PersistenceContext
    private EntityManager entityManager;

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

์ด์ œ JPAQueryFactory๋ฅผ ์ฃผ์ž…๋ฐ›์•„ QueryDSL์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

์‚ฌ์šฉ๋ฒ•

1. ๊ธฐ๋ณธ ์‚ฌ์šฉ๋ฒ•

Post ์—”ํ‹ฐํ‹ฐ

@Entity
public class Post {

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

    @Column(nullable = false)
    private String title;

    @Column(nullable = false)
    private String content;

    // ...
}

PostRepository

public interface PostRepository extends JpaRepository<Post, Long> {
}

PostRepositorySupport

@Repository
public class PostRepositorySupport extends QuerydslRepositorySupport {

    private final JPAQueryFactory jpaQueryFactory;

    public PostRepositorySupport(final JPAQueryFactory jpaQueryFactory) {
        super(Post.class);
        this.jpaQueryFactory = jpaQueryFactory;
    }

    public List<Post> findByTitle(final String title) {
        return jpaQueryFactory.selectFrom(post)
                .where(post.title.eq(title))
                .fetch();
    }
}

selectFrom์— ์žˆ๋Š” post๋Š” ์–ด๋””์„œ ์˜จ ๊ฒƒ์ผ๊นŒ? ์•„๊นŒ compileJava๋ฅผ ์‹คํ–‰์‹œ์ผœ์„œ ๋งŒ๋“  Qํด๋ž˜์Šค์—์„œ ์˜จ ๊ฒƒ์ด๋‹ค.

์ด์ œ ํ…Œ์ŠคํŠธ๋ฅผ ํ•ด๋ณด์ž.

@Test
void findByTitle() {
    postRepository.saveAll(Arrays.asList(
            new Post("test", "content"),
            new Post("test", "content"),
            new Post("test", "content"),
            new Post("title1", "content"),
            new Post("title2", "content"),
            new Post("title3", "content")
    ));

    final List<Post> posts = postRepositorySupport.findByTitle("test");

    assertAll(
            () -> assertThat(posts).hasSize(3),
            () -> assertThat(posts.get(0).getTitle()).isEqualTo("test")
    );
}

2. Spring Data Jpa Custom Repository ์ ์šฉ

์œ„์™€ ๊ฐ™์ด ์‚ฌ์šฉํ•˜๋ฉด ํ•ญ์ƒ 2๊ฐœ์˜ Repository(QueryDSL์˜ Custom Repository, JpaRepository๋ฅผ ์ƒ์†ํ•œ Repository)๋ฅผ ์˜์กด์„ฑ์œผ๋กœ ๋ฐ›์•„์•ผ ํ•œ๋‹ค.

์ด๋ฒˆ์—๋Š” Custom Repository๋ฅผ JpaRepository ์ƒ์† ํด๋ž˜์Šค์—์„œ ์‚ฌ์šฉํ•ด๋ณด์ž.

CustomizedPostRepository

public interface CustomizedPostRepository {
    List<Post> findByTitle(final String title);
}

CustomizedPostRepositoryImpl

public class CustomizedPostRepositoryImpl implements CustomizedPostRepository {

    private final JPAQueryFactory jpaQueryFactory;

    private CustomizedPostRepositoryImpl(final JPAQueryFactory jpaQueryFactory) {
        this.jpaQueryFactory = jpaQueryFactory;
    }

    @Override
    public List<Post> findByTitle(final String title) {
        return jpaQueryFactory.selectFrom(post)
                .where(post.title.eq(title))
                .fetch();
    }
}

PostRepository

public interface PostRepository extends JpaRepository<Post, Long>, CustomizedPostRepository {
}

์ด๋ ‡๊ฒŒ ๊ตฌ์„ฑํ•˜๋ฉด CustomizedPostRepositoryImpl์˜ ์ฝ”๋“œ๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค. PostRepository๋Š” ์–ด๋–ป๊ฒŒ CustomizedPostRepository์„ ์ƒ์†๋ฐ›์•„์„œ CustomizedPostRepositoryImpl์˜ ์ฝ”๋“œ๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์„๊นŒ?

Spring ๊ณต์‹ ๋ฌธ์„œ๋ฅผ ๋ณด์ž. ์š”์•ฝํ•˜๋ฉด CustomizedRepository ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ์ƒ์†ํ•œ Impl ํด๋ž˜์Šค์˜ ์ฝ”๋“œ๋ฅผ ๋‹น์‹ ์˜ Repository์— CustomizedRepository๋ฅผ ์ƒ์†๋ฐ›์•„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค๊ณ  ํ•œ๋‹ค. CustomizedRepository์˜ ์ด๋ฆ„์„ ํ•œ ๋ฒˆ ๋ฐ”๊ฟ”๋ณด์•˜์ง€๋งŒ ์ž˜ ๋™์ž‘ํ–ˆ๋‹ค. ์ค‘์š”ํ•œ ๊ฒƒ์€ Impl ์ ‘๋ฏธ์‚ฌ ๊ฐ™๋‹ค.

The most important part of the class name that corresponds to the fragment interface is the Impl postfix.

Spring ๊ณต์‹ ๋ฌธ์„œ

์ด์ œ ํ…Œ์ŠคํŠธํ•ด๋ณด์ž.

@Test
void findByTitle() {
    postRepository.saveAll(Arrays.asList(
            new Post("test", "content"),
            new Post("test", "content"),
            new Post("test", "content"),
            new Post("title1", "content"),
            new Post("title2", "content"),
            new Post("title3", "content")
    ));

    final List<Post> posts = postRepository.findByTitle("test");

    assertAll(
            () -> assertThat(posts).hasSize(3),
            () -> assertThat(posts.get(0).getTitle()).isEqualTo("test")
    );
}

3. ์ƒ์†/๊ตฌํ˜„ ์—†๋Š” Repository

QueryDSL๋งŒ์œผ๋กœ Repository๋ฅผ ๊ตฌํ˜„ํ•˜๋Š” ๋ฐฉ๋ฒ•์ด๋‹ค.

PostQueryRepository

@Repository
public class PostQueryRepository {

    private final JPAQueryFactory jpaQueryFactory;

    public PostQueryRepository(final JPAQueryFactory jpaQueryFactory) {
        this.jpaQueryFactory = jpaQueryFactory;
    }

    public List<Post> findByTitle(final String title) {
        return jpaQueryFactory.selectFrom(post)
                .where(post.title.eq(title))
                .fetch();
    }
}

ํ…Œ์ŠคํŠธํ•ด๋ณด์ž.

@Test
void findByTitle() {
    postRepository.saveAll(Arrays.asList(
            new Post("test", "content"),
            new Post("test", "content"),
            new Post("test", "content"),
            new Post("title1", "content"),
            new Post("title2", "content"),
            new Post("title3", "content")
    ));
    
    final List<Post> posts = postQueryRepository.findByTitle("test");

    assertAll(
    	    () -> assertThat(posts).hasSize(3),
    	    () -> assertThat(posts.get(0).getTitle()).isEqualTo("test")
    );
}

Last updated