Notice
Recent Posts
Recent Comments
Link
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
Tags
- kotlin
- AI
- APM
- MySQL
- AWS EKS
- Linux
- mysql 튜닝
- aws
- IntelliJ
- minikube
- Java
- 공부
- CloudWatch
- 정보처리기사실기 기출문제
- 정보처리기사 실기 기출문제
- 기록으로 실력을 쌓자
- CKA 기출문제
- 티스토리챌린지
- kotlin spring
- kotlin coroutine
- CKA
- PETERICA
- 코틀린 코루틴의 정석
- Kubernetes
- 정보처리기사 실기
- Pinpoint
- Spring
- 오블완
- kotlin querydsl
- Elasticsearch
Archives
- Today
- Total
피터의 개발이야기
[Kotlin] Spring Boot와 Kotlin으로 QueryDSL 페이징 처리하기 본문
반응형
ㅁ 들어가며
지난 글, [Spring] Kotlin으로 JPA Querydsl 세팅에서 Spring Boot와 Kotlin을 사용하여 QueryDSL을 적용한 프로젝트를 구성하였다. QueryDSL을 사용하면 동적 쿼리를 쉽게 작성할 수 있으며, Spring Data의 페이징 기능을 활용하면 대량의 데이터를 효율적으로 처리할 수 있다. 이번 글에서는 Spring Boot와 Kotlin을 사용하여 QueryDSL을 적용한 프로젝트에서 페이징 처리를 구현하는 방법을 정리하였다.
ㅁ Repository 인터페이스 생성
import org.springframework.data.domain.*
import org.springframework.data.jpa.repository.JpaRepository
interface ProductRepository : JpaRepository<Product, Long>, ProductRepositoryCustom
interface ProductRepositoryCustom {
fun findProductsByName(name: String, pageable: Pageable): Page<Product>
}
ㅇ ProductRepository.kt에 Product를 name으로 검색하는 인터페이스 생성
ㅇ ProductRepositoryCustom은 JPA 기본 기능에 대한 override를 피하기 위해 만들었다.
import com.querydsl.jpa.impl.JPAQueryFactory
import org.springframework.data.domain.*
import org.springframework.stereotype.Repository
@Repository
class ProductRepositoryImpl(
private val jpaQueryFactory: JPAQueryFactory
): ProductRepositoryCustom {
override fun findProductsByName(name: String, pageable: Pageable): Page<Product> {
val qProduct = QProduct.product
val query = jpaQueryFactory.selectFrom(qProduct)
.where(qProduct.name.containsIgnoreCase(name))
.offset(pageable.offset)
.limit(pageable.pageSize.toLong())
val products = query.fetch()
val countQuery = jpaQueryFactory.selectFrom(qProduct)
.where(qProduct.name.containsIgnoreCase(name))
return PageImpl(products, pageable, countQuery.fetchCount())
}
}
ㅇ ProductRepository.kt에 Product를 name으로 검색하는 기능을 구현하였다.
ㅁ 서비스 클래스 구현
import com.peterica.kotlinquerydsl.entity.Product
import com.peterica.kotlinquerydsl.entity.ProductRepository
import org.springframework.data.domain.Page
import org.springframework.data.domain.Pageable
import org.springframework.stereotype.Service
@Service
class ProductService(
private val productRepository: ProductRepository
) {
fun saveProduct(product: Product): Product =
productRepository.save(product)
fun findProductById(id: Long): Product? =
productRepository.findById(id).orElse(null)
fun searchProductsByName(name: String, pageable: Pageable): Page<Product> =
productRepository.findProductsByName(name, pageable)
}
ㅇ ProductService.kt 구현.
ㅁ 컨트롤러 구현
package com.peterica.kotlinquerydsl.controller
import com.peterica.kotlinquerydsl.entity.Product
import com.peterica.kotlinquerydsl.service.ProductService
import org.springframework.data.domain.Pageable
import org.springframework.stereotype.Controller
import org.springframework.web.bind.annotation.*
@Controller
@RequestMapping("/products")
class ProductController(
private val productService: ProductService
) {
@PostMapping
fun createProduct(@RequestBody product: Product): Product {
return productService.saveProduct(product)
}
@GetMapping("/{id}")
fun getProduct(@PathVariable id: Long): Product? {
return productService.findProductById(id)
}
@GetMapping("/search")
fun searchProductsByName(@RequestParam name: String, pageable: Pageable): Page<Product> {
return productService.searchProductsByName(name, pageable)
}
}
ㅁ Circular view path 에러 트러블 슈팅
jakarta.servlet.ServletException: Circular view path [products]: would dispatch back to the current handler URL [/products] again. Check your ViewResolver setup! (Hint: This may be the result of an unspecified view, due to default view name generation.)
ㅇ Circular view path 에러가 발생하여 해결방법을 [Kotlin] Spring MVC, Circular view path 에러에 정리하였다.
ㅁ 테스트 코드 작성
import com.fasterxml.jackson.databind.ObjectMapper
import com.peterica.kotlinquerydsl.controller.ProductController
import com.peterica.kotlinquerydsl.entity.Product
import com.peterica.kotlinquerydsl.service.ProductService
import org.junit.jupiter.api.Test
import org.mockito.Mockito.`when`
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest
import org.springframework.boot.test.mock.mockito.MockBean
import org.springframework.data.domain.PageImpl
import org.springframework.data.domain.PageRequest
import org.springframework.http.MediaType
import org.springframework.test.web.servlet.MockMvc
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.*
import java.time.LocalDateTime
@WebMvcTest(ProductController::class)
class ProductControllerTest {
@Autowired
private lateinit var mockMvc: MockMvc
@MockBean
private lateinit var productService: ProductService
@Autowired
private lateinit var objectMapper: ObjectMapper
@Test
fun `create product`() {
val product = Product(name = "Test Product", quantity = 2, registeredAt = LocalDateTime.now())
val savedProduct = product.copy(id = 1)
`when`(productService.saveProduct(product)).thenReturn(savedProduct)
mockMvc.perform(post("/products")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(product)))
.andExpect(status().isOk)
.andExpect(jsonPath("$.id").value(1))
.andExpect(jsonPath("$.name").value("Test Product"))
.andExpect(jsonPath("$.quantity").value(2))
}
@Test
fun `get product by id`() {
val product = Product(id = 1, name = "Test Product", quantity = 2, registeredAt = LocalDateTime.now())
`when`(productService.findProductById(1)).thenReturn(product)
mockMvc.perform(get("/products/1"))
.andExpect(status().isOk)
.andExpect(jsonPath("$.id").value(1))
.andExpect(jsonPath("$.name").value("Test Product"))
.andExpect(jsonPath("$.quantity").value(2))
}
@Test
fun `return 404 when product not found`() {
`when`(productService.findProductById(1)).thenReturn(null)
mockMvc.perform(get("/products/1"))
.andExpect(status().isNotFound)
}
@Test
fun `search products`() {
val products = listOf(
Product(id = 1, name = "Test Product 1", quantity = 1, registeredAt = LocalDateTime.now()),
Product(id = 2, name = "Test Product 2", quantity = 2, registeredAt = LocalDateTime.now())
)
val pageable = PageRequest.of(0, 10)
val page = PageImpl(products, pageable, 2)
`when`(productService.searchProductsByName("Test", pageable)).thenReturn(page)
mockMvc.perform(get("/products/search")
.param("name", "Test")
.param("page", "0")
.param("size", "10"))
.andExpect(status().isOk)
.andExpect(jsonPath("$.content.length()").value(2))
.andExpect(jsonPath("$.content[0].id").value(1))
.andExpect(jsonPath("$.content[0].name").value("Test Product 1"))
.andExpect(jsonPath("$.content[1].id").value(2))
.andExpect(jsonPath("$.content[1].name").value("Test Product 2"))
.andExpect(jsonPath("$.totalElements").value(2))
.andExpect(jsonPath("$.totalPages").value(1))
}
}
ㅁ PostMan 테스트
ㅇ createProduct
curl --location 'localhost:8080/products' \
--header 'Content-Type: application/json' \
--data '{
"name": "Peterica",
"quantity": 1,
"registeredAt": "2024-08-04T14:22:33"
}'
ㅇ getProduct
curl --location 'localhost:8080/products/1'
{
"name": "Peterica",
"quantity": 1,
"registeredAt": "2024-08-04T14:22:33",
"id": 1
}
curl --location 'localhost:8080/products/1'{ "name": "Peterica", "quantity": 1, "registeredAt": "2024-08-04T14:22:33", "id": 1}
ㅇ searchProductsByName
curl --location 'localhost:8080/products/search?name=peterica&page=0&size=10'
{
"content": [
{
"name": "Peterica",
"quantity": 1,
"registeredAt": "2024-08-04T14:22:33",
"id": 1
}
],
"pageable": {
"pageNumber": 0,
"pageSize": 10,
"sort": {
"sorted": false,
"unsorted": true,
"empty": true
},
"offset": 0,
"paged": true,
"unpaged": false
},
"totalPages": 1,
"totalElements": 1,
"last": true,
"numberOfElements": 1,
"size": 10,
"number": 0,
"first": true,
"sort": {
"sorted": false,
"unsorted": true,
"empty": true
},
"empty": false
}
ㅁ 함께 보면 좋은 사이트
ㅇ kotlin + spring data jpa로 pageable을 사용해보자
ㅇ Spring Boot Rest API with Kotlin - Pagination, Search & Sorting using MongoDB
반응형
'Programming > Kotlin' 카테고리의 다른 글
[Kotlin] Spring Boot Kotlin 프로젝트에 ktlint 설정하기 (0) | 2024.08.05 |
---|---|
[Kotlin] Spring MVC, Circular view path 에러 (0) | 2024.08.04 |
[Kotlin] Spring Validation 이용한 입력 데이터 유효성 검증 (0) | 2024.07.30 |
[kotlin] Constructor threw exception; nested exception is java.lang.NullPointerException; 에러 해결방법 (0) | 2024.07.29 |
[Kotlin]JPA의 더티 체킹 (Dirty Checking) (0) | 2024.07.28 |
Comments