Hibernate允许自定义复杂数据类型的对象字段映射。Hibernate为用户提供了Java and SQL descriptors和UserType接口方式自定义自己的数据类型映射。我们首选Java and SQL descriptors方式,因为它允许更好地分离 Java-to-JDBC 和 JDBC-to-SQL 类型处理。
PostgreSQL允许将字段定义成变长的多维数组。数组类型可以是任何基本类型或用户定义类型,枚举类型或复合类型。
在实际应用中有时我们需要定义复杂的数据类型,比如集合或者数组类型数据,往往我们会新建一张关联表来进行一对多的关联,但是这种方式导致增加了数据库的开销;或者是将集合或者数组中的数据拼接成有分割的字符串进行存储,但是这种方式会导致程序代码不友好的情况发生。
如果这时我们所使用的数据库支持数组类型的字段类型,比如PostgreSQL,就可以将字段定义成数组类型进行数据存储。为了方便地对复杂数据字段ORM映射对象进行操作,我们需要将数据库中的数组自动映射到Java实体对象上。
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 为二维文本类型数组。
@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;
}
@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就是我们自定义实现类。
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);
}
}
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。
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数组的深层复制,包装和展开逻辑。
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