본문 바로가기
📗 JPA

JPA 집계함수 sum 은 long 을 반환한다.

by GroovyArea 2023. 7. 26.

최근 대량의 데이터들을 집계하여 조회하는 API 프로젝트를 진행 중에,

Spring Data JPA 와 Querydsl 을 이용하여 쿼리를 작성하고 있었다.

 

오늘, sum 집계 함수 때문에, java object 로 매핑이 안되는 문제가 있었는데,

그 이유와 해결과정을 설명해보겠다.

 

기존 쿼리

@Query(
        """
            select
            new service.dto.PaymentTotalSumDTO(
                sum(sub.totalAmount) as totalAmount,
                sum(sub.discount) as discount,
                sum(sub.usedPoint) as usedPoint
                )
            from
            (select
                tpd.totalAmount as totalAmount,
                tpd.discount as discount,
                tpd.usedPoint as usedPoint,
            from TransactionPaymentDetail tpd
            where tpd.sid = :sid
            group by
            tpd.pid,
            tpd.totalAmount,
            tpd.discount,
            tpd.usedPoint) sub
        """
    )
    fun findTotalSumBySid(
        sid: String
    ): PaymentTotalSumDTO?
    
    data class PaymentTotalSumDTO(
    val totalAmount: BigDecimal = BigDecimal.ZERO,
    val discount: BigDecimal = BigDecimal.ZERO,
    val usedPoint: BigDecimal = BigDecimal.ZERO,
    )

JPQL 로 groupby 하여 얻은 서브쿼리에서 각 컬럼별로 sum 을 때려 DTO 로 조회하는 함수이다.

해당 query 는 이러한 에러를 발생시키며, 실패한다.

Caused by: java.lang.IllegalArgumentException: org.hibernate.QueryException: Function argument [SqmBasicValuedSimplePath(<<derived>>(sub).totalAmount)] of type [org.hibernate.query.derived.AnonymousTupleSqmPathSource@59282e4a] at specified position [1] in call arguments was not typed as an allowable function return type
at org.hibernate.internal.ExceptionConverterImpl.convert(ExceptionConverterImpl.java:141)
at org.hibernate.query.spi.AbstractSelectionQuery.list(AbstractSelectionQuery.java:374)
at org.hibernate.query.sq m.internal.QuerySqmImpl.list(QuerySqmImpl.java:1073) 
at org.hibernate.query.spi.AbstractSelectionQuery.getSingleResult(AbstractSelectionQuery.java:457)
at org.hibernate.query.sqm.internal.QuerySqmImpl.getSingleResult(QuerySqmImpl.java:1103)

 

무엇이 문제였을까?

- 하이버네이트의 집계함수 sum 은 long 타입을 반환한다.

BigDecimal 타입과 Long 타입이 일치하지 않아, object 로 매핑이 되지 않는 것이다.

 

어떻게 해야 할까?

@Query(
        """
            select
            new service.dto.PaymentTotalSumDTO(
                sum(cast(sub.totalAmount as bigdecimal)) as ta,
				sum(cast(sub.discount as bigdecimal)) as discount,
				sum(cast(sub.usedPoint as bigdecimal)) as usedPoint
                )
            from
            (select
                tpd.totalAmount as totalAmount,
                tpd.discount as discount,
                tpd.usedPoint as usedPoint,
            from TransactionPaymentDetail tpd
            where tpd.sid = :sid
            group by
            tpd.pid,
            tpd.totalAmount,
            tpd.discount,
            tpd.usedPoint) sub
        """
    )
    fun findTotalSumBySid(
        sid: String
    ): PaymentTotalSumDTO?

- cast 함수를 이용하여 bigdecimal 타입으로 캐스팅 해주었다.

서브 쿼리인 sub 는 실행시점에 entity 에 명시된 타입대로 bigdecimal 타입을 반환하지만, 

sub 를 이용하여 select 하는 구간에서 하이버네이트는 type 적용이 되지 않아버린다.

DB 단에서 한번에 쿼리하는 것이 아닌, 애플리케이션 메모리에서 long 을 반환하는 sum 함수가 실행되기 때문이다.

 

그럼 정상적으로 쿼리가 실행되는 것을 알 수 있다.

 

According to the JPA spec 4.5.8

The Java type that is contained in the result of a query using an aggregate function is as follows:

• COUNT returns Long.

• MAX, MIN return the type of the state field to which they are applied.

• AVG returns Double.

• SUM returns Long when applied to state fields of integral types (other than BigInteger); Double when applied to state fields of floating point types; BigInteger when applied to state fields of type BigInteger; and BigDecimal when applied to state fields of type BigDecimal.

 

참조 :

https://stackoverflow.com/questions/61954687/jpa-sum-returns-long

반응형