본문 바로가기
Spring

[Spring] Spring Boot + Kotlin + JPA Native Query 사용법 (2) - entityManager.createNativeQuery

by 노력남자 2023. 6. 25.
반응형

이번 포스팅은 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

 

https://docs.jboss.org/hibernate/orm/5.6/userguide/html_single/Hibernate_User_Guide.html#sql-dto-query

 

반응형

댓글