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 |