1. @SqlResultSetMapping
jpa 를 사용하다 보면, 쿼리문의 결과를 Entity 객체의 형태 이외의 POJO 객체 형태로 받고 싶을 때가 있다.
예를 들면 아래의 경우가 있을 수 있겠다.
- @Transient 필드를 쿼리 결과에 포함시키고 싶은 경우
- Union 의 결과를 하나의 객체로 결과를 받고 싶은 경우
- 위를 포함해서 native 쿼리를 통해 엔티티에 없는 새로운 컬럼을 추가한 어떤 객체로 결과를 반환받고 싶은 경우
이럴 때 두 단계를 통해 POJO 객체로 쿼리 결과를 매핑할 수 있다.
- 쿼리 결과 - 객체 매핑 정보 설정 : @SqlResultSetMapping
- 쿼리 작성 : @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://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
'TIL' 카테고리의 다른 글
TIL) @Transactional Propagation, 스프링에서 초기화 코드를 넣기 (0) | 2022.02.17 |
---|---|
TIL) TINYINT/BIT, Spring Content-type, 코드를 DB 에서 읽어와서 처리할 때 resource (0) | 2022.02.15 |
TIL) cherry-pick 외 git 각종 취소 명령어들, ORIG_HEAD (0) | 2022.02.03 |
TIL) 쉘 스크립트, linux screen 명령어 (0) | 2022.01.31 |
TIL) pull or Merge 취소, SFTP 서버제공 사이트, CONTENT_DISPOSITION, @Async (0) | 2021.12.29 |