QueryDsl

현재 Sensor API는 검색조건이 전혀 없습니다.

검색조건을 추가하기 위해 QueryDsl을 사용하겠습니다.

JPQL이나 JPA Criteria를 사용하면 검색 조건을 추가할 수 있지만, 이 방식들에는 한계가 있습니다.

JPQL과 JPA Criteria는 문자열 기반의 쿼리 작성 방식을 사용합니다. 이로 인해 쿼리를 작성하는 데 번거로움이 있으며, 오류를 발견하기가 어렵습니다. 또한 복잡한 쿼리를 작성해야 하는 경우 유지보수가 어려워질 수 있습니다.

 

반면, QueryDsl은 이러한 JPQL과 JPA Criteria의 단점을 보완할 수 있습니다

QueryDsl은 타입 기반의 쿼리 작성 방식을 사용하므로, 컴파일 시점에 오류를 발견할 수 있습니다.

또한 동적 쿼리 생성을 위한 풍부한 API를 제공하여, 유연하고 가독성 높은 쿼리 작성이 가능합니다.

하지만 집계내거나 복잡한 쿼리에서는 한계가 있으니 상황에 맞춰 사용해주세요

 

개발 환경

  • Spring Boot 3.2.4
  • Spring Data Jpa 3.2.4
  • QueryDsl 5.1

환경 설정

intellij

File > Settings > Build, Execution, Deployment > Build Tools > Gradle

build.gradle

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

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

java {
    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-web'
    compileOnly 'org.projectlombok:lombok'
    runtimeOnly 'com.h2database:h2'
    annotationProcessor 'org.projectlombok:lombok'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'

    // Swagger
    implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.4.0'

    runtimeOnly 'com.microsoft.sqlserver:mssql-jdbc'
    runtimeOnly 'com.mysql:mysql-connector-j'

    // QueryDSL
    implementation 'com.querydsl:querydsl-jpa:5.1.0:jakarta'
    annotationProcessor 'com.querydsl:querydsl-apt:5.1.0:jakarta'
    annotationProcessor "jakarta.annotation:jakarta.annotation-api"
    annotationProcessor 'jakarta.persistence:jakarta.persistence-api'
}

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

// Querydsl 설정 시작
def generated = 'src/main/generated'

tasks.withType(JavaCompile).configureEach {
    options.generatedSourceOutputDirectory = file(generated)
}

sourceSets {
    main.java.srcDirs += "$projectDir/$generated"
}

clean {
    delete file('src/main/generated')
}
// Querydsl 설정 끝

 

위 설정을 마치셨으면 ./gradlew build 시 generated 폴더가 생성됩니다

여기에 QueryDsl이 만든 도메인 특화 언어(DSL: Domain Specific Language) 파일인 QClass 들이 생성됩니다.

 

entity.Sensor.java

변경된건 없지만 이전글을 안보는 경우도 있을 것 같아 넣어뒀습니다

@Entity
@Getter
@Setter
@Table(name = "sensors")
public class Sensor {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String code;
    private String name;
    private String type; // 온도, 습도, 전력, 움직임 등
}

config.QuerydslConfig.java

@Configuration
public class QuerydslConfig {
    @PersistenceContext
    private EntityManager entityManager;

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

 

dto.SensorSelectRequest.java

id, code, type은 여러개를 받을 수 있도록 배열로 선언하고

이름은 일부 포함하는 like 문을 사용하기 위해 아래처럼 구성했습니다

@Getter
@Setter
public class SensorSelectRequest {
    private Long[] id;
    private String[] code;
    private String[] type;
    private String likeName;
}

 

expression.SensorExpression.java

검색조건을 위한 클래스생성은 안해도 되지만, 재사용성을 위해서 만들어 놓고 쓰는걸 추천드립니다

@RequiredArgsConstructor
public class SensorExpression {
    private final QSensor entity;

    public BooleanExpression inId(Long... param) {
        return param == null ? null : entity.id.in(param);
    }

    public BooleanExpression inCode(String... param) {
        return param == null ? null : entity.code.in(param);
    }

    public BooleanExpression inType(String... param) {
        return param == null ? null : entity.type.in(param);
    }

    public BooleanExpression likeName(String param) {
    	// import org.apache.commons.lang3.StringUtils;
        return StringUtils.isEmpty(param) ? null : entity.name.contains(param);
    }
}

 

repository.SensorQueryRepository.java

@Repository
@RequiredArgsConstructor
public class SensorQueryRepository {

    private final JPAQueryFactory jpaQueryFactory;

    public List<Sensor> select(SensorSelectRequest param) {
        var sensor = QSensor.sensor;
        var sensorQueryExpression = new SensorExpression(sensor);
        return jpaQueryFactory
                .selectFrom(sensor)
                .where(
                        sensorQueryExpression.inId(param.getId()),
                        sensorQueryExpression.inCode(param.getCode()),
                        sensorQueryExpression.inType(param.getType()),
                        sensorQueryExpression.likeName(param.getLikeName())
                )
                .fetch();
    }
}

 

service.SensorService.java

@Service
@Transactional
@RequiredArgsConstructor
public class SensorService {

    private final SensorRepository sensorRepository;
    private final SensorQueryRepository sensorQueryRepository;

    public List<Sensor> select(SensorSelectRequest param) {
        return sensorQueryRepository.select(param);
    }

    public Sensor create(Sensor param) {
        return sensorRepository.save(param);
    }

    public Sensor update(Sensor param) {
        return sensorRepository.save(param);
    }

    public boolean delete(Long param) {
        sensorRepository.deleteById(param);
        return true;
    }
}

 

controller.SensorController.java

@RestController
@RequestMapping("api/sensor")
@RequiredArgsConstructor
public class SensorController {

    private final SensorService sensorService;

    @GetMapping
    public List<Sensor> select(@ParameterObject SensorSelectRequest param) {
        return sensorService.select(param);
    }

    @PostMapping
    public Sensor create(@RequestBody Sensor param) {
        return sensorService.create(param);
    }

    @PutMapping
    public Sensor update(@RequestBody Sensor param) {
        return sensorService.update(param);
    }

    @DeleteMapping("{id}")
    public boolean delete(@PathVariable("id") Long param) {
        return sensorService.delete(param);
    }
}

 

확인

 

검색조건이 동작하는것을 확인할 수 있습니다

 

코드는 GitHub - ahnjehoon/spring-boot3-tutorial 여기서 확인 가능합니다

'JAVA > SPRING' 카테고리의 다른 글

Spring Boot 3 Tutorial - 5 Security(2)  (0) 2024.04.16
Spring Boot 3 Tutorial - 5 Security(1)  (2) 2024.04.12
Spring Boot 3 Tutorial - 3 DBMS switch  (0) 2024.04.04
Spring Boot 3 Tutorial - 2 CRUD  (0) 2024.04.04
Spring Boot 3 Tutorial - 1 Hello World  (0) 2024.04.04

Hello World

  • 누군가 저와 같은 삽질을 반복할 것 같아 작성합니다
  • DBMS는 H2를 사용하다가 MySQL 및 MsSQL 테스트 예정입니다
  • JPA와 QueryDsl을 사용할 예정입니다
  • Spring Security와 JWT를 통해 인증할 예정입니다

환경

  • Open JDK 17
  • Spring Boot 3.2.4
  • Intellij Community 2023.3.6

개발환경 설정

  • JAVA 환경설정은 다른 분들이 올려놓으신 자료가 많기 때문에 넘어갑니다
  • 다음 사이트로 이동해서 스프링 초기 세팅을 구성합니다
    Spring Initializr
  • 제가 설정한 항목은 다음과 같습니다
    • Spring Web
    • Spring Data JPA
    • H2
    • Lombok

 

 

서버 띄워보기

  • src > main > java > [프로젝트 주소] > [Application.java]를 더블클릭합니다
  • 윈도우 기준 SHIFT + F9를 눌러 서버를 실행합니다

 

  • 로그에 Tomcat started on port 8080이라는 명령어가 아래에서 두 번째 줄에 보이면
    브라우저를 열고 http://localhost:8080 으로 이동해 봅니다

  • 위와 같은 페이지를 만난다면 서버가 정상적으로 띄워진 겁니다

Swagger  적용

build.gradle
...
dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
	implementation 'org.springframework.boot:spring-boot-starter-web'
	compileOnly 'org.projectlombok:lombok'
	runtimeOnly 'com.h2database:h2'
	annotationProcessor 'org.projectlombok:lombok'
	testImplementation 'org.springframework.boot:spring-boot-starter-test'

	// Swagger
	implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.4.0'
}
...

 

  •  루트경로로 접근 시 바로 Swagger 창이 열리도록 변경합니다

application.properties

spring.application.name=tutorial

springdoc.swagger-ui.path=/

 

  • 서버를 재시작하면 다음 화면이 정상적으로 출력되어야 합니다

 

Hello World API 생성

  • 이제 HelloWorld API를 생성해 보겠습니다
com/jehoon/tutorial/controller/HelloController.java
package com.jehoon.tutorial.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HelloController {
    @GetMapping("/hello-world")
    public String get() {
        return "Hello World";
    }
}

 

  • 서버를 재시작하고 브라우저를 확인하면 hello-controller 가 생긴 것을 확인할 수 있습니다.
  • /hello-world API > Try it out > Execute
    다음처럼 Hello World가 정상출력됩니다

'JAVA > SPRING' 카테고리의 다른 글

Spring Boot 3 Tutorial - 5 Security(2)  (0) 2024.04.16
Spring Boot 3 Tutorial - 5 Security(1)  (2) 2024.04.12
Spring Boot 3 Tutorial - 4 QueryDsl  (0) 2024.04.08
Spring Boot 3 Tutorial - 3 DBMS switch  (0) 2024.04.04
Spring Boot 3 Tutorial - 2 CRUD  (0) 2024.04.04

+ Recent posts