본문 바로가기
Spring/JPA

[Spring] JPA의 AttributeConverter, @Converter, @Convert 사용법

by 노력남자 2024. 2. 4.
반응형

이번 포스팅에선 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? {
        // 복호화
    }
}
반응형

댓글