스프링 - PostgreSQL text[] 타입을 엔티티의 List 필드에 매핑하기
작성 일자 : 2025년 02월 02일
개요
PostgreSQL에서는 text[]
타입과 같은 배열 타입을 기본으로 지원합니다.
이를 활용하면 @ElementCollection
을 사용하는 방법과는 다르게 별도의 조인 테이블을 생성하지 않고도 여러 값을 하나의 컬럼에 저장할 수 있습니다.
하지만, Hibernate는 기본적으로 List<String>
과 PostgreSQL의 text[]
타입을 매핑할 수 없기 때문에 직접 변환 로직을 구현해주어야 합니다.
사용자 정의 Hibernate UserType: StringArrayType
구현
먼저, Java의 List과 PostgreSQL의 text[] 사이의 변환을 담당하는 사용자 정의 UserType을 만들어 보겠습니다.
import java.io.Serializable;
import java.sql.Array;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import org.hibernate.dialect.Dialect;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.type.descriptor.jdbc.JdbcType;
import org.hibernate.type.spi.TypeConfiguration;
import org.hibernate.usertype.UserType;
public class StringArrayType implements UserType<List<String>> {
@Override
public int getSqlType() {
return Types.ARRAY;
}
@Override
@SuppressWarnings("unchecked")
public Class<List<String>> returnedClass() {
return (Class<List<String>>) (Class<?>) List.class;
}
@Override
public boolean equals(List<String> x, List<String> y) {
return Objects.equals(x, y);
}
@Override
public int hashCode(List<String> x) {
return x == null ? 0 : x.hashCode();
}
@Override
public List<String> nullSafeGet(ResultSet rs, int position,
SharedSessionContractImplementor session, Object owner) throws SQLException {
Array array = rs.getArray(position);
if (array == null) {
return null;
}
return Arrays.asList((String[]) array.getArray());
}
@Override
public void nullSafeSet(PreparedStatement st, List<String> value, int index,
SharedSessionContractImplementor session) throws SQLException {
if (st != null) {
if (value != null) {
try (var connection = session.getJdbcConnectionAccess().obtainConnection()) {
Array array = connection.createArrayOf("text", value.toArray());
st.setArray(index, array);
}
} else {
st.setNull(index, Types.ARRAY);
}
}
}
@Override
public List<String> deepCopy(List<String> value) {
return value == null ? null : List.copyOf(value);
}
@Override
public boolean isMutable() {
return true;
}
@Override
public Serializable disassemble(List<String> value) {
return (Serializable) value;
}
@Override
@SuppressWarnings("unchecked")
public List<String> assemble(Serializable cached, Object owner) {
return (List<String>) cached;
}
@Override
public List<String> replace(List<String> detached, List<String> managed, Object owner) {
return deepCopy(detached);
}
@Override
public JdbcType getJdbcType(TypeConfiguration typeConfiguration) {
return typeConfiguration.getJdbcTypeRegistry().getDescriptor(getSqlType());
}
@Override
public long getDefaultSqlLength(Dialect dialect, JdbcType jdbcType) {
return 255L;
}
@Override
public int getDefaultSqlPrecision(Dialect dialect, JdbcType jdbcType) {
return 0;
}
@Override
public int getDefaultSqlScale(Dialect dialect, JdbcType jdbcType) {
return 0;
}
}
엔티티 클래스에 List<String>
필드 추가
다음으로, 엔티티 클래스에 List<String>
타입의 필드를 추가하고, @Type
어노테이션을 사용하여 사용자 정의 UserType을 지정해줍니다.
@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Table(name = "sample_entities")
public class SampleEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
/*
* PostgreSQL의 text[] 컬럼과 매핑됩니다.
*/
@Type(StringArrayType.class)
@Column(name = "string_array", columnDefinition = "text[]", nullable = false)
private List<String> stringArray;
}
@Type
어노테이션을 사용하여 Hibernate에게 해당 필드가 사용자 정의StringArrayType
을 이용해 매핑되어야 함을 알려줍니다.columnDefinition
속성을 사용하여 실제 데이터베이스 컬럼이 PostgreSQL의text[]
타입임을 명시합니다.
Reference
deltafi/deltafi-core/src/main/java/org/deltafi/core/types/hibernate/StringArrayType.java at 18b4ef67e7eb2da185b6ba52fedb0036bfb6
DeltaFi is a flexible, code-light data transformation and normalization platform. - deltafi/deltafi
github.com