본문 바로가기

TIL

TIL) @SqlResultSetMapping, Kotlin-Fold, Reduce

1. @SqlResultSetMapping

jpa 를 사용하다 보면, 쿼리문의 결과를 Entity 객체의 형태 이외의 POJO 객체 형태로 받고 싶을 때가 있다.

예를 들면 아래의 경우가 있을 수 있겠다.

  • @Transient 필드를 쿼리 결과에 포함시키고 싶은 경우
  • Union 의 결과를 하나의 객체로 결과를 받고 싶은 경우
  • 위를 포함해서 native 쿼리를 통해 엔티티에 없는 새로운 컬럼을 추가한 어떤 객체로 결과를 반환받고 싶은 경우

 

 

 

이럴 때 두 단계를 통해 POJO 객체로 쿼리 결과를 매핑할 수 있다.

  1. 쿼리 결과 - 객체 매핑 정보 설정 : @SqlResultSetMapping
  2. 쿼리 작성 : @NamedNativeQuery, EntityManager.createNativeQuery()

 

1-1. @SqlResultSetMapping

  • name : 매퍼의 이름
  • classes : 매핑할 객체의 정보 (List 타입)
    • 아래 예를 보면 @ConstructorResult 라는 에너테이션을 사용한다. JPA 2.1 이전에는 이 에너테이션이 없었어서, Entity 객체만을 매핑할 수 있었다. 이 에너테이션을 사용하면 우리가 하려는 POJO 객체의 생성자를 사용해여 매핑할 수 있다.
    • @ColumnResult 으로 컬럼과 객체의 프로퍼티를 매핑한다. (name 에는 DB 입장에서 컬럼 이름을 적는다.)
      • columns속성의 순서와 사용하고자 하는 생성자의 파라미터 갯수 및 순서가 일치해야 한다
@SqlResultSetMapping(
    name = "[매퍼 이름(이 에너테이션의 이름)]",
    classes = (
        arrayOf(
            @ConstructorResult(
                targetClass = [Class 타입의 결과 매핑 타겟 객체],
                columns = (
                    arrayOf(
                        @ColumnResult(name = "[컬럼1]", type = Long::class),
                        @ColumnResult(name = "[컬럼2]", type = Int::class)
                    )
                    )
            )
        )
        )
)

@Entity
...

@SqlResultSetMapping 의 타겟을 보면, Type 에 붙일 수 있는데

나는 Entity 객체 위에 붙이는 편이다.

리포지토리 위에도 붙여도 될지도 ?

 

1-2. @NamedNativeQuery, EntityManager.createNativeQuery()

쿼리의 결과를 매핑하는 작업을 했으면, 쿼리를 작성하는 작업을 해보자.

위에 적은 것 처럼 두 방법으로 사용할 수 있다.

 

  • @NamedNativeQuery
    • name : @Repository 의 메서드 이름과 일치시켜야 한다. 메서드와 이 에너테이션의 맵핑 목적.
    • query : 쿼리. JPQL 이 아닌 네이티브 쿼리.
    • resultSetMapping : @SqlResultSetMapping 의 name 값과 일치시켜서 매핑한다.
@NamedNativeQuery(
    name = "[리포지토리 메서드 이름]", 
    query = "[네이티브 쿼리]", 
    resultSetMapping = "[매퍼의 이름]" 
)


@Repository
...

이 에너테이션은 리포지토리의 메서드와 매핑되므로, @Repository 인터페이스 상단에 적는다.

그러면 엔티티객체에 있는 매퍼와 떨어지게 되는 단점이 있긴 하다.

 

 

  • EntityManager.createNativeQuery()
    • 이 방법은 에너테이션이 아니라, @Repository 의 메서드 구현체에 작성하는 방법이다.
@Repository
class Repository {

    @PersistenceContext
    private lateinit var em: EntityManager

    fun mehtod(): List<타입> {
        ...
        
        return em.createNativeQuery("""
			[네이티브 쿼리]
        """.trimIndent(), "[매퍼]")
            .resultList as List<타입>
    }
}

 

https://stackoverflow.com/questions/13012584/jpa-how-to-convert-a-native-query-result-set-to-pojo-class-collection

 

JPA : How to convert a native query result set to POJO class collection

I am using JPA in my project. I came to a query in which I need to make join operation on five tables. So I created a native query which returns five fields. Now I want to convert the result obje...

stackoverflow.com

 

https://www.baeldung.com/jpa-sql-resultset-mapping

 

 

 

 

2. Fold, Reduce

둘 다 누산기이다. 두 메서드의 차이점은 초기값을 받냐 마냐인데, 알아보자.

 

  • fold
public inline fun <T, R> Iterable<T>.fold(initial: R, operation: (acc: R, T) -> R): R {
    var accumulator = initial
    for (element in this) accumulator = operation(accumulator, element)
    return accumulator
}

T : 순회할 Iterable 의 타입

R : 반환 타입

 

아래와 같이 쓸 수 있겠다.

fun List<Wood>.toBook(): List<Book> =
        this.fold(ArrayList(this.size)) { books, wood ->
            books.apply {
                add(Wood.makeBook(wood))
            }
        }

fold 안에 초기값으로 리스트를 만들었다.

이후 리스트에 나무로 책을 만들어서 추가한다.

fold 의 정의를 보면 알겠지만, operation 의 리턴 타입이 R 이어야 하고, add 는 리턴타입이 Boolean 이어서, apply 를 써주었다.

 

 

  • reduce
public inline fun <S, T : S> Iterable<T>.reduce(operation: (acc: S, T) -> S): S {
    val iterator = this.iterator()
    if (!iterator.hasNext()) throw UnsupportedOperationException("Empty collection can't be reduced.")
    var accumulator: S = iterator.next()
    while (iterator.hasNext()) {
        accumulator = operation(accumulator, iterator.next())
    }
    return accumulator
}

T : 순회할 Iterable 의 타입

S : 반환 타입

 

fold 와의 차이점은 인자의 initial 이 없다는 것이다.

 

listOf(1, 2, 3).reduce { sum, num -> sum + num }

=> 6