PostgreSQL数据库数组类型字段和Java实体对象字段值映射实现(Hibernate JPA)

  • Hibernate允许自定义复杂数据类型的对象字段映射。Hibernate为用户提供了Java and SQL descriptors和UserType接口方式自定义自己的数据类型映射。我们首选Java and SQL descriptors方式,因为它允许更好地分离 Java-to-JDBC 和 JDBC-to-SQL 类型处理。

  • PostgreSQL允许将字段定义成变长的多维数组。数组类型可以是任何基本类型或用户定义类型,枚举类型或复合类型。

  • 在实际应用中有时我们需要定义复杂的数据类型,比如集合或者数组类型数据,往往我们会新建一张关联表来进行一对多的关联,但是这种方式导致增加了数据库的开销;或者是将集合或者数组中的数据拼接成有分割的字符串进行存储,但是这种方式会导致程序代码不友好的情况发生。
    如果这时我们所使用的数据库支持数组类型的字段类型,比如PostgreSQL,就可以将字段定义成数组类型进行数据存储。为了方便地对复杂数据字段ORM映射对象进行操作,我们需要将数据库中的数组自动映射到Java实体对象上。


本文将介绍如何将PostgreSQL数组类型字段值映射到Java Entity字段中
  1. 首先在PostgreSQL中创建如下表并插入测试数据:
CREATE TABLE sal_emp (
	id             integer,
    name            text,
    pay_by_quarter  integer[],
    schedule        text[][]
);
INSERT INTO sal_emp VALUES (1, 'Bill', '{10001, 10002, 10003, 10004}', '{{"meeting", "lunch"}, {"training", "presentation"}}');
INSERT INTO sal_emp VALUES (2, 'Carol', '{20001, 20002, 20003, 20004}', '{{"breakfast", "consulting"}, {"meeting", "lunch"}}');

pay_by_quarter 为一位整型数组、schedule 为二维文本类型数组。

  1. 创建Java实体对象:
@Getter
@Setter
@ToString
@Entity
@Table(name = "sal_emp")
@TypeDefs({
        @TypeDef(name = "string-array", typeClass = StringArrayType.class),
        @TypeDef(name = "int-array", typeClass = IntArrayType.class)
})
public class SalEmpEntity {

    @Id
    @Column(name = "id")
    private Integer id;

    @Column(name = "name")
    private String name;

    @Type(type = "int-array")
    @Column(name = "pay_by_quarter")
    private Integer[] payByQuarter;

    @Type(type = "string-array")
    @Column(name = "schedule")
    private String[][] schedule;

}
  1. 二话不说直接写个Test测试:
@Test
public void testPgFieldTypes2() {
    List<SalEmpEntity> lists = (List<SalEmpEntity>) salEmpRepository.findAll();
    System.out.println(lists);
}

查询结果:
查询结果
查询结果表明数据库数据已经和Java对象字段映射上了!


下面详细说明实现方式:

实体类中我们使用到了@TypeDefs@TypeDef@Type注解。将自定义类型(string-array和int-array)添加到对应实体对象字段。StringArrayType和IntArrayType就是我们自定义实现类。

  1. 下面来定义StringArrayType和IntArrayType类,需要继承AbstractSingleColumnStandardBasicType并实现DynamicParameterizedType:
import org.hibernate.type.AbstractSingleColumnStandardBasicType;
import org.hibernate.usertype.DynamicParameterizedType;
import java.util.Properties;

public class StringArrayType extends AbstractSingleColumnStandardBasicType<String[]>
		implements DynamicParameterizedType {

    public StringArrayType() {
        super(
                ArraySqlTypeDescriptor.INSTANCE,
                StringArrayTypeDescriptor.INSTANCE
        );
    }

    @Override
    public String getName() {
        return "string-array";
    }

    @Override
    protected boolean registerUnderJavaType() {
        return true;
    }

    @Override
    public void setParameterValues(Properties parameters) {
        ((StringArrayTypeDescriptor) getJavaTypeDescriptor()).setParameterValues(parameters);
    }
}
import org.hibernate.type.AbstractSingleColumnStandardBasicType;
import org.hibernate.usertype.DynamicParameterizedType;
import java.util.Properties;

public class IntArrayType extends AbstractSingleColumnStandardBasicType<int[]>
        implements DynamicParameterizedType {

    public IntArrayType() {
        super(
                ArraySqlTypeDescriptor.INSTANCE,
                IntArrayTypeDescriptor.INSTANCE
        );
    }

    @Override
    public String getName() {
        return "int-array";
    }

    @Override
    protected boolean registerUnderJavaType() {
        return true;
    }

    @Override
    public void setParameterValues(Properties parameters) {
        ((IntArrayTypeDescriptor) getJavaTypeDescriptor()).setParameterValues(parameters);
    }
}

  1. 接下面需要来自定义descriptor,定义公共的ArraySqlTypeDescriptor实现SqlTypeDescriptor接口如下:
import org.hibernate.type.descriptor.ValueBinder;
import org.hibernate.type.descriptor.ValueExtractor;
import org.hibernate.type.descriptor.WrapperOptions;
import org.hibernate.type.descriptor.java.JavaTypeDescriptor;
import org.hibernate.type.descriptor.sql.BasicBinder;
import org.hibernate.type.descriptor.sql.BasicExtractor;
import org.hibernate.type.descriptor.sql.SqlTypeDescriptor;
import java.sql.*;

public class ArraySqlTypeDescriptor implements SqlTypeDescriptor {

    public static final ArraySqlTypeDescriptor INSTANCE = new ArraySqlTypeDescriptor();

    @Override
    public int getSqlType() {
        return Types.ARRAY;
    }

    @Override
    public boolean canBeRemapped() {
        return true;
    }

    @Override
    public <X> ValueBinder<X> getBinder(JavaTypeDescriptor<X> javaTypeDescriptor) {
        return new BasicBinder<X>(javaTypeDescriptor, this) {
            @Override
            protected void doBind(
                    PreparedStatement st,
                    X value,
                    int index,
                    WrapperOptions options
            ) throws SQLException {
                AbstractArrayTypeDescriptor<Object> abstractArrayTypeDescriptor
                        = (AbstractArrayTypeDescriptor<Object>) javaTypeDescriptor;
                st.setArray(
                        index,
                        st.getConnection().createArrayOf(
                                abstractArrayTypeDescriptor.getSqlArrayType(),
                                abstractArrayTypeDescriptor.unwrap(
                                        value,
                                        Object[].class,
                                        options
                                )
                        )
                );
            }

            @Override
            protected void doBind(
                    CallableStatement st,
                    X value,
                    String name,
                    WrapperOptions options
            ) throws SQLException {
                throw new UnsupportedOperationException(
                        "Binding by name is not supported!"
                );
            }
        };
    }

    @Override
    public <X> ValueExtractor<X> getExtractor(
            final JavaTypeDescriptor<X> javaTypeDescriptor) {
        return new BasicExtractor<X>(javaTypeDescriptor, this) {
            @Override
            protected X doExtract(
                    ResultSet rs,
                    String name,
                    WrapperOptions options
            ) throws SQLException {
                return javaTypeDescriptor.wrap(
                        rs.getArray(name),
                        options
                );
            }

            @Override
            protected X doExtract(
                    CallableStatement statement,
                    int index,
                    WrapperOptions options
            ) throws SQLException {
                return javaTypeDescriptor.wrap(
                        statement.getArray(index),
                        options
                );
            }

            @Override
            protected X doExtract(
                    CallableStatement statement,
                    String name,
                    WrapperOptions options
            ) throws SQLException {
                return javaTypeDescriptor.wrap(
                        statement.getArray(name),
                        options
                );
            }
        };
    }
}

这段代码我们大概浏览可以知晓是使用JDBC处理ResultSet和java.sql.Array。


  1. 然后定义抽象类AbstractArrayTypeDescriptor继承AbstractTypeDescriptor和实现DynamicParameterizedType。用于实现解析、复制和封装数据的复杂逻辑:
import com.test.utils.ArrayUtil;
import org.hibernate.type.descriptor.WrapperOptions;
import org.hibernate.type.descriptor.java.AbstractTypeDescriptor;
import org.hibernate.type.descriptor.java.MutabilityPlan;
import org.hibernate.type.descriptor.java.MutableMutabilityPlan;
import org.hibernate.usertype.DynamicParameterizedType;
import java.sql.Array;
import java.sql.SQLException;
import java.util.Arrays;
import java.util.Properties;

public abstract class AbstractArrayTypeDescriptor<T> extends AbstractTypeDescriptor<T> implements DynamicParameterizedType {

    private Class<T> arrayObjectClass;

    @Override
    public void setParameterValues(Properties parameters) {
        arrayObjectClass = ((ParameterType) parameters
                .get(PARAMETER_TYPE))
                .getReturnedClass();

    }

    public AbstractArrayTypeDescriptor(Class<T> arrayObjectClass) {
        super(
                arrayObjectClass,
                (MutabilityPlan<T>) new MutableMutabilityPlan<Object>() {
                    @Override
                    protected T deepCopyNotNull(Object value) {
                        return ArrayUtil.deepCopy(value);
                    }
                }
        );
        this.arrayObjectClass = arrayObjectClass;
    }

    @Override
    public boolean areEqual(Object one, Object another) {
        if (one == another) {
            return true;
        }
        if (one == null || another == null) {
            return false;
        }
        return ArrayUtil.isEquals(one, another);
    }

    @Override
    public String toString(Object value) {
        return Arrays.deepToString((Object[]) value);
    }

    @Override
    public T fromString(String string) {
        return ArrayUtil.fromString(
                string,
                arrayObjectClass
        );
    }

    @SuppressWarnings({"unchecked"})
    @Override
    public <X> X unwrap(
            T value,
            Class<X> type,
            WrapperOptions options
    ) {
        return (X) ArrayUtil.wrapArray(value);
    }

    @Override
    public <X> T wrap(
            X value,
            WrapperOptions options
    ) {
        if (value instanceof Array) {
            Array array = (Array) value;
            try {
                return ArrayUtil.unwrapArray((Object[]) array.getArray(), arrayObjectClass);
            } catch (SQLException e) {
                throw new IllegalArgumentException(e);
            }
        }
        return (T) value;
    }

    protected abstract String getSqlArrayType();
}

AbstractArrayTypeDescriptor主要依靠ArrayUtil来处理Java数组的深层复制,包装和展开逻辑。


  1. 最后定义StringArrayType和IntArrayType各自的descriptor,实现AbstractArrayTypeDescriptor:
public class StringArrayTypeDescriptor extends AbstractArrayTypeDescriptor<String[]> {

    public static final StringArrayTypeDescriptor INSTANCE = new StringArrayTypeDescriptor();

    public StringArrayTypeDescriptor() {
        super(String[].class);
    }

    @Override
    protected String getSqlArrayType() {
        return "text";
    }
}
public class IntArrayTypeDescriptor extends AbstractArrayTypeDescriptor<int[]> {

    public static final IntArrayTypeDescriptor INSTANCE = new IntArrayTypeDescriptor();

    public IntArrayTypeDescriptor() {
        super( int[].class );
    }

    @Override
    protected String getSqlArrayType() {
        return "integer";
    }
}

再试试插入操作:

@Test
public void testInsert() {
    SalEmpEntity entity = new SalEmpEntity();
    entity.setId(3);
    entity.setName("caijun");
    Integer[] is = {30000, 30001, 30002};
    entity.setPayByQuarter(is);
    String[][] ss = {{"breakfast", "consulting"}, {"meeting", "lunch"}};
    entity.setSchedule(ss);
    salEmpRepository.save(entity);
}

查询数据,插入成功

select * from sal_emp where id = 3;

插入查询


这样就可以达到将PostgreSQL数组类型字段值映射到Java Entity字段中的目的。你也可根据不同数据类型完成其他类型的自定义映射


参考:https://stackoverflow.com/questions/4332467/mapping-array-with-hibernate

  • 3
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值