본문 바로가기

카테고리 없음

24.03.05 페이징, 순환오류(StackOverflowError) 해결

오늘은 페이징을 시도해 봤는데, 처음해보는거러 시행착오가 오래 걸렸다.

강의자료를 이상한걸 찾아봐서 쓸모없는 이넘 클래스를 만드는 등 삽질을 하다가 제대로 강의자료를 보고 드디어 만들어낼수 있었다.

 

우선 필요한게

abstract class QueryDslSupport {
    @PersistenceContext
    protected lateinit var entityManager: EntityManager

    protected val queryFactory: JPAQueryFactory
        get() {
            return JPAQueryFactory(entityManager)
        }
}

이건 팀장님이 만들어놓은 것이다. 그 다음엔

interface CustomProductRepository {
    fun findByPageable(pageable: Pageable): Page<Product>
}

 

먼저 이걸 생성해준다. 이건 service 같은 것이다. 즉, serviceimpl도 있다는 뜻이다.

 

package com.teamsparta.moamoa.product.repository

import com.teamsparta.moamoa.infra.QueryDslSupport
import com.teamsparta.moamoa.product.model.Product
import com.teamsparta.moamoa.product.model.QProduct
import org.springframework.data.domain.Page
import org.springframework.data.domain.PageImpl
import org.springframework.data.domain.Pageable
import org.springframework.stereotype.Repository

@Repository
class CustomProductRepositoryImpl : QueryDslSupport(), CustomProductRepository {
    private val product = QProduct.product

    override fun findByPageable(pageable: Pageable): Page<Product> {
        val totalCount = queryFactory.select(product.count()).from(product).fetchOne() ?: 0L

        val query =
            queryFactory.selectFrom(product)
                .offset(pageable.offset)
                .limit(pageable.pageSize.toLong())

        if (pageable.sort.isSorted) {
            pageable.sort.forEach { sort ->
                val direction = sort.direction
                val property = sort.property

                when (property) {
                    "id" -> if (direction.isAscending) query.orderBy(product.id.asc()) else query.orderBy(product.id.desc())
                }
            }
        } else {
            query.orderBy(product.id.asc())
        }

        val contents = query.fetch()

        return PageImpl(contents, pageable, totalCount)
    }
}

 

이녀석은 먼저 QueryDslSupport을 받아오고

 

이걸 참조해서 CustomProductRepository의 findByPageable을 받아온 다음   

 

totalCount가 상품의 수를 조회하고, oL부분은 Product가 널일걸 대비하기 위해 존재한다고 한다.

 Pageable 객체에 설정된 페이지 번호와 페이지 크기를 기반으로, 해당 범위의 상품 데이터를 조회하는 쿼리를 생성하고 sort로 정렬을 한다.

 

@GetMapping("/pages")
fun getPaginatedProductList(
    @PageableDefault(size = 15, sort = ["id"]) pageable: Pageable,
): ResponseEntity<Page<Product>> {
    val products = productService.getPaginatedProductList(pageable)
    return ResponseEntity.status(HttpStatus.OK).body(products)// 페이징 아직 오류 해결못함
}

이걸로 GET/products/pages 를 눌러서 페이징을 할수 있고, 한 페이지에 15개의 상품을 보여주도록 설정하고, 상품들은 'id' 필드를 기준으로 정렬되도록 설정하였다.

 

 

 

그런데 스웨거에서 GET/products/pages을 하면 로딩이 끊나지 않는데 에러 라고 표시도 안되서 뭐가 문제인지 인식이 안되니 어려웠는데,  에러는 아닌 WARN 으로,

2024-03-05T19:24:31.985+09:00  WARN 9832 --- [nio-8080-exec-9] .w.s.m.s.DefaultHandlerExceptionResolver : Ignoring exception, response committed already: org.springframework.http.converter.HttpMessageNotWritableException: Could not write JSON: Infinite recursion (StackOverflowError)

 

이렇게 StackOverflowError가 나와있었다. 그래서 해결법을 찾았는데.

package com.teamsparta.moamoa.product.model

import com.fasterxml.jackson.annotation.JsonManagedReference
import com.teamsparta.moamoa.infra.BaseTimeEntity
import jakarta.persistence.*
import java.time.LocalDateTime

@Entity
@Table(name = "product")
data class Product(
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    val id: Long? = null,
    @Column(name = "sellerId", nullable = false)
    val sellerId: Long,
    @Column(name = "price", nullable = false)
    var price: Int,
    @Column(name = "title", nullable = false)
    var title: String,
    @Column(name = "content", nullable = false)
    var content: String,
    @Column(name = "purchaseAble", nullable = false)
    var purchaseAble: Boolean,
    @Column(name = "ratingAverage", nullable = true)
    val ratingAverage: Double,
    @Column(name = "imageUrl", nullable = true)
    var imageUrl: String,
    @Column(name = "productDiscount", nullable = true)
    val productDiscount: Double,
    @Column(name = "likes", nullable = true)
    val likes: Int,
    @Column(name = "deleted_at")
    var deletedAt: LocalDateTime? = null,
    @Column(name = "userLimit")
    val userLimit: Int,
    @Column(name = "discount")
    val discount: Double,
//    @OneToOne(cascade = [CascadeType.ALL], fetch = FetchType.LAZY)
//    @JoinColumn(name = "product_stock_id")
//    var productStock: ProductStock? = null,
    @OneToOne(mappedBy = "product", cascade = [CascadeType.ALL], fetch = FetchType.LAZY)
    @JsonManagedReference  바로 이부분을 추가
    var productStock: ProductStock? = null,
    // 재고처리?
    @Column(name = "is_sold_out", nullable = false)
    var isSoldOut: Boolean = false,
) : BaseTimeEntity()

 

package com.teamsparta.moamoa.product.model

import com.fasterxml.jackson.annotation.JsonBackReference
import com.teamsparta.moamoa.infra.BaseTimeEntity
import jakarta.persistence.*

@Entity
@Table(name = "product_stock")
data class ProductStock(
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    val id: Long? = null,
//    @OneToOne(mappedBy = "productStock", cascade = [CascadeType.ALL], fetch = FetchType.LAZY)
//    val product: Product,
    @OneToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "product_id")
    @JsonBackReference 여기도 이걸 추가
    val product: Product,
    @Column(name = "stock")
    var stock: Int,
    @Column(name = "product_name")
    val productName: String,
) : BaseTimeEntity()

 이렇게 했더니 무한 로딩이 사라졌다. 즉 @JsonManagedReference,  @JsonBackReference을 추가하면 순환참조가 해결된다.

출처:https://m.blog.naver.com/writer0713/221587351970

 

[JPA] 순환참조와 해결 방법 @JsonIgnore, @JsonManagedReference, @JsonBackReference

실무에서 개발을 진행하다가 희안한 현상을 맞닥뜨렸다. JPA를 사용하여 개발을 진행하면서 여러 Entit...

blog.naver.com

이렇게 페이징을 완료했다!! 그 외에도 서비스임플에 트랜잭션을 넣고 id를 id, productid로 나뉜걸 productid로 통일 했다.