본문으로 바로가기

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
}