이번 포스팅은 Kotlin + JPA Native Query 사용법 2번째 포스팅으로 entityManager.createNativeQuery를 사용하는 방법에 대해 알아보겠다.
entityManager.createNativeQuery 사용법
이 방법은 Spring Data JPA를 사용하는 게 아니고 hibernate를 직접 사용하는 방법이다.
조회하는 방법은 @Entity, DTO class 2가지가 있다.
하나씩 알아보자.
1. @Entity class
아래 Address Entity를 조회하는 동적쿼리를 예로 들어 설명하곘다.
@Entity
class Address(
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
val id: Long = 0,
var name: String,
var phoneNum: String,
var zipCode: String,
var address1 : String,
var address2: String
)
@Entity class는 entityManager.createNativeQuery("query", "Entity Class")를 사용해야 한다.
맨 마지막에 as Address를 붙이는 이유는 타입이 Any?로 리턴되기 때문이다.
@Repository
class AddressNativeQueryRepository(
private val entityManager: EntityManager
) {
fun findByName(
name: String? = null,
): Address {
val query = """
SELECT * // Entity class 사용 시 *를 사용해야함
FROM address
WHERE name = :name
""".trimIndent()
return entityManager.createNativeQuery(query, Address::class.java)
.setParameter("name", name)
.resultList
.first() as Address
}
}
2. DTO class
dto class는 allOpen, no-arg 플러그인 적용 대상이 아니라 open, no-arg 생성자를 명시해줘야 한다.
open class NameOfAddress(
var name: String
) {
constructor() : this(name = "")
}
entity가 아닌 class를 사용하기 위해선 아래와 같이 복잡한 방법을 거쳐야 한다.
(setResultTransformer는 deprecated됐다. 최신 방법은 여기를 참고하자. 근데 아래 방법보다 더 불편해보인다..)
fun findByName(
name: String? = null,
): NameOfAddress {
val query = """
SELECT name
FROM address
WHERE name = :name
""".trimIndent()
return entityManager.createNativeQuery(query)
.setParameter("name", name)
.unwrap(NativeQuery::class.java)
.setResultTransformer(Transformers.aliasToBean(NameOfAddress::class.java))
.resultList
.first() as NameOfAddress
}
entityManager.createNativeQuery 장점
동적 쿼리 작성이 가능해진다.
예)
name 값이 있는 경우 name 조건을,
address1 값이 있는 경우 address1 조건을,
address2 값이 있는 경우 address2 조건을 추가하고 싶다면?
@Repository
class AddressNativeQueryRepository(
private val entityManager: EntityManager
) {
fun findByName(
name: String? = null,
address1: String? = null,
address2: String? = null
): NameOfAddress {
val query = createQuery(
name = name,
address1 = address1,
address2 = address2
)
return entityManager.createNativeQuery(query)
.unwrap(NativeQuery::class.java)
.setResultTransformer(Transformers.aliasToBean(NameOfAddress::class.java))
.resultList
.first() as NameOfAddress
}
private fun createQuery(
name: String?,
address1: String?,
address2: String?
) = """
SELECT name
FROM address
${
createWhere(
name = name,
address1 = address1,
address2 = address2
)
}
""".trimIndent()
private fun createWhere(
name: String?,
address1: String?,
address2: String?
) = listOfNotNull(
"name".eq(name),
"address1".eq(address1),
"address2".eq(address2)
).toWhere()
}
private fun String.eq(string: String?) = string?.let { "$this = '$string'" }
private fun List<String>.toWhere() = "WHERE ${this.joinToString(" and ")}"
entityManager.createNativeQuery 단점
단점이 너무 많다.
1. 형변환이 제대로 안 돼서 코드가 지저분하다.
자바는 잘 되던데..
...
return entityManager.createNativeQuery(query)
.unwrap(NativeQuery::class.java)
.setResultTransformer(Transformers.aliasToBean(NameOfAddress::class.java))
.resultList
.first() as NameOfAddress << 으으
...
2. natvie query를 사용하는 경우 entity class보다 dto class를 더 많이 사용하는데 open, no-arg 생성자를 선언해줘야 하는 게 너무 별로다.
생각해보니 이건 별도 어노테이션 만들어서 no-arg, kotlin-spring에 옵션으로 추가하면 해결 가능하다라고 생각했지만..
no-arg가 안 먹힌다. no-arg 생성자가 없다고 NoSuchMethodException이 발생한다.
open class NameOfAddress(
var name: String
) {
constructor() : this(name = "")
}
3. 사용법이 너무 어렵다.
4. DB에 따라 작동 방법이 다르다?
위 예제들은 mysql로 테스트를 했다.
사실 처음엔 h2로 테스트했는데 자꾸 아래와 같이 속성명을 대문자로 찾아서 에러가 발생했다.
Could not resolve PropertyAccess for NAME on class com.effortyguy.nativequery.dto.NameOfAddress; nested exception is org.hibernate.PropertyNotFoundException: Could not resolve PropertyAccess for NAME on class com.effortyguy.nativequery.dto.NameOfAddress
아래와 같이 name -> NAME으로 변경시켜주니 정상적으로 되긴했다.
open class NameOfAddress(
var NAME: String
) {
constructor() : this(NAME = "")
}
무슨 옵션으로 해결할 수 있는 건지 잘 모르겠지만 불편했다.
5. 컬럼이 날짜 형식인 경우 TimeStamp 타입으로 받아와야 한다.
간단한 건 써도 될 거 같은데 그냥 안 쓸란다.. 코틀린하고 너무 안 맞는다.
References
'Spring' 카테고리의 다른 글
[Spring] Spring Boot + Kotlin + MyBatis ResultType, ResultClass에 Enum 사용법 (0) | 2023.07.02 |
---|---|
[Spring] Spring Boot + MySQL 설정 방법 (0) | 2023.07.02 |
[Spring] Spring Boot + Kotlin + JPA Native Query 사용법 (1) - @Query (0) | 2023.06.25 |
[Spring] Spring Boot + Kotlin + Spring Data JPA 설정 주의 사항 (0) | 2023.06.25 |
[Spring] Spring Boot 테스트에 사용할 h2 DB 설정 방법 (0) | 2023.06.24 |
댓글