build.gradle.kt
plugins {
id("org.springframework.boot") version "3.4.2"
id("io.spring.dependency-management") version "1.1.7"
kotlin("jvm") version "1.9.25"
kotlin("plugin.spring") version "1.9.25"
kotlin("plugin.jpa") version "1.9.25"
kotlin("kapt") version "1.9.25"
}
group = "com.contact"
version = "0.0.1-SNAPSHOT"
java {
toolchain {
languageVersion.set(JavaLanguageVersion.of(17))
}
}
repositories {
mavenCentral()
}
dependencies {
implementation("org.springframework.boot:spring-boot-starter-data-jpa")
implementation("org.springframework.boot:spring-boot-starter-web")
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
implementation("org.jetbrains.kotlin:kotlin-reflect")
implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
implementation("org.springframework.boot:spring-boot-starter-validation")
// QueryDSL 의존성 추가
implementation("com.querydsl:querydsl-jpa:5.1.0:jakarta")
kapt("com.querydsl:querydsl-apt:5.1.0:jakarta")
kapt("jakarta.annotation:jakarta.annotation-api")
kapt("jakarta.persistence:jakarta.persistence-api")
runtimeOnly("com.h2database:h2")
testImplementation("org.springframework.boot:spring-boot-starter-test")
}
// Querydsl 설정부 추가 - start
val generated = file("src/main/generated")
// querydsl QClass 파일 생성 위치를 지정
tasks.withType<JavaCompile> {
options.generatedSourceOutputDirectory.set(generated)
}
// kotlin source set 에 querydsl QClass 위치 추가
sourceSets {
main {
kotlin.srcDirs += generated
}
}
// gradle clean 시에 QClass 디렉토리 삭제
tasks.named("clean") {
doLast {
generated.deleteRecursively()
}
}
kapt {
generateStubs = true
}
설명
1. Query-dsl 종속성 추가
implementation("com.querydsl:querydsl-jpa:5.1.0:jakarta")
kapt("com.querydsl:querydsl-apt:5.1.0:jakarta")
kapt("jakarta.annotation:jakarta.annotation-api")
kapt("jakarta.persistence:jakarta.persistence-api")
com.querydsl:querydsl-jpa: 이는 QueryDSL JPA 모듈로, QueryDSL을 사용하여 유형이 안전한 쿼리를 작성하기 위한 JPA 관련 기능을 제공
com.querydsl:querydsl-apt: 이 주석 프로세서는 빌드 프로세스 중에 QueryDSL Q-클래스(즉, JPA 엔터티를 나타내는 클래스)를 생성
jakarta.annotation-api&jakarta.persistence-api : JPA 엔터티를 처리하는 데 사용되는 Jakarta 지속성과 관련된 필수
2. Q-class 생성
val generated = file("src/main/generated")
tasks.withType<JavaCompile> {
options.generatedSourceOutputDirectory.set(generated)
}
생성된 Q-클래스에 대한 디렉토리 설정 : 생성된 QueryDSL Q-클래스가 저장될 위치를 지정
3. 생성된 소스를 Kotlin 소스 세트에 추가
sourceSets {
main {
kotlin.srcDirs += generated
}
}
이렇게 하면 생성된 QueryDSL Q-클래스가 Kotlin 소스 세트에 추가되어 프로젝트의 나머지 부분에서 사용 generatedKotlin이 코드를 컴파일할 때 고려하는 디렉토리 목록에 디렉토리를 추가
4. Gradle에서 생성된 파일 정리 Clean:
tasks.named("clean") {
doLast {
generated.deleteRecursively()
}
}
gradle clean시 Qclass 디렉토리를 삭제
querydsl 패키지 생성후 QuerydslConfog.kt 생성
package com.contact.management.querydsl
import com.querydsl.jpa.impl.JPAQueryFactory
import jakarta.persistence.EntityManager
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
@Configuration
class QueryDslConfig(
val em: EntityManager
) {
@Bean
fun queryFactory(): JPAQueryFactory {
return JPAQueryFactory(em)
}
}
service 패키지에 QuerydslUserSerivce.kt 생성
package com.contact.management.service
import com.contact.management.dto.UserDto
import com.contact.management.entity.QUser
import com.contact.management.repository.UserRepository
import com.querydsl.jpa.impl.JPAQueryFactory
import org.springframework.data.domain.Page
import org.springframework.data.domain.PageImpl
import org.springframework.data.domain.Pageable
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional
@Service
@Transactional
class QuerydslUserService(
private val queryFactory: JPAQueryFactory
) {
@Transactional(readOnly = true)
fun getUsersWithPaging(pageable: Pageable): Page<UserDto> {
val qUser = QUser.user
// JPAQueryFactory를 사용하여 쿼리 작성
val query = queryFactory
.selectFrom(qUser)
.orderBy(qUser.id.asc()) // ID 기준으로 오름차순 정렬 (필요에 따라 변경 가능)
// 실제 데이터 조회
val users = query
.offset(pageable.offset) // 페이지의 시작 인덱스
.limit(pageable.pageSize.toLong()) // 페이지 크기
.fetch() // 결과 가져오기
// 총 개수를 구하는 쿼리 작성 (countQuery)
val countQuery = queryFactory
.select(qUser.count()) // 총 개수 계산
.from(qUser)
val total = countQuery.fetchOne() ?: 0L // fetchOne()으로 단일 값 가져오기
// Page 객체로 반환
val userDtos = users.map { UserDto(it.id, it.name, it.email) }
return PageImpl(userDtos, pageable, total)
}
}
UserController.kt
package com.contact.management.controller
import com.contact.management.dto.UserDto
import com.contact.management.service.QuerydslUserService
import com.contact.management.service.UserService
import jakarta.validation.Valid
import org.springframework.data.domain.Page
import org.springframework.data.domain.Pageable
import org.springframework.http.HttpHeaders
import org.springframework.http.HttpStatus
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.*
@RestController
@RequestMapping("/api/users")
class UserController(
private val userService: UserService
,private val querydslUserService: QuerydslUserService
) {
@GetMapping
fun getUsers(): ResponseEntity<List<UserDto>> {
val users = userService.getAllUsers()
return ResponseEntity.ok(users)
}
@GetMapping("/{id}")
fun getUser(@PathVariable id: Long): ResponseEntity<UserDto> {
return ResponseEntity.ok(userService.getUserById(id))
}
@PostMapping
fun createUser(@Valid @RequestBody userDto: UserDto): ResponseEntity<UserDto> {
val createdUser = userService.createdUser(userDto)
return ResponseEntity
.status(HttpStatus.CREATED) // 201 Created
.header(HttpHeaders.LOCATION, "/api/users/${createdUser.id}") // 생성된 리소스의 URI 반환
.body(createdUser)
}
@PutMapping("/{id}")
fun updateUser(@PathVariable id: Long,@Valid @RequestBody userDto: UserDto): ResponseEntity<UserDto> {
val updatedUser = userService.updateUser(id, userDto)
return ResponseEntity.ok(updatedUser)
}
@DeleteMapping("/{id}")
fun deleteUser(@PathVariable id: Long): ResponseEntity<Void> {
userService.deleteUser(id)
return ResponseEntity.noContent().build() // 204 No Content
}
@GetMapping("/paged")
fun getUsersWithPaging(pageable: Pageable): Page<UserDto> {
// 페이징된 사용자 목록 반환
return querydslUserService.getUsersWithPaging(pageable)
}
}
getUsersWithPaging 메소드 추가
데이터 미리 넣어보고 /api/users/paged 호출하면 아래형식으로 return됨
{
"content": [
{
"id": 1,
"name": "aa",
"email": "aa@domain.com"
},
{
"id": 2,
"name": "bb",
"email": "bb@domain.com"
},
{
"id": 3,
"name": "cc",
"email": "cc@domain.com"
},
{
"id": 4,
"name": "dd",
"email": "dd@domain.com"
},
{
"id": 5,
"name": "ee",
"email": "ee@domain.com"
},
{
"id": 6,
"name": "ff",
"email": "ff@domain.com"
},
{
"id": 7,
"name": "gg",
"email": "gg@domain.com"
},
{
"id": 8,
"name": "hh",
"email": "hh@domain.com"
},
{
"id": 9,
"name": "ii",
"email": "ii@domain.com"
},
{
"id": 10,
"name": "jj",
"email": "jj@domain.com"
},
{
"id": 11,
"name": "kk",
"email": "kk@domain.com"
},
{
"id": 12,
"name": "ll",
"email": "ll@domain.com"
},
{
"id": 13,
"name": "mm",
"email": "mm@domain.com"
},
{
"id": 14,
"name": "nn",
"email": "nn@domain.com"
},
{
"id": 15,
"name": "oo",
"email": "oo@domain.com"
},
{
"id": 16,
"name": "pp",
"email": "pp@domain.com"
},
{
"id": 17,
"name": "qq",
"email": "qq@domain.com"
},
{
"id": 18,
"name": "rr",
"email": "rr@domain.com"
},
{
"id": 19,
"name": "ss",
"email": "ss@domain.com"
},
{
"id": 20,
"name": "tt",
"email": "tt@domain.com"
}
],
"pageable": {
"pageNumber": 0,
"pageSize": 20,
"sort": {
"empty": true,
"sorted": false,
"unsorted": true
},
"offset": 0,
"paged": true,
"unpaged": false
},
"last": true,
"totalElements": 20,
"totalPages": 1,
"first": true,
"size": 20,
"number": 0,
"sort": {
"empty": true,
"sorted": false,
"unsorted": true
},
"numberOfElements": 20,
"empty": false
}