이번 포스팅에선 AttributeConveter, @Converter, @Convert에 대해 알아보려고 한다.
AttributeConverter란?
JPA 엔티티의 프로퍼티 값을 저장할 때 원하는 값으로, 읽어올 때 원하는 타입이나 값으로 변환하고자 할 때 사용하는 인터페이스다.
사용처를 간단하게 예를 들면
1. enum 타입 프로퍼티를 DB에 저장
2. 암호화된 DB 값을 조회할 때 복호화
자세한 예는 아래에서 설명하겠다.
AttributeConveter는 아래와 같이 정의되어 있다.
public interface AttributeConverter<X,Y> {
// 컨버터를 적용할 프로퍼티의 값이 DB에 저장될 때 어떤 값으로 저장되길 원하는지
public Y convertToDatabaseColumn (X attribute);
// DB에서 읽어온 값이 어떤 값, 어떤 타입으로 변환됐으면 좋겠는지
public X convertToEntityAttribute (Y dbData);
}
AttributeConverter 사용법
AES 암복호화 해주는 AESConverter를 예로 들어 사용법을 설명하겠다.
@Converter
private class AESConverter() : AttributeConverter<String, String> {
override fun convertToDatabaseColumn(attribute: String?): String {
// 암호화
}
override fun convertToEntityAttribute(dbData: String?): String {
// 복호화
}
}
1. AttributeConverter를 구현하는 클래스를 만들고 필수 함수들을 구현한다.
- convertToDatabaseColumn: 암호화 로직 구현
- convertToEntityAttribute: 복호화 로직 구현
2. JPA가 이 클래스가 컨버터인지를 알 수 있게 @Converter 어노테이션를 붙여준다.
3. 1, 2에서 만든 컨버터를 엔티티의 프로퍼티에 적용해야 한다. 방법이 2가지가 있다.
- 특정 프로퍼티에만 적용하기
컨버터 적용을 원하는 프로퍼티 위에 @Convert 어노테이션을 붙이면 된다.
@Entity
class User(
...
@Convert(converter = AESConverter::class) << 여기
@Column(name = "enc_name")
val name: String,
)
- 글로벌 설정
컨버터에 붙어있는 @Converter 어노테이션에 autoApply 프로퍼티를 true로 주면 된다.
AttributeConverter<X, Y>의 X 타입을 갖는 프로퍼티에 모두 적용되니 조심해서 써야한다.
그래서 나는 EncryptString이라는 클래스를 정의해서 해당 클래스만 적용되도록 컨버터를 수정했다.
@Entity
class User(
...
@Column(name = "enc_name")
val name: EncryptString,
)
@Converter(autoApply = true)
private class AESConverter : AttributeConverter<EncryptString, String> {
override fun convertToDatabaseColumn(attribute: String?): String? {
// 암호화
}
override fun convertToEntityAttribute(dbData: String?): EncryptString? {
// 복호화
}
}
*직접 설정과 글로벌 설정이 둘 다 선언되어 있다면, 당연하게도 직접 설정이 우선 순위가 높다. (우선 순위: 직접 설정 > 글로벌 설정)
AttributeConverter 사용 예제
1. 우리는 이미 AttributeConverter를 사용하고 있었다. autoApply = true여서 몰랐을 뿐..
JPA에서 기본으로 제공해주는 Converter들이다. 이중 날짜와 관련된 것들은 autoApply가 true로 되어있다.
LocalDateTime, LocalTime 등 별도 Converter가 없어도 동작하는 이유다.
2. Enum (엔티티) <-> String (DB)
조금 복잡해보이지만 코드를 보면 이해가 갈 거다.
엔티티 프로퍼티가 Enum인 경우 value를 db에 저장 <-> db 컬럼 값하고 해당 enum의 value를 비교해서 enum 사용
@Entity
class User(
...
@Convert(converter = UserType::class)
val type: UserType
) {
enum class UserType(
override val value: String
): EnumType<String> {
NORMAL("n"),
ADMIN("a"),
}
}
interface EnumType<T> {
val value: T
}
abstract class EnumTypeConverter<T : EnumType<X>, X>(
private val values: Array<T>
) : AttributeConverter<T, X> {
override fun convertToDatabaseColumn(attribute: T): X? {
return attribute.value
}
override fun convertToEntityAttribute(dbData: X?): T? {
return runCatching { values.find { it == dbData } }
.onFailure { /* 실패 로그 */ }
.getOrNull()
}
}
class UserTypeConverter : EnumTypeConverter<UserType, String>(UserType.values())
3. 암호화 <-> 복호화
@Converter(autoApply = true)
private class AESConverter : AttributeConverter<EncryptString, String> {
override fun convertToDatabaseColumn(attribute: String?): String? {
// 암호화
}
override fun convertToEntityAttribute(dbData: String?): EncryptString? {
// 복호화
}
}
'Spring > JPA' 카테고리의 다른 글
[JPA] Hibernate 6.2.5.Final 공식 가이드 번역 (3) - Architecture (0) | 2023.06.25 |
---|---|
[JPA] Hibernate 6.2.5.Final 공식 가이드 번역 (2) - System Requirements (0) | 2023.06.25 |
[JPA] Hibernate 6.2.5.Final 공식 가이드 번역 (1) - Preface (0) | 2023.06.25 |
댓글