Hibernate自定义类型允许映射各种数据库特定的列类型,比如IP地址、JSON列、位集或SQL数组。定义定制Hibernate类型有两种方法: UserType接口和 Java and SQL descriptors。 Java and SQL descriptors是首选的,因为它允许更好地分离 Java-to-JDBC 和 JDBC-to-SQL 类型处理。在本文中,我们将了解如何将PostgreSQL数据库中的数组映射到它们的Java对应对象中。
首先我们在postgreSQL数据库中创建如下含有Array列的test表:
create table array_test (
id int8 not null,
version int4,
sensor_names text[],
sensor_values integer[],
primary key (id)
)
我们希望使用JPA和Hibernate映射这个表。但是,JPA和Hibernate默认都不支持SQL数组,我们希望将这些数组分别映射到字符串和int Java数组。
这个数据库表的JPA实体映射如下(string-array和int-array是自定义类型,实现SQL数组与java数组转换):
@Entity(name = "ArrayTestEntity")
@Table(name = "array_test")
@TypeDefs({
@TypeDef(name = "string-array", typeClass = StringArrayType.class),
@TypeDef(name = "int-array", typeClass = IntArrayType.class)
})
public class ArrayTestEntity {
@Id
private Long id;
@Version
private int version;
@Type( type = "string-array" )
@Column(name = "sensor_names",columnDefinition = "text[]")
private String[] sensorNames;
@Type( type = "int-array" )
@Column(name = "sensor_values", columnDefinition = "int[]")
private int[] sensorValues;
//Getters and setters omitted for brevity
}
接下来完成自定义StringArrayType类与IntArrayType类,需要继承AbstractSingleColumnStandardBasicType类并完成定义 Java and SQL descriptors:
public class StringArrayType
extends AbstractSingleColumnStandardBasicType<String[]>
implements DynamicParameterizedType {
public StringArrayType() {
super(
ArraySqlTypeDescriptor.INSTANCE,
StringArrayTypeDescriptor.INSTANCE
);
}
public String getName() {
return "string-array";
}
@Override
protected boolean registerUnderJavaType() {
return true;
}
@Override
public void setParameterValues(Properties parameters) {
((StringArrayTypeDescriptor)
getJavaTypeDescriptor())
.setParameterValues(parameters);
}
}
public class IntArrayType
extends AbstractSingleColumnStandardBasicType<int[]>
implements DynamicParameterizedType {
public IntArrayType() {
super(
ArraySqlTypeDescriptor.INSTANCE,
IntArrayTypeDescriptor.INSTANCE
);
}
public String getName() {
return "int-array";
}
@Override
protected boolean registerUnderJavaType() {
return true;
}
@Override
public void setParameterValues(Properties parameters) {
((IntArrayTypeDescriptor)
getJavaTypeDescriptor())
.setParameterValues(parameters);
}
}
接下来还有最后一步就是descriptor的定义,从前面两个自定义数组类提到的Hibernate类型中可以看出,两个数组类共享用于处理jdbc到sql类型映射的相同SqlTypeDescriptor,那么可以写一个公用的实现SqlTypeDescriptor类如下所示:
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
);
}
};
}
}
基本上,ArraySqlTypeDescriptor定义了Hibernate如何使用JDBC语句处理Statement.setArray 和ResultSet.
StringArrayTypeDescriptor很简单,因为大部分逻辑都封装在AbstractArrayTypeDescriptor中。StringArrayTypeDescriptor定义了期望的Java类型(如String[])和postgreSQL底层数据库数组类型(如text[])。
public class StringArrayTypeDescriptor
extends AbstractArrayTypeDescriptor<String[]> {
public static final StringArrayTypeDescriptor INSTANCE =
new StringArrayTypeDescriptor();
public StringArrayTypeDescriptor() {
super( String[].class );
}
@Override
protected String getSqlArrayType() {
return "text";
}
}
IntArrayTypeDescriptor也是一样,因为它定义了预期的Java类型(例如int[])和底层数据库数组类型(例如int[])。
public class IntArrayTypeDescriptor
extends AbstractArrayTypeDescriptor<int[]> {
public static final IntArrayTypeDescriptor INSTANCE =
new IntArrayTypeDescriptor();
public IntArrayTypeDescriptor() {
super( int[].class );
}
@Override
protected String getSqlArrayType() {
return "integer";
}
}
这样就可以实现Java数组与postgreSQL数据库数组列之间的映射。同理也可以完成其他数组的映射。