QueryDSL - Basic
💡 QueryDSL
QueryDSL은 SQL과 유사한 문법으로 Java와 Kotlin 등의 언어로 쿼리를 작성할 수 있는 라이브러리입니다. 다음은 Kotlin 코드를 이용한 QueryDSL 사용 방법에 대해 구체적으로 설명하겠습니다.
Gradle 설정
QueryDSL을 사용하기 위해서는 Gradle에 QueryDSL 관련 의존성을 추가해야 합니다. build.gradle 파일에 다음과 같이 의존성을 추가합니다.
dependencies {
implementation("com.querydsl:querydsl-jpa")
kapt("com.querydsl:querydsl-apt")
}
Entity 클래스 생성
QueryDSL을 사용하려면 Entity 클래스를 먼저 생성해야 합니다.
예를 들어, 다음과 같은 User 엔티티 클래스가 있다고 가정합니다.
@Entity
class User(
@Id
val id: Long,
val name: String,
val age: Int,
val email: String
)
QEntity 클래스 생성
QueryDSL은 엔티티 클래스에 대한 메타 모델 클래스인 QEntity 클래스를 생성해야 합니다.
QEntity 클래스는 compile-time에 생성되며, 엔티티 클래스의 필드와 매핑되는 필드를 가지고 있습니다.
QEntity 클래스는 다음과 같이 생성합니다.
object QUser {
val user = QUser("user")
class QUser(name: String) : EntityPathBase<User>(User::class.java, name) {
val id = NumberPath<Long>("id")
val name = StringPath("name")
val age = NumberPath<Int>("age")
val email = StringPath("email")
}
}
Query 작성
QueryDSL을 사용하여 쿼리를 작성하려면 JPQLQueryFactory 클래스를 사용해야 합니다.
다음은 User 엔티티에서 name이 "john"인 User를 조회하는 예제 코드입니다.
@Repository
class UserRepository(
private val entityManager: EntityManager
) {
private val jpaQueryFactory: JPAQueryFactory = JPAQueryFactory(entityManager)
fun findByName(name: String): List<User> {
return jpaQueryFactory
.selectFrom(QUser.user)
.where(QUser.user.name.eq(name))
.fetch()
}
}
위의 코드에서 EntityManager를 주입받아 JPAQueryFactory 객체를 생성합니다.
그리고, JPAQueryFactory 객체를 사용하여 쿼리를 작성합니다.
이 예제에서는 "john"이라는 이름을 가진 User를 조회하는 쿼리를 작성했습니다.
BooleanBuilder와 BooleanExpression 사용하기
BooleanBuilder를 사용하면 동적으로 조건을 추가할 수 있습니다. BooleanExpression은 조건식을 나타내는 인터페이스로, 여러 조건을 조합할 수 있습니다.
예를 들어, User 엔티티에서 이름이 "john"이고 나이가 20 이상인 User를 조회하는 쿼리를 작성해보겠습니다. 다음은 UserRepository에서 해당 쿼리를 작성하는 코드입니다.
@Repository
class UserRepository(
private val entityManager: EntityManager
) {
private val jpaQueryFactory: JPAQueryFactory = JPAQueryFactory(entityManager)
fun searchUser(name: String?, age: Int?): List<User> {
val builder = BooleanBuilder()
if (name != null) {
builder.and(QUser.user.name.eq(name))
}
if (age != null) {
builder.and(QUser.user.age.goe(age))
}
return jpaQueryFactory
.selectFrom(QUser.user)
.where(builder)
.fetch()
}
}
위의 코드에서 BooleanBuilder를 사용하여 동적으로 조건을 추가했습니다.
만약 이름이 전달되지 않으면 해당 조건은 추가되지 않고,
나이가 전달되지 않으면 해당 조건은 추가되지 않습니다.
마지막으로 where 절에 builder를 추가하여 쿼리를 완성합니다.
위 코드에서 builder.and() 메서드를 사용하여 조건을 추가할 수 있습니다.
and() 메서드는 조건식을 논리곱(AND)으로 조합합니다.
만약 조건식을 논리합(OR)으로 조합하고 싶다면 or() 메서드를 사용하면 됩니다.
예를 들어, 나이가 20 이상이거나 이메일이 "gmail.com"인 User를 조회하려면 다음과 같이 작성할 수 있습니다.
val builder = BooleanBuilder()
builder.and(QUser.user.age.goe(20).or(QUser.user.email.endsWith("gmail.com")))
위의 코드에서 or() 메서드를 사용하여 조건식을 논리합(OR)으로 조합하였습니다.
이메일이 "gmail.com"으로 끝나는 조건은 endsWith() 메서드를 사용하여 작성할 수 있습니다.
💡 QueryDSL - Auto Entity Serialization
코틀린 기반 Spring Boot에서 QueryDSL을 이용해 자동으로 엔티티 직렬화를 하려면 다음과 같은 설정이 필요합니다.
build.gradle 파일에 QueryDSL 관련 의존성 추가
plugins {
kotlin("kapt") version "1.4.10"
}
val querydslVersion ="5.0.0"
dependencies {
implementation "com.querydsl:querydsl-jpa"
implementation 'com.querydsl:querydsl-apt'
kapt 'com.querydsl:querydsl-apt'
// P
implementation("com.querydsl:querydsl-jpa:$querydslVersion")
kapt("com.querydsl:querydsl-apt:$querydslVersion:jpa")
}
< br>
src/main/kotlin 디렉터리에 Q 클래스 생성 설정
plugins {
id("com.ewerk.gradle.plugins.querydsl") version "1.0.10"
}
def querydslSrcDir = "src/main/generated"
querydsl {
jpa = true
querydslSourcesDir = querydslSrcDir
}
sourceSets {
main {
kotlin.srcDir(querydslSrcDir)
}
}
위의 설정을 추가하면, Gradle 빌드 시 ./gradlew compileKotlin
명령어를 실행하여 Q 클래스가 자동으로 생성됩니다.
Entity Serializer 작성
QueryDSL에서 엔티티를 직렬화하려면 Entity Serializer를 작성해야 합니다.
Entity Serializer는 com.querydsl.core.types.dsl.EntityPathBase
클래스를 상속받아 구현합니다.
import com.querydsl.core.types.dsl.EntityPathBase
import com.querydsl.core.types.dsl.NumberPath
import com.querydsl.core.types.dsl.StringPath
import com.querydsl.core.types.dsl.BooleanPath
import com.querydsl.core.types.dsl.DateTimePath
class QUser(name: String) : EntityPathBase<User>(User::class.java, name) {
val id: NumberPath<Long> = createNumber("id", Long::class.java)
val username: StringPath = createString("username")
val email: StringPath = createString("email")
val active: BooleanPath = createBoolean("active")
val createdAt: DateTimePath<LocalDateTime> = createDateTime("createdAt", LocalDateTime::class.java)
val updatedAt: DateTimePath<LocalDateTime> = createDateTime("updatedAt", LocalDateTime::class.java)
}
위의 예제에서는 QUser 클래스를 생성하여 User 엔티티의 필드를 정의합니다.
QueryDSL 설정 클래스에서 Entity Serializer 빈 등록
QueryDSL 설정 클래스에서 Entity Serializer 빈을 등록해야 합니다. 다음은 설정 클래스의 예시입니다.
@Configuration
@EnableJpaRepositories(basePackages = ["com.example.repository"])
@EnableTransactionManagement
class QuerydslConfig(
private val entityManager: EntityManager,
private val jpaProperties: JpaProperties
) {
...
@Bean
fun entitySerializer(): EntitySerializer {
val serializerConfig = SerializerConfig()
serializerConfig.addCustomProjection(User::class.java, QUser(qUser.toString()))
return EntitySerializer(serializerConfig)
}
...
}
위의 설정 클래스에서는 Entity Serializer 빈을 등록하고, SerializerConfig
객체를 생성하여 addCustomProjection()
메서드를 사용하여 직렬화를 위한 클래스 정보를 등록합니다.
DTO 클래스 생성
마지막으로, DTO 클래스를 생성합니다. DTO 클래스는 엔티티를 직렬화하는 데 사용됩니다.
data class UserDTO(
val id: Long,
val username: String,
val email: String,
val active: Boolean,
val createdAt: LocalDateTime,
val updatedAt: LocalDateTime
) {
companion object {
fun from(entity: User, serializer: EntitySerializer): UserDTO {
val qUser = QUser("user")
return serializer.serialize(qUser, entity) { id, username, email, active, createdAt, updatedAt ->
UserDTO(id, username, email, active, createdAt, updatedAt)
}
}
}
}
위의 예제에서는 UserDTO 클래스를 생성하여 User 엔티티의 필드를 정의합니다.
from()
메서드에서는 Entity Serializer를 사용하여 User 엔티티를 UserDTO 객체로 변환합니다.
이제, QueryDSL을 사용하여 자동으로 엔티티 직렬화를 수행할 수 있습니다.
예를 들어, 다음과 같이 UserRepository를 사용하여 UserDTO 객체를 가져올 수 있습니다.
@Service
class UserService(
private val userRepository: UserRepository,
private val entitySerializer: EntitySerializer
) {
fun getUser(id: Long): UserDTO? {
val user = userRepository.findById(id).orElse(null) ?: return null
return UserDTO.from(user, entitySerializer)
}
}
위의 코드에서는 UserRepository를 사용하여 User 엔티티를 가져온 다음,
Entity Serializer를 사용하여 UserDTO 객체로 변환합니다.
이렇게 하면 엔티티를 직렬화하는 과정을 자동화하고, DTO 클래스를 생성하지 않아도 됩니다.