Spring/Spring JPA

@Converter 간단하게

개발자잡 2025. 1. 7. 00:48

@Converter는 JPA가 제공하는 어노테이션으로, 엔티티 필드와 데이터베이스 컬럼 간의 변환 로직을 정의할 때 사용합니다.

주요 기능

  • Java 객체 ↔ DB 데이터 간의 변환
    엔티티 필드 값을 데이터베이스에 저장하거나 읽어올 때 변환 작업을 자동으로 수행합니다.
  • 재사용 가능
    변환 로직을 분리해 여러 엔티티에서 쉽게 재사용할 수 있습니다.
  • 자동 적용
    @Converter(autoApply = true)를 설정하면, 특정 타입에 대해 변환기를 전역적으로 적용할 수 있습니다.

2. 기본 사용법

2.1. Converter 인터페이스

변환 로직은 jakarta.persistence.AttributeConverter 인터페이스를 구현하여 정의합니다.

public interface AttributeConverter<X, Y> {
    Y convertToDatabaseColumn(X attribute); // Java 객체 → DB 값
    X convertToEntityAttribute(Y dbData);   // DB 값 → Java 객체
}

 

 

  • X: 엔티티 필드의 데이터 타입 (Java 객체 타입)
  • Y: 데이터베이스 컬럼의 데이터 타입

2.2. 예제: Enum ↔ String 변환

1. Enum 타입 정의

public enum UserStatus {
    ACTIVE,
    INACTIVE,
    SUSPENDED
}

 

2. Converter 구현

import jakarta.persistence.AttributeConverter;
import jakarta.persistence.Converter;

@Converter
public class UserStatusConverter implements AttributeConverter<UserStatus, String> {

    @Override
    public String convertToDatabaseColumn(UserStatus status) {
        if (status == null) {
            return null;
        }
        return status.name(); // Enum → String
    }

    @Override
    public UserStatus convertToEntityAttribute(String dbData) {
        if (dbData == null || dbData.isEmpty()) {
            return null;
        }
        return UserStatus.valueOf(dbData); // String → Enum
    }
}

 

 

3. 엔티티에서 사용

@Entity
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Convert(converter = UserStatusConverter.class)
    private UserStatus status;

    // Getter, Setter
}

@Convert를 통해 필드에 변환기를 명시적으로 적용합니다.

 

2.3. autoApply로 전역 적용

autoApply = true로 설정하면, 해당 타입에 대해 모든 엔티티에서 자동으로 변환기가 적용됩니다.

@Converter(autoApply = true)
public class UserStatusConverter implements AttributeConverter<UserStatus, String> {
    // 변환 로직 동일
}
  • @Convert를 엔티티 필드에 명시하지 않아도 자동으로 적용됩니다.

 

 

 

3. 다양한 사용 사례

3.1. JSON 데이터 처리

데이터베이스에 JSON 데이터를 저장하고, 엔티티에서는 Java 객체로 매핑하고 싶을 때.

1. POJO 클래스 정의

public class Address {
    private String street;
    private String city;

    // Getter, Setter, toString
}

 

2. JSON 변환기 구현

Jackson 라이브러리를 활용한 예제:

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.persistence.AttributeConverter;
import jakarta.persistence.Converter;

@Converter
public class AddressConverter implements AttributeConverter<Address, String> {

    private static final ObjectMapper objectMapper = new ObjectMapper();

    @Override
    public String convertToDatabaseColumn(Address address) {
        try {
            return address == null ? null : objectMapper.writeValueAsString(address);
        } catch (JsonProcessingException e) {
            throw new IllegalArgumentException("Error converting Address to JSON", e);
        }
    }

    @Override
    public Address convertToEntityAttribute(String dbData) {
        try {
            return dbData == null ? null : objectMapper.readValue(dbData, Address.class);
        } catch (JsonProcessingException e) {
            throw new IllegalArgumentException("Error converting JSON to Address", e);
        }
    }
}

 

 

3. 엔티티에서 사용

@Entity
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Convert(converter = AddressConverter.class)
    private Address address;

    // Getter, Setter
}

 

 

3.2. 암호화된 데이터 처리

데이터베이스에 저장되는 값을 암호화하고, 엔티티에서 복호화하여 사용.

import jakarta.persistence.AttributeConverter;
import jakarta.persistence.Converter;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import java.util.Base64;

@Converter
public class EncryptionConverter implements AttributeConverter<String, String> {

    private static final String ALGORITHM = "AES";
    private static final SecretKey secretKey;

    static {
        try {
            KeyGenerator keyGen = KeyGenerator.getInstance(ALGORITHM);
            keyGen.init(128);
            secretKey = keyGen.generateKey();
        } catch (Exception e) {
            throw new RuntimeException("Error initializing encryption key", e);
        }
    }

    @Override
    public String convertToDatabaseColumn(String attribute) {
        try {
            Cipher cipher = Cipher.getInstance(ALGORITHM);
            cipher.init(Cipher.ENCRYPT_MODE, secretKey);
            return Base64.getEncoder().encodeToString(cipher.doFinal(attribute.getBytes()));
        } catch (Exception e) {
            throw new IllegalArgumentException("Error encrypting data", e);
        }
    }

    @Override
    public String convertToEntityAttribute(String dbData) {
        try {
            Cipher cipher = Cipher.getInstance(ALGORITHM);
            cipher.init(Cipher.DECRYPT_MODE, secretKey);
            return new String(cipher.doFinal(Base64.getDecoder().decode(dbData)));
        } catch (Exception e) {
            throw new IllegalArgumentException("Error decrypting data", e);
        }
    }
}
  • 엔티티 필드에 암호화된 데이터를 저장하고, 사용할 때 복호화된 값을 제공.

4. Converter 사용 시 주의 사항

  1. Null 처리
    • 변환기 로직에서 null 값에 대한 처리를 명확히 해야 합니다.
    • 예: convertToDatabaseColumn과 convertToEntityAttribute에서 null을 반환.
  2. 성능
    • JSON 변환, 암호화/복호화와 같은 작업은 성능에 영향을 줄 수 있으므로 주의가 필요합니다.
  3. 재사용 가능성
    • 변환기는 여러 엔티티에서 재사용 가능하도록 설계하는 것이 좋습니다.
  4. 전역 적용 시 충돌 주의
    • autoApply = true로 설정하면, 해당 타입에 대해 항상 변환기가 적용되므로, 의도치 않은 충돌이 발생할 수 있습니다.

 

5. 결론

  • JPA의 Converter는 데이터 변환 로직을 분리하여 코드의 재사용성과 유지보수성을 높이는 강력한 도구입니다.
  • 특히, Enum 처리, JSON 변환, 암호화/복호화와 같은 작업에서 유용합니다.
  • 사용 시, 성능과 유지보수성을 고려해 설계해야 하며, 필요에 따라 autoApply를 활용해 변환 로직을 전역으로 적용할 수 있습니다.