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 |