我按照优秀的指南How to map Java and SQL arrays with JPA and Hibernate将特殊的sql类型tsrange
映射到hibernate。我决定使用Java和SQL描述符,而不是用户类型,因为jdbcsql处理应该更好。
当我试图持久化一个名为time range的列和tsrange类型的实体时,我总是得到:ERROR [org.hibernate.engine.jdbc.spi.SqlExceptionHelper] (default task-2) ERROR: column "time_range" is of type tsrange but expression is of type character varying Hinweis: You will need to rewrite or cast the expression.
我的理解是,我需要用setObject方法和type.OTHER或type.JAVA_对象编写一个特殊/非标准的sql类型。将range sql类型放入PreparedStatement的首选方法是什么?
在BasicBinder源代码中,我填充jdbc prepared语句,sqlString包含"[2019-01-14 13:06:26, 2019-01-14 13:12:39]"
:
@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 {
String sqlString = javaTypeDescriptor.toString(value);
// I tried the following statements:
//st.setObject(index, sqlString, getSqlType());
//st.setObject(index, sqlString);
//st.setString(index, sqlString+"::tsrange");
st.setString(index, sqlString);
}
@Override
protected void doBind(CallableStatement st, X value, String name, WrapperOptions options) throws SQLException {
st.setObject(name, javaTypeDescriptor.toString(value));
}
};
}
以下是实体的列定义:
@Id
@Column(name = "id")
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Basic
@Column(nullable = false, name = "time_range", columnDefinition = "tsrange")
@Type(type="com.example.model.types.TsRange")
private PgTsRange timeRange;
最佳答案
我找到了解决办法。tsrange
可以作为"[2019-01-14 13:06:26, 2019-01-14 13:12:39]"
格式的字符串写入PreparedStatement st
中
下面是workingst.setObject(index, sqlString, Type.OTHER);
类:
package com.example.galea.model.types;
import java.lang.reflect.InvocationTargetException;
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
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;
public class TsRangeSqlTypeDescriptor implements SqlTypeDescriptor {
/** */
private static final long serialVersionUID = -4377165492827156136L;
private static final Log log = LogFactory.getLog(TsRangeSqlTypeDescriptor.class);
public static final TsRangeSqlTypeDescriptor INSTANCE = new TsRangeSqlTypeDescriptor();
@Override
public int getSqlType() {
return Types.OTHER; // <--- This is importand!
}
@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 {
String sqlString = javaTypeDescriptor.toString(value);
// Here is the solution with Type.OTHER
st.setObject(index, sqlString, getSqlType());
}
@Override
protected void doBind(CallableStatement st, X value, String name, WrapperOptions options) throws SQLException {
st.setObject(name, javaTypeDescriptor.toString(value));
}
};
}
@Override
public <X> ValueExtractor<X> getExtractor(JavaTypeDescriptor<X> javaTypeDescriptor) {
return new BasicExtractor<X>(javaTypeDescriptor, this) {
@Override
protected X doExtract(ResultSet rs, String name, WrapperOptions options) throws SQLException {
if(javaTypeDescriptor instanceof TsRangeJavaTypeDescriptor) {
TsRangeJavaTypeDescriptor rangeJavaTypeDescriptor = (TsRangeJavaTypeDescriptor) javaTypeDescriptor;
Object pgObject = rs.getObject(name);
Object valueRaw;
// Ugly, but I can not cast PGobject
try {
valueRaw = pgObject.getClass().getMethod(getValue, null).invoke(pgObject);
if(valueRaw instanceof String) {
String value = (String) valueRaw;
return (X) rangeJavaTypeDescriptor.wrap(value, options);
}
} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException | NoSuchMethodException | SecurityException e) {
log.error(Failed to parse pgObject,e);
}
}
return javaTypeDescriptor.wrap(rs.getObject(name), options);
}
@Override
protected X doExtract(CallableStatement statement, int index, WrapperOptions options) throws SQLException {
return javaTypeDescriptor.wrap(statement.getObject(index), options);
}
@Override
protected X doExtract(CallableStatement statement, String name, WrapperOptions options) throws SQLException {
return javaTypeDescriptor.wrap(statement.getObject(name), options);
}
};
}
}
一个难看的部分是第一个
TsRangeSqlTypeDescriptor
方法。这不在我的问题范围内,但我不可能将doExtract
转换为PGobject。我也把rs.getObject(name)
放到了依赖项中,但我收到了奇怪的类加载警告,无法进行强制转换。但是,反射的肮脏黑客是有效的。我用wildfy 14。