我按照优秀的指南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。

07-26 06:34