Spring Boot에서 JSON 데이터 타입에 AttributeConverter 활용하기
2025년 04월 19일
spring
📕 목차
AttributeConverter란?
Java Persistence API (JPA)에서 AttributeConverter
는 엔티티 속성과 데이터베이스 컬럼 간의 변환을 정의하는 인터페이스입니다.
이 기능은 엔티티 속성을 데이터베이스에 저장하거나, 데이터베이스로부터 엔티티로 데이터를 로드할때 어떠한 방식으로 해석해야 하는지를 정의합니다.
저는 주로 복잡한 Enum 클래스 혹은 JSON 데이터를 다룰 때, 이 기능을 사용합니다.
AttributeConverter 인터페이스 살펴보기
이 인터페이스는 두 개의 타입 파라미터를 가집니다.
- X: 엔티티 속성의 타입 (예: Map<String, Object>)
- Y: 데이터베이스 컬럼의 타입 (예: String)
public interface AttributeConverter<X, Y> {
Y convertToDatabaseColumn(X attribute);
X convertToEntityAttribute(Y dbData);
}
스펙을 살펴보면 다음과 같은 메서드를 구현해야합니다.
- 데이터베이스 컬럼 타입(Y)을 -> 엔티티 속성타입(X)로 변환하는
convertToEntityAttribute
메서드 - 엔티티 속성타입(X) -> Y로 변환하는
convertToDatabaseColumn
메서드를 구현해야 합니다.
컨버터(AttributeConverter) 사용사례
Case 1. 복잡한 Enum 클래스
우리는 DB 필드에 지정된 값만 저장되도록 강제할 때 Enum 타입을 사용합니다. 보통의 Enum은 다음과 같이 정의합니다
enum class OrderStatus {
PENDING, SHIPPED, DELIVERED, CANCELED
}
이 경우에는 보통 @Enumerated(EnumType.STRING)을 사용하여 JPA Entity 컬럼에 Enum을 매핑합니다.
@Entity
@Table(name = "orders")
public class Order {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String customerName;
@Enumerated(EnumType.STRING)
private OrderStatus status;
}
하지만 Enum이 복잡한 구조를 가질 때는 Enum의 어떤 속성으로 값을 저장하고 불러올 지 알 수 없습니다. 그래서, 우리는 AttributeConverter
를 사용하여 Enum의 code와 같은 특정 필드의 값으로 저장하고 DB로부터 읽은 값을 Enum으로 변환할 수 있습니다.
예를 들어, OrderStatus가 다음과 같이 정의되어 있다고 가정해봅시다
enum class OrderStatus(val code: String, val description: String) {
PENDING("P", "주문 대기"),
SHIPPED("S", "배송 중"),
DELIVERED("D", "배송 완료"),
CANCELED("C", "주문 취소");
companion object {
fun fromCode(code: String): OrderStatus? {
return values().find { it.code == code }
}
}
}
이 경우, OrderStatus의 code를 DB에 저장하고, 이를 다시 Enum으로 변환하는 AttributeConverter를 구현할 수 있습니다
import jakarta.persistence.AttributeConverter;
import jakarta.persistence.Converter;
import jakarta.persistence.Enumerated;
import jakarta.persistence.EnumType;
@Converter(autoApply = true)
public class OrderStatusConverter: AttributeConverter<OrderStatus, String> {
@Override
public String convertToDatabaseColumn(OrderStatus attribute) {
if (attribute == null) return null
return attribute.code
}
@Override
public OrderStatus convertToEntityAttribute(String dbData) {
if (dbData == null) return null
return OrderStatus.fromCode(dbData)
}
}
Case 2. 구체 클래스에 JSON 데이터 매핑
이번에는 JSON 데이터를 구체 클래스에 매핑해서 타입을 사용해서 데이터를 처리하는 방법을 살펴보겠습니다. 다음과 같은 JSON 데이터가 있다고 가정해봅시다.
{
"baseAddress": "서울시 강남구 역삼동",
"detailAddress": "1234호",
"zipCode": "123-456",
"phoneNumber": "010-1234-5678"
}
우리는 이런 데이터는 Map<String, Object> 형태로도 사용할 수 있지만, 구체적인 클래스를 만들어서 타입 안전성을 높일 수 있습니다.
public class Address {
private String baseAddress;
private String detailAddress;
private String zipCode;
private String phoneNumber;
}
Address라는 클래스를 DB에 저장하기 위해서는 JSON 문자열로 변환해야하고, DB에서 읽어올 때는 JSON 문자열을 Address 객체로 변환해야 합니다. 이때 ObjectMapper를 사용해서 처리하는 AttributeConverter를 구현해서 처리하면 됩니다.
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.persistence.AttributeConverter;
import jakarta.persistence.Converter;
@Converter(autoApply = true)
class AddressConverter : AttributeConverter<Address, String> {
private val objectMapper = ObjectMapper().apply {
registerModule(KotlinModule().Builder().build())
}
@Override
public String convertToDatabaseColumn(Address attribute) {
if (attribute == null) return null
return objectMapper.writeValueAsString(attribute)
}
@Override
public Address convertToEntityAttribute(String dbData) {
if (dbData.isNullOrBlank()) return Address(
baseAddress = "",
detailAddress = "",
zipCode = "",
phoneNumber = ""
)
try {
// JSON 문자열을 Address 객체로 변환
return objectMapper.readValue<Address>(dbData)
} catch (e: Exception) {
throw RuntimeException("Failed to convert JSON to Address", e)
}
}
}
이제 AddressConverter를 사용하여 Address 객체를 DB에 저장하고, DB에서 읽어올 수 있습니다.
@Entity
@Table(name = "orders")
class Order(
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
val id: Long? = null,
// Converter 지정
@Convert(converter = AddressConverter::class)
@Column(name = "address", columnDefinition = "json")
val address: Address
)