최근 진행 중인 프로젝트에서 비즈니스 뷰를 구축하게 되었다.
이 뷰에는 도감 데이터를 하나의 테이블로 집약해 관리할 필요가 있었는데, 이 과정에서 자연스럽게 RDBMS의 전통적인 "정형 데이터"만 다루는 방식을 넘어, JSON이나 Array 같은 타입도 함께 사용하게 되었다.
특히 PostgreSQL은 jsonb
타입과 배열(array
) 타입을 강력하게 지원한다.
이번 작업에서는
habitat_type_enum
이라는 PostgreSQL enum을 미리 정의해두었다.)문제는, 이걸 JPA Entity에 어떻게 매핑할 것인가였다. jsonb나 enum array는 RDB스러운 정형화된 데이터 형식이 아니므로, 단순히 @Column
만으로 매핑하긴 어렵다.
처음 검색을 통해 다음 사실을 알게 되었다:
@JdbcTypeCode(SqlTypes.JSON)
을 통해 jsonb
매핑이 표준 지원된다.참고 문서:
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;
하지만 적용 과정에서 여러 에러가 발생했다.
(※ 정확한 원인은 알 수 없었지만, 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
를 적극 활용하는 게 좋겠다.
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;
}
이렇게 설정해주니,
그리고, 매핑 오류 없이 정상적으로 동작했다.
최종적으로 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;
}
}