๐ŸŒฑSpring Querydsl

Dohyeon Kongยท2024๋…„ 7์›” 8์ผ
0

Spring๐ŸŒฑ

๋ชฉ๋ก ๋ณด๊ธฐ
7/11
post-thumbnail

Querydsl

Querydsl์ด๋ž€ ์ •์  ํƒ€์ž…์„ ์ด์šฉํ•˜์—ฌ SQL๊ณผ ๊ฐ™์€ ์ฟผ๋ฆฌ๋ฅผ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ๋„๋ก ์ง€์›ํ•˜๋Š” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์˜๋ฏธํ•œ๋‹ค.
์ฆ‰ SQL ํ˜•์‹์˜ ์ฟผ๋ฆฌ๋ฅผ Type-Safeํ•˜๊ฒŒ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ๋„๋ก DSL์„ ์ œ๊ณตํ•˜๋Š” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์ธ ๊ฒƒ์ด๋‹ค.

  • ์—ฌ๊ธฐ์„œ 'dsl'์ด๋ผ๋Š” ์šฉ์–ด๋Š” ํŠน์ • ๋„๋ฉ”์ธ์—์„œ ๋ฐœ์ƒํ•˜๋Š” ๋ฌธ์ œ๋ฅผ ํšจ๊ณผ์ ์œผ๋กœ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ์„ค๊ณ„๋œ ์–ธ์–ด์ด๋‹ค.
  • @Query๋ฅผ ์‚ฌ์šฉํ•ด์„œ ์ง์ ‘ JPQL์˜ ์ฟผ๋ฆฌ๋ฅผ ๋ณดํ†ต ์ž‘์„ฑํ•  ๊ฒƒ์ด๋‹ค.
    ํ•˜์ง€๋งŒ ์ง์ ‘ ๋ฌธ์ž์—ด์„ ์ž…๋ ฅํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์ปดํŒŒ์ผ ์‹œ์ ์—์„œ ์—๋Ÿฌ๋ฅผ ์žก์•„๋‚ด์ง€ ๋ชปํ•œ๋‹ค.
  • Querydsl์˜ ์ฃผ์š” ์žฅ์  ์ค‘ ํ•˜๋‚˜๋กœ์„œ ์ปดํŒŒ์ผ ์‹œ์ ์—์„œ ์ฟผ๋ฆฌ ์˜ค๋ฅ˜๋ฅผ ์žก์•„๋‚ผ ์ˆ˜ ์žˆ๋‹ค.
  • IDE๊ฐ€ ์ œ๊ณตํ•˜๋Š” ์ฝ”๋“œ ์ž๋™ ์™„์„ฑ ๊ธฐ๋Šฅ์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.
  • ์ฃผ์˜์  : Spring Boot 2์—์„œ 3๋กœ ์˜ฌ๋ผ์˜ค๋ฉด์„œ javax๊ฐ€ jakarta๋กœ ๋ณ€๊ฒฝ๋˜์—ˆ๋‹ค.

Querydsl ์‚ฌ์šฉ๋ฐฉ๋ฒ•

1. Querydsl - JPA ์˜์กด์„ฑ ์ถ”๊ฐ€

implementaion 'com.querydsl:querydsl-jpa:5.0.0:jakarata'

ํ•ด๋‹น ์ฝ”๋“œ๋ฅผ ์ถ”๊ฐ€ํ•˜๋Š”๋ฐ, querydsl-jpa ๋ฒ„์ „ ๋ช…์‹œ ๋’ค์— :jakarta ์ถ”๊ฐ€ ๊ผญ ํ•˜๊ธฐ!!

2. QClass ์ƒ์„ฑ์„ ์œ„ํ•œ annotaionProcessor ์ถ”๊ฐ€

    annotationProcessor "com.querydsl:querydsl-apt:${dependencyManagement.importedProperties['querydsl.version']}:jakarta"
    annotationProcessor "jakarta.annotation:jakarta.annotation-api"
    annotationProcessor "jakarta.persistence:jakarta.persistence-api"
  • QClass๋ž€ ์—”ํ‹ฐํ‹ฐ ํด๋ž˜์Šค ์†์„ฑ๊ณผ ๊ตฌ์กฐ๋ฅผ ์„ค๋ช…ํ•ด์ฃผ๋Š” ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ๋ฅผ ์˜๋ฏธํ•œ๋‹ค. Type-Safeํ•˜๊ฒŒ ์ฟผ๋ฆฌ ์กฐ๊ฑด ์„ค์ •์ด ๊ฐ€๋Šฅํ•˜๋‹ค.

3. build.gradle๋‚ด querydsl ์„ธํŒ… ํ•˜๊ธฐ

// querydsl ์„ธํŒ… ์‹œ์ž‘
def querydslDir = "$buildDir/generated/querydsl"

sourceSets {
    main.java.srcDir querydslDir
}
configurations {
    querydsl.extendsFrom compileClasspath
}
๐Ÿ“œbuild.gradle ์ „์ฒด ์ฝ”๋“œ ๐Ÿ“œ
buildscript {
    ext {
        queryDslVersion = "5.0.0"
    }
}

plugins {
    id 'java'
    id 'org.springframework.boot' version '3.3.0'
    id 'io.spring.dependency-management' version '1.1.5'
}

group = 'com.example'
version = '0.0.1-SNAPSHOT'

java {
    toolchain {
        languageVersion = JavaLanguageVersion.of(17)
    }
}

configurations {
    compileOnly {
        extendsFrom annotationProcessor
    }
}

repositories {
    mavenCentral()
}

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
    implementation 'org.springframework.boot:spring-boot-starter-web'
    compileOnly 'org.projectlombok:lombok'
    developmentOnly 'org.springframework.boot:spring-boot-devtools'
    runtimeOnly 'com.mysql:mysql-connector-j'
    annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor'
    annotationProcessor 'org.projectlombok:lombok'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
    testRuntimeOnly 'org.junit.platform:junit-platform-launcher'

    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()
}
// querydsl ์„ธํŒ… ์‹œ์ž‘
def querydslDir = "$buildDir/generated/querydsl"

sourceSets {
    main.java.srcDir querydslDir
}
configurations {
    querydsl.extendsFrom compileClasspath
}

4. DB์™€ ์—ฐ๋™ํ•˜๋Š” ๋ฐฉ๋ฒ•

  • application.properties์— ๋‹ค์Œ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•œ๋‹ค.
spring.application.name=QueryDsl-tutorial
spring.datasource.url=jdbc:mysql://localhost:3306/querydsl
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.username={๋‚˜์˜ ์‚ฌ์šฉ์ž ์ด๋ฆ„}
spring.datasource.password={๋‚˜์˜ ์‚ฌ์šฉ์ž๊ฐ€ ์ ‘๊ทผํ•  ๋น„๋ฐ€๋ฒˆํ˜ธ}
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQLDialect

logging.level.org.springframework.data.jpa.repository.query=DEBUG
logging.level.com.querydsl.jpa.impl.JPAQueryFactory=DEBUG

5. Entity ํด๋ž˜์Šค ๋งŒ๋“ค๊ธฐ

package com.springboot.querydsltutorial.entity;

import com.fasterxml.jackson.annotation.JsonBackReference;
import com.fasterxml.jackson.annotation.JsonManagedReference;
import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;

import java.util.List;

@Entity
@NoArgsConstructor
@AllArgsConstructor
@Getter
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    @Column(nullable = false)
    private String name;
    @Column(nullable = false)
    private String email;
    @OneToMany(mappedBy = "user")
    @JsonManagedReference
    private List<Post> posts;
}
package com.springboot.querydsltutorial.entity;

import com.fasterxml.jackson.annotation.JsonBackReference;
import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Entity
@NoArgsConstructor
@AllArgsConstructor
@Getter
public class Post {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(nullable = false)
    private String title;

    @Column(nullable = false)
    private String content;

    @ManyToOne(fetch = FetchType.LAZY)
    @JsonBackReference
    @JoinColumn(name = "user_id")
    private User user;
}

์—ฌ๊ธฐ๊นŒ์ง€ ์ž˜ ๋”ฐ๋ผ์™”์œผ๋ฉด build๋ฅผ ํ•œ๋ฒˆ ์‹คํ–‰ํ•ด๋ณด์ž!
๊ทธ๋ ‡๊ฒŒ ๋˜๋ฉด Q๋„๋ฉ”์ธ ํด๋ž˜์Šค๊ฐ€ ์ƒ๊ธฐ๋Š” ๊ฑธ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.

๋‚˜๋Š” User ์—”ํ‹ฐํ‹ฐ์™€ Post ์—”ํ‹ฐํ‹ฐ๋ฅผ ๋งŒ๋“ค์—ˆ๊ธฐ ๋•Œ๋ฌธ์— QUser ํด๋ž˜์Šค, QPost ํด๋ž˜์Šค๊ฐ€ ๋งŒ๋“ค์–ด ์กŒ๋‹ค.

Q๋„๋ฉ”์ธ ํด๋ž˜์Šค๋Š” JPA ์—”ํ‹ฐํ‹ฐ๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ์ƒ์„ฑ๋˜์–ด ํ•ด๋‹น ์—”ํ‹ฐํ‹ฐ์˜ ํ•„๋“œ์™€ ๊ด€๊ณ„๋ฅผ ์ •์ ์œผ๋กœ ๋‚˜ํƒ€๋‚ด๋Š” java ํด๋ž˜์Šค๋‹ค.


  • Q๋„๋ฉ”์ธ ํด๋ž˜์Šค๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์ฟผ๋ฆฌ๋ฅผ ์ž‘์„ฑํ•  ๋•Œ ๋ฌธ์ž์—ด ๊ธฐ๋ฐ˜์˜ ํ•„๋“œ๋ช…์ด๋‚˜ ํ…Œ์ด๋ธ”๋ช…์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ๋ณด๋‹ค ํƒ€์ž… ์•ˆ์ •์„ฑ์„ ์ œ๊ณต ๋ฐ›์„ ์ˆ˜ ์žˆ๋‹ค.

6. JPAQueryFactory ๋นˆ์„ ๋“ฑ๋กํ•˜๊ธฐ

package com.springboot.querydsltutorial.config;

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

@Configuration
public class QueryDSLConfiguration {
    @PersistenceContext
    EntityManager entityManager;
    
    @Bean
    public JPAQueryFactory jpaQueryFactory() {
        return new JPAQueryFactory(entityManager);
    }
}
  • QueryDSL ์‚ฌ์šฉ์„ ์œ„ํ•œ Factory๋ฅผ Bean์œผ๋กœ ๋“ฑ๋กํ•œ๋‹ค.
  • JPAQueryFactory๋Š” QueryDSL์—์„œ ์ œ๊ณตํ•˜๋Š” ์ฃผ์š” ํด๋ž˜์Šค ์ค‘ ํ•˜๋‚˜์ด๋ฉฐ, ํ•ด๋‹น Config ํŒŒ์ผ์„ ๋งŒ๋“ค์–ด JPAQueryFactory๋ฅผ QueryDSL์„ ์ด์šฉํ•œ JPA ์ฟผ๋ฆฌ๋ฅผ ๋นŒ๋“œํ•˜๋Š” Factory ์—ญํ• ๋กœ์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋„๋ก Bean์œผ๋กœ ๋“ฑ๋กํ•ด์•ผ ํ•œ๋‹ค.

@PersistenceContext
JPA์—์„œ ์‚ฌ์šฉํ•˜๋ฉฐ Entity Manager๋ฅผ ํ•„๋“œ๋‚˜ ๋ฉ”์„œ๋“œ ๋งค๊ฐœ๋ณ€์ˆ˜๋กœ ์ฃผ์ž…ํ•  ๋•Œ ์‚ฌ์šฉํ•œ๋‹ค.
Entity Manager๋Š” JPA์—์„œ ์˜์†์„ฑ ์ปจํ…์ŠคํŠธ๋ฅผ ๊ด€๋ฆฌํ•˜๋ฏ€๋กœ ํŠธ๋žœ์žญ์…˜ ๊ด€๋ฆฌ, ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์™€ ์ƒํ˜ธ์ž‘์šฉ(CRUD) ๋“ฑ์„ ์ง€์›ํ•œ๋‹ค.


7. ์‚ฌ์šฉํ•  ๋ ˆํฌ์ง€ํ„ฐ๋ฆฌ ์„ ์–ธํ•˜๊ธฐ

  • Repository๋Š” ์ด 3๊ฐœ๋ฅผ ๋งŒ๋“ค์–ด์•ผ ํ•œ๋‹ค.
    1. JpaRepository(Interface)
    2. ์ง์ ‘ ์ปค์Šคํ…€ํ•œ CustomRepository(Interface)
    3. ์ง์ ‘ ์ปค์Šคํ…€ํ•œ CustomRepository(Interface)๋ฅผ ๊ตฌํ˜„ํ•œ CustomRepositoryImpl(Class)๊ตฌํ˜„์ฒด๋ฅผ ์‚ฌ์šฉํ•˜๊ฒŒ ๋œ๋‹ค.
  1. JpaRepository(Interface)
// UserRepository๋กœ์„œ JpaRepository๋ž‘ UserRepositoryCustom์„ ์ƒ์†๋ฐ›๋Š”๋‹ค. 
// (๋‘˜ ๋‹ค ์ธํ„ฐํŽ˜์ด์Šค!!)
@Repository
public interface UserRepository extends JpaRepository<User, Long>, UserRepositoryCustom {
}
  1. CustomRepository(Interface)
package com.springboot.querydsltutorial.repository;

import com.springboot.querydsltutorial.entity.User;

import java.util.List;

// ์šฐ๋ฆฌ๊ฐ€ ์‚ฌ์šฉํ•  ๋ฉ”์„œ๋“œ๋ฅผ ์„ ์–ธํ•ด์ค€๋‹ค.
public interface UserRepositoryCustom {
    List<User> findUsersByName(String name);
    List<User> findUsersByNameAndEmail(String name, String email);
}
  1. CustomRepository(Class)
// ์šฐ๋ฆฌ๋Š” ํ•ด๋‹น CustomRepository๋ฅผ ์ด์šฉํ•ด์„œ QueryDsl์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋˜๊ณ , ๋™์  ์ฟผ๋ฆฌ ๋˜ํ•œ ๋งŒ๋“ค ์ˆ˜ ์žˆ๋‹ค.
package com.springboot.querydsltutorial.repository;

import com.querydsl.core.BooleanBuilder;
import com.querydsl.jpa.impl.JPAQueryFactory;
import com.springboot.querydsltutorial.entity.QPost;
import com.springboot.querydsltutorial.entity.QUser;
import com.springboot.querydsltutorial.entity.User;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Repository;
import java.util.List;

import static com.springboot.querydsltutorial.entity.QPost.post;

@Repository
@RequiredArgsConstructor
public class UserRepositoryImpl implements UserRepositoryCustom {
    private final JPAQueryFactory queryFactory;

    @Override
    public List<User> findUsersByName(String name) {
        // RequestParam์œผ๋กœ ๋ฐ›์•„์˜จ ์ด๋ฆ„์„ ์กฐ๊ฑด์‹์— ์‚ฌ์šฉํ•ด์„œ ํ•ด๋‹น ์กฐ๊ฑด์— ๋งž๋Š” ์—”ํ‹ฐํ‹ฐ ๊ฐ€์ ธ์˜ค๊ธฐ
        QUser user = QUser.user;
        QPost post = QPost.post;
        return queryFactory.select(user).from(user)
                .leftJoin(user.posts, post).fetchJoin()
                .where(user.name.eq(name))
                .fetch();
    }

    @Override
    public List<User> findUsersByNameAndEmail(String name, String email) {
        QUser user = QUser.user;
        QPost post = QPost.post;
        BooleanBuilder builder = new BooleanBuilder();
        // BooleanBuilder๋Š” ๋‹ค์ค‘ ์กฐ๊ฑด ์ฒ˜๋ฆฌ๋ฅผ ํ•  ๋•Œ, '()'๊ฐ€ ๋“ค์–ด๊ฐ€๋Š” ์ƒํ™ฉ์—์„œ ์ข‹๊ณ 
        // BooleanExpression์€ ๋‹จ์ˆœํ•œ ์ฟผ๋ฆฌ๋‚˜ ๋‹จ์ผ ์กฐ๊ฑด์ผ ๋•Œ

        if (name != null && !name.isEmpty()) {
            builder.and(user.name.eq(name));
        }
        if( email != null && !email.isEmpty()) {
            builder.and(user.email.eq(email));
        }
        return queryFactory.selectFrom(user)
                .leftJoin(user.posts, post).fetchJoin()
                .where(builder)
                .fetch();
    }
}
  • ๋™์  ์ฟผ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” BooleanBuilder๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค. ์ฆ‰
    BooleanBuilder๋ฅผ ์ด์šฉํ•ด ์กฐ๊ฑด์ ˆ์„ ์ถ”๊ฐ€ํ•œ ๋’ค where์ ˆ์— ์ „๋‹ฌํ•ด์„œ ๋™์ ์œผ๋กœ ๊ตฌํ˜„ํ•ด ๋‚ผ ์ˆ˜ ์žˆ๋‹ค.
  • JPAQueryFactory๋ฅผ ์„ ์–ธํ•  ๋•Œ Entity์˜ ํƒ€์ž…์„ ๋ช…์‹œํ•ด์ฃผ์ง€ ์•Š์•„๋„ ๋˜๊ณ  selectFrom()๋ถ€ํ„ฐ ์‹œ์ž‘ํ•œ๋‹ค. ๋งŒ์•ฝ ์ „์ฒด๊ฐ€ ์•„๋‹ˆ๋ผ ์ผ๋ถ€๋งŒ ์กฐํšŒํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด .select().from()๊ณผ ๊ฐ™์ด ๋ถ„๋ฆฌํ•ด์„œ ์ž‘์„ฑํ•˜๋ฉด ๋œ๋‹ค. ๋˜ํ•œ, select()์— select(a,b,c)์™€ ๊ฐ™์ด ์กฐํšŒํ•˜๊ณ  ์‹ถ์€ ์ผ๋ถ€๋งŒ ๋ฝ‘์•„๋‚ผ ์ˆ˜ ์žˆ๋‹ค.
  • return ๋ฐ›์œผ๋ ค๋ฉด fetch() ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•ด์•ผ ํ•œ๋‹ค.
    ํ•œ ๊ฑด์˜ ์กฐํšŒ ๊ฒฝ์šฐ fetchOne(), ์—ฌ๋Ÿฌ ๊ฑด์˜ ์กฐํšŒ ๊ฒฐ๊ณผ ์ค‘ 1๊ฑด ๋ฐ˜ํ™˜์€ fetchFirst(), ์กฐํšŒ ๊ฒฐ๊ณผ์˜ ๊ฐœ์ˆ˜๋Š” fetchCount(), ์กฐํšŒ ๊ฒฐ๊ณผ ๋ฆฌ์ŠคํŠธ์™€ ๊ฐœ์ˆ˜๋ฅผ ํฌํ•จํ•œ ๊ฒƒ์€ fetchResults()๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๋œ๋‹ค.

BooleanBuilder์™€ BooleanExpression์˜ ์ฐจ์ด์ 

  • BooleanBuilder
    - ๊ฐ€๋ณ€ ๊ฐ์ฒด์ด๋ฉฐ, ์กฐ๊ฑด์„ ๋™์ ์œผ๋กœ ์ถ”๊ฐ€ํ•˜๊ฑฐ๋‚˜ ์ œ๊ฑฐํ•  ์ˆ˜ ์žˆ๋‹ค๋Š” ์ ์ด ์กด์žฌํ•œ๋‹ค. ๊ทธ๋Ÿฌ๋ฏ€๋กœ ์ƒํ™ฉ์— ๋”ฐ๋ผ ์กฐ๊ฑด์„ ์กฐํ•ฉํ•ด์•ผํ•˜๋Š” ๋™์  ์ฟผ๋ฆฌ์— ๋” ์œ ์šฉํ•˜๊ฒŒ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

  • BooleanExpression
    - ๋ถˆ๋ณ€ ๊ฐ์ฒด์ด๋ฉฐ, ํ•œ๋ฒˆ ์„ค์ •๋œ ์กฐ๊ฑด์„ ๋ณ€๊ฒฝํ•  ์ˆ˜ ์—†๋‹ค๋Š” ์ ์ด ์กด์žฌํ•œ๋‹ค. ๊ทธ๋Ÿฌ๋ฏ€๋กœ ์ƒˆ๋กœ์šด ์กฐ๊ฑด์ด ์ถ”๊ฐ€๋˜๋ฉด ์ƒˆ๋กœ์šด BooleanExpression์„ ์ƒ์„ฑํ•ด์•ผ๋œ๋‹ค.

Expressions.numberTemplate

  • Expressions.numberTemplate์€ Querydsl์—์„œ ์ œ๊ณตํ•˜๋Š” ๋ฉ”์†Œ๋“œ ์ค‘ ํ•˜๋‚˜๋กœ, SQL ํ…œํ”Œ๋ฆฟ ํ‘œํ˜„์‹์„ ์‚ฌ์šฉํ•˜์—ฌ ์ฟผ๋ฆฌ์˜ ํŠน์ • ๋ถ€๋ถ„์„ ์ •์˜ํ•  ๋•Œ ์‚ฌ์šฉ๋œ๋‹ค.

7. Service๋‹จ ๋งŒ๋“ค๊ธฐ

package com.springboot.querydsltutorial.service;

import com.springboot.querydsltutorial.dto.UserResponseDto;
import com.springboot.querydsltutorial.entity.User;
import com.springboot.querydsltutorial.repository.UserRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;
import java.util.stream.Collectors;

@Service
@RequiredArgsConstructor
public class UserService {
    private final UserRepository userRepository;

    @Transactional(readOnly = true)
    public List<UserResponseDto> findUsersByName(String name) {
        List<User> userList = userRepository.findUsersByName(name);
        List<UserResponseDto> userResponseDtoList = userList.stream().map(UserResponseDto::of).collect(Collectors.toList());
        return userResponseDtoList;
    }
    @Transactional(readOnly = true)
    public List<UserResponseDto> findUsersByNameAndEmail(String name, String email) {
        List<User> users = userRepository.findUsersByNameAndEmail(name, email);
        List<UserResponseDto> userResponseDtoList = users.stream().map(UserResponseDto::of).collect(Collectors.toList());
        return userResponseDtoList;
    }
}

Service๋‹จ์—์„œ๋Š” ๊ทธ๋ƒฅ UserRepository์™€ ๊ฐ™์ด JpaRepository์™€ CustomRepository (ํ˜„์žฌ ์ฝ”๋“œ์ƒ์—์„œ๋Š” UserRepository)๋ฅผ ์ƒ์†๋ฐ›์€ ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ DI๋ฐ›์•„์„œ ์‚ฌ์šฉํ•˜๋ฉด ๋œ๋‹ค.


8. Controller๋‹จ์—์„œ ๋ฐ์ดํ„ฐ ๋ฝ‘์•„๋‚ด๊ธฐ

package com.springboot.querydsltutorial.controller;

import com.springboot.querydsltutorial.dto.UserResponseDto;
import com.springboot.querydsltutorial.service.UserService;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@RestController
@RequiredArgsConstructor
public class UserController {
    private final UserService userService;

    @GetMapping("/users")
    public List<UserResponseDto> getUsersByName(@RequestParam("name") String name) {
        return userService.findUsersByName(name);
    }

    @GetMapping("/users/search")
    public List<UserResponseDto> getUsersByNameAndEmail(@RequestParam(required = false) String name,
                                                        @RequestParam(required = false) String email) {
        return userService.findUsersByNameAndEmail(name, email);
    }
    // /users/search : ๋ชจ๋“  ์‚ฌ์šฉ์ž๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค.
    // /users/search?name=์•ˆ๋…• : ์ด๋ฆ„์ด ์•ˆ๋…•์ธ ์‚ฌ์šฉ์ž์˜ ๋ฐ์ดํ„ฐ๋ฅผ ๋ชจ๋‘ ๋ฐ˜ํ™˜ํ•œ๋‹ค.
    // /users/search?email=hello@example.com : ์ด๋ฉ”์ผ์ด hello@example.com์ธ ์‚ฌ์šฉ์ž์˜ ๋ฐ์ดํ„ฐ๋ฅผ ๋ชจ๋‘ ๋ฐ˜ํ™˜ํ•œ๋‹ค.
    // /users/search?name=์•ˆ๋…•&email=hello@example.com : ์ด๋ฆ„์ด "์•ˆ๋…•"์ด๊ณ  ์ด๋ฉ”์ผ์ด "hello@example.com"์ธ ์‚ฌ์šฉ์ž๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค.
}

๊ฒฐ๊ณผ. ์‹ค์ œ ๊ตฌํ˜„ํ•œ ์ฝ”๋“œ์˜ REST API๐Ÿ”ซ

/users?name=์•ˆ๋…• : ์ด๋ฆ„์ด ์•ˆ๋…•์ธ ์‚ฌ์šฉ์ž์˜ ๋ฐ์ดํ„ฐ๋ฅผ ๋ชจ๋‘ ๋ฐ˜ํ™˜ํ•œ๋‹ค.

๋™์  ์ฟผ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•œ GET Method

/users/search?name=์•ˆ๋…• : ์ด๋ฆ„์ด ์•ˆ๋…•์ธ ์‚ฌ์šฉ์ž์˜ ๋ฐ์ดํ„ฐ๋ฅผ ๋ชจ๋‘ ๋ฐ˜ํ™˜ํ•œ๋‹ค.

/users/search?email=email@naver.com : ์ด๋ฉ”์ผ์ด email@naver.com์ธ ์‚ฌ์šฉ์ž์˜ ๋ฐ์ดํ„ฐ๋ฅผ ๋ชจ๋‘ ๋ฐ˜ํ™˜ํ•œ๋‹ค.

/users/search?name=์•ˆ๋…•&email=email@naver.com : ์ด๋ฆ„์ด "์•ˆ๋…•"์ด๊ณ  ์ด๋ฉ”์ผ์ด "email@naver.com"์ธ ์‚ฌ์šฉ์ž๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค.


Querydsl, ๊ทธ๋Ÿผ ์–ธ์ œ ์‚ฌ์šฉํ• ๊นŒ?๐Ÿง

  • ๊ฐ€๋…์„ฑ ํ–ฅ์ƒ, ๋ฉ”์„œ๋“œ ๋„ค์ด๋ฐ์„ ํ†ตํ•ด ์ฟผ๋ฆฌ ์กฐ๊ฑด, ์ •๋ ฌ ๋ฐฉ์‹์„ ์œ ์ถ”ํ•  ์ˆ˜ ์žˆ๋‹ค.
  • ๊ณ ์ •๋œ SQL ์ฟผ๋ฆฌ๋ฅผ ์ž‘์„ฑํ•˜์ง€ ์•Š๊ณ  ๋™์ ์œผ๋กœ ์ฟผ๋ฆฌ๋ฅผ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ๋‹ค.
  • ๋ฉ”์„œ๋“œ ๋ถ„๋ฆฌ๋ฅผ ํ†ตํ•œ ์žฌ์‚ฌ์šฉ์„ฑ์ด ํ–ฅ์ƒ๋œ๋‹ค.
  • Type-Safeํ•œ Qํด๋ž˜์Šค๋ฅผ ํ†ตํ•œ ๋ฌธ๋ฒ•์œผ๋กœ ์ธํ•ด์„œ ๋Ÿฐํƒ€์ž„ ์—๋Ÿฌ๋ฅผ ๋ฐฉ์ง€ํ•  ์ˆ˜ ์žˆ๋‹ค.

์ฆ‰ ๊ฐ„๋‹จํ•˜๊ฒŒ ์ •๋ฆฌํ•˜์ž๋ฉด ๋™์ ์œผ๋กœ ์กฐ๊ฑด๋ฌธ์„ ์ฃผ๊ณ  ์‹ถ์„๋•Œ, ๋Ÿฐํƒ€์ž„ ์—๋Ÿฌ๋ฅผ ๋ฐฉ์ง€ํ•˜๊ณ  ์‹ถ์„๋•Œ ์‚ฌ์šฉํ•œ๋‹ค.
(Ex. ํ•„ํ„ฐ๋ฅผ ๊ฑธ์–ด์„œ ๊ฒ€์ƒ‰์„ ํ•˜๋Š” ๊ฒฝ์šฐ)

  • ํ•˜์ง€๋งŒ 1์ฐจ ์บ์‹œ์˜ ์žฅ์ ์„ ๋ˆ„๋ฆด ์ˆ˜ ์—†๋‹ค. (ํ•ด๋‹น ๋‹จ์ ์€ JPQL์˜ ํŠน์ง•์—๋„ ํฌํ•จ๋œ๋‹ค.)

profile
์ฒœ์ฒœํžˆ, ๊พธ์ค€ํžˆ, ๊ทธ๋ฆฌ๊ณ  ๋๊นŒ์ง€

0๊ฐœ์˜ ๋Œ“๊ธ€