PostgreSQL JSONB, Enum Array를 JPA Entity에 매핑하기: 트러블슈팅 기록

순덕·2025년 4월 26일
0
post-thumbnail

문제 상황

최근 진행 중인 프로젝트에서 비즈니스 뷰를 구축하게 되었다.
이 뷰에는 도감 데이터를 하나의 테이블로 집약해 관리할 필요가 있었는데, 이 과정에서 자연스럽게 RDBMS의 전통적인 "정형 데이터"만 다루는 방식을 넘어, JSON이나 Array 같은 타입도 함께 사용하게 되었다.

특히 PostgreSQL은 jsonb 타입과 배열(array) 타입을 강력하게 지원한다.
이번 작업에서는

  • jsonb 타입으로 객체 리스트를 저장하고,
  • enum array 타입으로 여러 열거형 값을 저장하는 방식을 적용했다.
    (※ enum 타입은 habitat_type_enum이라는 PostgreSQL enum을 미리 정의해두었다.)

문제는, 이걸 JPA Entity에 어떻게 매핑할 것인가였다. jsonb나 enum array는 RDB스러운 정형화된 데이터 형식이 아니므로, 단순히 @Column만으로 매핑하긴 어렵다.

해결 과정

1. 기본 검색

처음 검색을 통해 다음 사실을 알게 되었다:

  • Hibernate 6부터는 @JdbcTypeCode(SqlTypes.JSON)을 통해 jsonb 매핑이 표준 지원된다.
  • PostgreSQL의 enum array는 별도로 지원되지 않기 때문에, hypersistence-utils라는 오픈소스 라이브러리를 사용하는 방법이 소개되어 있었다.

참고 문서:
How to map a PostgreSQL Enum ARRAY to a JPA entity property using Hibernate

이 문서에서는 다음과 같은 방식이 제시되었다.

@Type(
    value = EnumArrayType.class,
    parameters = @Parameter(
        name = AbstractArrayType.SQL_ARRAY_TYPE,
        value = "sensor_state"
    )
)
@Column(
    name = "sensor_states",
    columnDefinition = "sensor_state[]"
)
private SensorState[] sensorStates;

이를 참고해 habitats 필드에 적용해봤다.

@Type(
    value = EnumArrayType.class,
    parameters = @Parameter(
        name = AbstractArrayType.SQL_ARRAY_TYPE,
        value = "habitat_type_enum"
    )
)
@Column(
    name = "habitats",
    columnDefinition = "habitat_type_enum[]"
)
private HabitatType[] habitats;

2. 삽질과 문제

하지만 적용 과정에서 여러 에러가 발생했다.
(※ 정확한 원인은 알 수 없었지만, Hibernate 버전이나 hypersistence-utils와의 호환성 문제로 추정된다.)

결국, 문서 방식을 포기하고 직접 실험을 시작했다.
그 결과, 굳이 @Type을 쓰지 않고, 단순히 @JdbcTypeCode(SqlTypes.ARRAY)만 붙이면 정상 동작한다는 것을 발견했다!

@JdbcTypeCode(SqlTypes.ARRAY)
@Column(name = "habitats")
private List<HabitatType> habitats;

정리:

  • jsonb@JdbcTypeCode(SqlTypes.JSON)
  • enum[]@JdbcTypeCode(SqlTypes.ARRAY)

두 경우 모두 @JdbcTypeCode로 심플하게 해결할 수 있었다. Hibernate 5 버전까지는 hypersistence-utils 같은 오픈소스 라이브러리를 쓸 수밖에 없던 상황으로 보이나, 6부터는 이렇게 Hibernate가 표준으로 지원하는 @JdbcTypeCode를 적극 활용하는 게 좋겠다.

3. JSON Key 매칭 문제

jsonb 타입 매핑을 할 때 또 하나 고려해야 할 점이 있었다.
바로 JSON Key와 Java 필드명 매칭 문제였다.

PostgreSQL에 저장된 JSON 구조는 Snake Case(s3_url, original_url 등)였는데,
Java에서는 Camel Case(s3Url, originalUrl)를 쓰고 싶었다.

처음에는 그냥 맞춰주기 위해 Java 필드명도 Snake Case로 작성했다.

private String s3_url;
private String original_url;

하지만 가독성 문제와 코딩 스타일 불일치가 거슬렸다.
그래서 다른 방법을 찾다가 @JsonProperty 어노테이션을 발견했다.

@Data
public static class Image {
    @JsonProperty("s3_url")
    private String s3Url;

    @JsonProperty("original_url")
    private String originalUrl;

    @JsonProperty("is_thumb")
    private Boolean isThumb;

    @JsonProperty("order_index")
    private Integer orderIndex;
}

이렇게 설정해주니,

  • 데이터베이스에서는 그대로 Snake Case를 유지하고
  • Java 코드에서는 자연스럽게 Camel Case를 사용할 수 있게 되었다.

그리고, 매핑 오류 없이 정상적으로 동작했다.

최종 결과

최종적으로 Entity 클래스는 다음과 같은 구조가 되었다.

@Entity
@Immutable
@Table(name = "bird_profile_mv")
public class BirdProfile {

    @Id
    private Long id;

    (... 중략 ...)

    @JdbcTypeCode(SqlTypes.ARRAY)
    @Column(name = "habitats")
    private List<HabitatType> habitats;

    @JdbcTypeCode(SqlTypes.JSON)
    @Column(name = "seasons_with_rarity")
    private List<SeasonWithRarity> seasonsWithRarity;

    @JdbcTypeCode(SqlTypes.JSON)
    @Column(name = "images")
    private List<Image> images;

    @Data
    public static class SeasonWithRarity {

        @JsonProperty("rarity")
        private String rarity;

        @JsonProperty("season")
        private String season;

        @JsonProperty("priority")
        private Integer priority;
    }

    @Data
    public static class Image {

        @JsonProperty("s3_url")
        private String s3Url;

        @JsonProperty("original_url")
        private String originalUrl;

        @JsonProperty("is_thumb")
        private Boolean isThumb;

        @JsonProperty("order_index")
        private Integer orderIndex;
    }
}
profile
soonduck dreams

0개의 댓글