Как использовать Spring Data / JPA для вставки в столбец типа массива Postgres?
скажем, у меня есть таблица postgres так:
CREATE TABLE sal_emp (
name text,
pay_by_quarter integer[],
schedule text[][]
);
смогу ли я даже использовать данные Spring для вставки в столбцы pay_by_quarter
или schedule
? Если возможно, как это будет выглядеть как репозиторий и сущность ? Я не смог найти никакой документации или примеров, касающихся этого, возможно, из-за того, как он перекрывается с более распространенным вариантом использования, вставляя в несколько таблиц как отношения "один ко многим". Говоря об этом, я полностью намерен использовать Postgresql array
тип данных и отсутствие реляционных таблиц.
1 ответов
вам нужно создать свой собственный тип и реализовать UserType interface
. На основе в next ответ я написал общий UserType
использовать во всех массивах, и он работает, но вы должны использовать не примитивные типы данных (Целое Число, Длинное, Строка,...). В противном случае см. приведенное выше обновление с Boolean
тип.
public class GenericArrayUserType<T extends Serializable> implements UserType {
protected static final int[] SQL_TYPES = { Types.ARRAY };
private Class<T> typeParameterClass;
@Override
public Object assemble(Serializable cached, Object owner) throws HibernateException {
return this.deepCopy(cached);
}
@Override
public Object deepCopy(Object value) throws HibernateException {
return value;
}
@SuppressWarnings("unchecked")
@Override
public Serializable disassemble(Object value) throws HibernateException {
return (T) this.deepCopy(value);
}
@Override
public boolean equals(Object x, Object y) throws HibernateException {
if (x == null) {
return y == null;
}
return x.equals(y);
}
@Override
public int hashCode(Object x) throws HibernateException {
return x.hashCode();
}
@Override
public boolean isMutable() {
return true;
}
@Override
public Object nullSafeGet(ResultSet resultSet, String[] names, SessionImplementor session, Object owner)
throws HibernateException, SQLException {
if (resultSet.wasNull()) {
return null;
}
if (resultSet.getArray(names[0]) == null) {
return new Integer[0];
}
Array array = resultSet.getArray(names[0]);
@SuppressWarnings("unchecked")
T javaArray = (T) array.getArray();
return javaArray;
}
@Override
public void nullSafeSet(PreparedStatement statement, Object value, int index, SessionImplementor session)
throws HibernateException, SQLException {
Connection connection = statement.getConnection();
if (value == null) {
statement.setNull(index, SQL_TYPES[0]);
} else {
@SuppressWarnings("unchecked")
T castObject = (T) value;
Array array = connection.createArrayOf("integer", (Object[]) castObject);
statement.setArray(index, array);
}
}
@Override
public Object replace(Object original, Object target, Object owner) throws HibernateException {
return original;
}
@Override
public Class<T> returnedClass() {
return typeParameterClass;
}
@Override
public int[] sqlTypes() {
return new int[] { Types.ARRAY };
}
}
тогда свойства массива будут иметь тот же тип базы данных с тем же измерением:
-
integer[]
->Integer[]
-
text[][]
->String[][]
и в этом особом случае поставить GenericType
класс над свойствами
@Type(type = "packageofclass.GenericArrayUserType")
тогда ваша сущность будет:
@Entity
@Table(name="sal_emp")
public class SalEmp {
@Id
private String name;
@Column(name="pay_by_quarter")
@Type(type = "packageofclass.GenericArrayUserType")
private Integer[] payByQuarter;
@Column(name="schedule")
@Type(type = "packageofclass.GenericArrayUserType")
private String[][] schedule;
//Getters, Setters, ToString, equals, and so on
}
если вы не хотите использовать этот универсальный UserType
на Integer[]
введите и напишите String[][]
тип. Вам нужно написать свои собственные типы, в вашем случае будет как далее:
-
целое число[]
public class IntArrayUserType implements UserType { protected static final int[] SQL_TYPES = { Types.ARRAY }; @Override public Object assemble(Serializable cached, Object owner) throws HibernateException { return this.deepCopy(cached); } @Override public Object deepCopy(Object value) throws HibernateException { return value; } @Override public Serializable disassemble(Object value) throws HibernateException { return (Integer[]) this.deepCopy(value); } @Override public boolean equals(Object x, Object y) throws HibernateException { if (x == null) { return y == null; } return x.equals(y); } @Override public int hashCode(Object x) throws HibernateException { return x.hashCode(); } @Override public boolean isMutable() { return true; } @Override public Object nullSafeGet(ResultSet resultSet, String[] names, SessionImplementor session, Object owner) throws HibernateException, SQLException { if (resultSet.wasNull()) { return null; } if (resultSet.getArray(names[0]) == null) { return new Integer[0]; } Array array = resultSet.getArray(names[0]); Integer[] javaArray = (Integer[]) array.getArray(); return javaArray; } @Override public void nullSafeSet(PreparedStatement statement, Object value, int index, SessionImplementor session) throws HibernateException, SQLException { Connection connection = statement.getConnection(); if (value == null) { statement.setNull(index, SQL_TYPES[0]); } else { Integer[] castObject = (Integer[]) value; Array array = connection.createArrayOf("integer", castObject); statement.setArray(index, array); } } @Override public Object replace(Object original, Object target, Object owner) throws HibernateException { return original; } @Override public Class<Integer[]> returnedClass() { return Integer[].class; } @Override public int[] sqlTypes() { return new int[] { Types.ARRAY }; } }
-
текст[][]
public class StringMultidimensionalArrayType implements UserType { protected static final int[] SQL_TYPES = { Types.ARRAY }; @Override public Object assemble(Serializable cached, Object owner) throws HibernateException { return this.deepCopy(cached); } @Override public Object deepCopy(Object value) throws HibernateException { return value; } @Override public Serializable disassemble(Object value) throws HibernateException { return (String[][]) this.deepCopy(value); } @Override public boolean equals(Object x, Object y) throws HibernateException { if (x == null) { return y == null; } return x.equals(y); } @Override public int hashCode(Object x) throws HibernateException { return x.hashCode(); } @Override public boolean isMutable() { return true; } @Override public Object nullSafeGet(ResultSet resultSet, String[] names, SessionImplementor session, Object owner) throws HibernateException, SQLException { if (resultSet.wasNull()) { return null; } if (resultSet.getArray(names[0]) == null) { return new String[0][]; } Array array = resultSet.getArray(names[0]); String[][] javaArray = (String[][]) array.getArray(); return javaArray; } @Override public void nullSafeSet(PreparedStatement statement, Object value, int index, SessionImplementor session) throws HibernateException, SQLException { Connection connection = statement.getConnection(); if (value == null) { statement.setNull(index, SQL_TYPES[0]); } else { String[][] castObject = (String[][]) value; Array array = connection.createArrayOf("integer", castObject); statement.setArray(index, array); } } @Override public Object replace(Object original, Object target, Object owner) throws HibernateException { return original; } @Override public Class<String[][]> returnedClass() { return String[][].class; } @Override public int[] sqlTypes() { return new int[] { Types.ARRAY }; } }
в этом случае ваши свойства имеют разные типы:
@Column(name="pay_by_quarter")
@Type(type = "packageofclass.IntArrayUserType")
private Integer[] payByQuarter;
@Column(name="schedule")
@Type(type = "packageofclass.StringMultidimensionalArrayType")
private String[][] schedule;
Обновить Тип Пользователя Hibernate
С Boolean или boolean кажется, что он не работает с GenericArrayUserType
, поэтому решения могут быть созданы в вашем CREATE DDL
объявить boolean
типа bytea
:
CREATE TABLE sal_emp (
name text,
pay_by_quarter integer[],
schedule text[][],
wow_boolean bytea
);
и ваше имущество без какого-либо типа:
private boolean[][][] wowBoolean;
он разбирает очень хорошо, без каких-либо Type
или Converter
. Вывод:wowBoolean=[[[true, false], [true, false]], [[true, true], [true, true]]])
Обновить С Помощью @Converter
of JPA 2.1
я пробовал вариант с @Converter
JPA 2.1 с EclipseLink
и Hibernate
. Я пробовал integer[]
(не text[][]
) Converter
как это (*я изменил свойство на List<Integer>
но это не важно):
@Converter
public class ConverterListInteger implements AttributeConverter<List<Integer>, Array>{
@Override
public Array convertToDatabaseColumn(List<Integer> attribute) {
DataSource source = ApplicationContextHolder.getContext().getBean(DataSource.class);
try {
Connection conn = source.getConnection();
Array array = conn.createArrayOf("integer", attribute.toArray());
return array;
} catch (SQLException e) {
e.printStackTrace();
}
return null;
}
@Override
public List<Integer> convertToEntityAttribute(Array dbData) {
List<Integer> list = new ArrayList<>();
try {
for(Object object : (Object[]) dbData.getArray()){
list.add((Integer) object);
}
} catch (SQLException e) {
e.printStackTrace();
}
return list;
}
}
затем добавьте преобразователь в свойство в сущности:
@Convert(converter=ConverterListInteger.class)
private List<Integer> pay_by_quarter;
Итак, решение основано на JPA specification
не работает. Почему? Hibernate не поддерживает массивы баз данных (java.sql.Array
)....
затем я попытался с EclipseLink (см. Как настроить здесь) и он работает, но не всегда ...кажется, есть ошибка, она работает в первый раз хорошо, но затем в следующий раз невозможно обновить или запросить эту строку. Затем просто я успешно добавляю новые строки, но обновить или запросить их невозможно после....
вывод
на данный момент, кажется, не поддерживается JPA
поставщики должным образом... Только решение с Hibernate
UserType
работает хорошо, но это только для Hibernate
.