Spring/JPA

Jpa ArrayType. PostgreSQL Array Type을 저장하는 방법 - Hibernate Types, List, Array

ysk(0soo) 2023. 1. 5. 00:34

PostgreSQL을 포함한 몇몇 RDB에서는 Array Type의 컬럼 유형을 지원한다.

그러나 hibernate에서 제공해주는 기본 Type들은 Array Type을 지원하지 않는다.

 

개발자는 Hibernate Type을 Custom 구현해서 사용하거나,

라이브러를 이용해서 PostgreSQL ArrayType을 Spring-data-jpa를 이용해서 hibernate로 매핑할 수 있다.

 

테스트에 사용된 도메인 엔티티는 다음과 같다.

도메인 엔티티

@Entity
public class User {
    @Id
    private Long id;
    private String name;

    private String[] roles;

    private List<String> hobbies;

}

1. Custom Hibernate Type

Hibernate는 다양한 기본 Type을 지정해 주므로 Custom 을 구현하기는 드물지만, 데이터를 저장, 가져오는 것에 대해 Custom Type을 지정할 수 있다.

org.hibernate.type.Type

인터페이스를 구현하거나

org.hibernate.usertype.UserType

인터페이스를 구현하고

타입을 적용할 컬럼에 @Type(type = "패키지명.커스텀타입명") 어노테이션을 적용해서 구현할 수 있다.

 

그 중 UserType을 이용해서 구현한 예제이다.

자바의 String[] 배열을 컬럼의 Array 타입으로 변환하는 Custom Type

public class CustomStringArrayType implements UserType {
    @Override
    public int[] sqlTypes() {
        return new int[]{Types.ARRAY};
    }

    @Override
    public Class returnedClass() {
        return String[].class;
    }

    @Override
    public Object nullSafeGet(ResultSet rs, String[] names, SharedSessionContractImplementor session, Object owner)
      throws HibernateException, SQLException {
        Array array = rs.getArray(names[0]);
        return array != null ? array.getArray() : null;
    }

    @Override
    public void nullSafeSet(PreparedStatement st, Object value, int index, SharedSessionContractImplementor session)
      throws HibernateException, SQLException {
        if (value != null && st != null) {
            Array array = session.connection().createArrayOf("text", (String[])value);
            st.setArray(index, array);
        } else {
            st.setNull(index, sqlTypes()[0]);
        }
    }
    //implement equals, hashCode, and other methods 
}
  • sqlTypes() 메소드 : 매핑된 컬럼에 대한 SQL Type을 반환해야 한다.
    • SQL Type은 java.sql.Types에 정의되어 있다.
  • returnedClass() 메소드 : nullSafeGet() 메소드에 의해 반환될 클래스 타입을 지정해야 한다.
    • SQL Type과 같아야 한다
    • 사용자 지정 커스텀 클래스 (예를들면 Embedded 클래스)도 반환이 가능하다.
  • nullSafeGet() 메소드 : DB에서 값을 읽어온 후 자바 데이터 타입으로 처리한다.
    • null을 반환할 수 있으므로 null 처리가 필요하다.
  • nullSafeSet() 메소드 : DB에 값을 쓸 때, 자바 타입을 어떻게 쓸건지 처리한다.
    • 여기서는 PostgreSQL text 타입의 배열을 지정한다.

 

그런 다음 CustomStringArrayType 클래스를 사용하여 문자열 배열을 PostgreSQL 텍스트 배열에 매핑한다.  

@Entity
public class User {
    //...

    @Column(columnDefinition = "text[]")
    @Type(type = "com.ys.arraymapping.CustomStringArrayType")
    private String[] roles;

}

2. hibernate-types 라이브러리로 매핑

깃허브 : https://github.com/vladmihalcea/hypersistence-utils

vladmihalcea의 오픈소스 라이브러리를 사용하면 쉽게 구현할 수 있다

여기서 다양한 데이터 타입들을 커스텀해서 지원해준다.

  • 라이브러리를 추가하고 필요한것만 사용하면 된다.

의존성 추가

implementation("com.vladmihalcea:hibernate-types-52:2.21.1")

하이버네이트 버전에 따른 지원하는 버전이 다르므로, 깃허브 가서 보고 하이브네이트 버전에 맞는 라이브러리 버전을 선택하면 된다.

 

List<String> 타입과 String[] 타입을 매핑하려면 다음과 같이 사용하면 된다.

@TypeDefs({
    @TypeDef(
        name = "string-array",
        typeClass = StringArrayType.class
    ),
    @TypeDef(
    name = "list-array",
    typeClass = ListArrayType.class
        )
})
@Entity
public class User {

  @Id
  private Long id;

  private String name;
  @Type(type = "string-array")
  @Column(
        name = "roles",
        columnDefinition = "text[]"
  )
  private String[] roles;

  @Type(type = "list-array")
  @Column(
        name = "hobbies",
        columnDefinition = "text[]"
   )
  private List<String> hobbies;

}

Custom Type을 구현한것과 같이 CustomStringArrayType 과 유사하게,
String 배열 에 대한 매퍼로서 hibernate-types 라이브러리에서 제공하는 StringArrayType 클래스를 사용할 수 있다.

 

또한, list-array 타입도 지원하므로 List에 대해서도 사용할 수 있다.

  • 라이브러리에서 DateArrayType, EnumArrayType, DoubleArrayType 과 같은 몇 가지 편리한 매퍼도 추가적으로 제공한다.

hibernate-types 에서 지원하는 다양한 Array Types 예제

스키마와 도메인 모델

event라는 Table이 있다. 이 Table의 스키마는 다음과 같다.

 

다음은 event table과 매핑되는 Entity Domain model이다.

 

다음과 같이 매핑할 수 있다.

@Entity(name = "Event")
@Table(name = "event")
@TypeDef(
    name = "list-array",
    typeClass = ListArrayType.class
)
public class Event {

    @Id
    private Long id;

    @Type(type = "list-array")
    @Column(
        name = "sensor_ids",
        columnDefinition = "uuid[]"
    )
    private List<UUID> sensorIds;

    @Type(type = "list-array")
    @Column(
        name = "sensor_names",
        columnDefinition = "text[]"
    )
    private List<String> sensorNames;

    @Type(type = "list-array")
    @Column(
        name = "sensor_values",
        columnDefinition = "integer[]"
    )
    private List<Integer> sensorValues;

    @Type(type = "list-array")
    @Column(
        name = "sensor_long_values",
        columnDefinition = "bigint[]"
    )
    private List<Long> sensorLongValues;

    @Type(
        type = "io.hypersistence.utils.hibernate.type.array.ListArrayType",
        parameters = {
            @Parameter(
                name = ListArrayType.SQL_ARRAY_TYPE,
                value = "sensor_state"
            )
        }
    )
    @Column(
        name = "sensor_states",
        columnDefinition = "sensor_state[]"
    )
    private List<SensorState> sensorStates;

    @Type(type = "list-array")
    @Column(
        name = "date_values",
        columnDefinition = "date[]"
    )
    private List<Date> dateValues;

    @Type(type = "list-array")
    @Column(
        name = "timestamp_values",
        columnDefinition = "timestamp[]"
    )
    private List<Date> timestampValues;

    //Getters and setters omitted for brevity
}

https://vladmihalcea.com/postgresql-array-java-list/

이 사이트에 사용방법이 자주 업데이트 되므로 필요할때마다 가서 보면 된다.

참조