2024. 8. 2. 00:00ㆍ스프링 (Spring)/스프링 데이터 (Spring Data)
Converter
말 그대로 자료형을 적재할 때 또는 로드할 때 바꿔서 가져오는 것입니다.
다만 그렇다고 숫자인데 굳이 문자열로 바꿔서 만들 필요는 없습니다.
즉 기본형에 대해서는 그닥 쓸 필요가 없죠.
하지만 때로는 유용한 게, 바로 List, Map 입니다.
예를 들어보죠. 여기 Tomato 엔티티가 있고 컬럼 중에 tags가 있습니다.
@Entity
@Table(name = "tomatoes")
class Tomato(
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
val srl: Long = Long.MIN_VALUE,
val tags: MutableList<String> = mutableListOf()
)
안타깝지만, JPA에서는 Collection을 지원하지 않습니다.
하지만 우리는 일전에 엔티티 관계(entity relationship)에 대해서 배웠었죠.
TomatoTag 엔티티를 만들어서 @OneToMany 관계를 만들어주면 됩니다.
하지만 알겠지만, 이는 많은 경우에서 비효율적입니다.
만일 tags의 대부분이 비어있거나, 몇 개 안될꺼라면 굳이 join 연산을 실행할 필요는 없기 때문입니다.
이런 경우에는 JPA가 지원하는 무언가로 바꿔서 저장하고, 로드할 때는 다시 컬렉션으로 만들 필요가 있습니다.
이럴 때 Converter를 사용합니다.
만드는 방법은 간단한데, 먼저 AttributeConverter를 구현해줍니다.
import jakarta.persistence.AttributeConverter
class TomatoTagAttributeConverterImpl: AttributeConverter<MutableList<String>, String> {
companion object {
private const val SEPARATOR = ","
}
override fun convertToDatabaseColumn(tags: MutableList<String>): String =
tags.joinToString(SEPARATOR)
override fun convertToEntityAttribute(tags: String): MutableList<String> =
tags.split(SEPARATOR).toMutableList()
}
필자의 경우에는 적재할 때는 문자열로, 로드할 때는 리스트로 취급했습니다.
재밌는 점은 MutableList<String>이 아닌 List<String>으로 동작하기도 합니다.
어쨌든 Converter를 만들어줬다면, 다음과 같이 tags 위에 @Convert 어노테이션을 만들어줍니다.
@Entity
@Table(name = "tomatoes")
class Tomato(
...
@Convert(converter = TomatoTagAttributeConverterImpl::class)
val tags: MutableList<String> = mutableListOf()
)
그럼 일단 오류는 없어졌습니다.
실제 저장하고, 읽어보면 정말로 MutableList<String> 처럼 사용합니다.
val tomato = Tomato(tags = mutableListOf("hello", "world"))
entityManager.persist(tomato)
entityManager.flush()
val savedTomato: Tomato = entityManager.find(Tomato::class.java, srl)
println(savedTomato.tags)
[hello, world]
만일 tags를 변경할 경우 update 쿼리도 실행됩니다.
val tomato = Tomato(tags = mutableListOf("hello", "world"))
entityManager.persist(tomato)
entityManager.flush()
val savedTomato: Tomato = entityManager.find(Tomato::class.java, tomato.srl)
savedTomato.tags.add("welcome")
entityManager.flush()
val updatedTomato: Tomato = entityManager.find(Tomato::class.java, tomato.srl)
println(updatedTomato.tags)
...
[Hibernate]
update
tomatoes
set
tags=?
where
srl=?
[hello, world, welcome]
하지만 실제로 테이블을 보면 콤마로 구분된 문자열로 저장되어 있죠.
여타 캐스팅과 마찬가지로 만일 타입 변환이 실패하면 롤백 대상이됩니다.
어느 정도 위험성이 있으니, 스스로 검증을 철저히하고 오류를 대비하는 것을 권장합니다.
마지막으로 하나 더 주의할 점은, 데이터를 한계까지 적재하기 위해 존재하는 건 아니라는 것입니다.
예를 들어 tags에 1000개를 넣을 수 있다고 하면 엔티티 관계, 즉 join을 이용하는 것이 더 나을 수 있습니다.
그렇지 않으면 tomatoes 조회 자체가 너무 느려지거나 시스템 자원이 부족해지는 결과를 초래할 수 있습니다.
'스프링 (Spring) > 스프링 데이터 (Spring Data)' 카테고리의 다른 글
JPA(Jakarta Persistence API) - Entity Listener (0) | 2024.07.31 |
---|---|
JPA(Jakarta Persistence API) - Entity Operation (1) | 2024.06.06 |
JPA(Jakarta Persistence API) - Entity (0) | 2024.05.22 |
JPA(Jakarta Persistence API) - Overview (0) | 2024.05.19 |
SpringBootTest에 HSQL 데이터베이스 적용기 (0) | 2024.05.03 |