Как записать в SqlDescriptor с подготовленным оператором jdbc столбец tsrange? - PullRequest
0 голосов
/ 14 января 2019

Я следовал прекрасному руководству Как сопоставить массивы Java и SQL с JPA и Hibernate , чтобы отобразить специальный тип sql tsrange в hibernate. Я решил использовать дескрипторы Java и SQL, а не пользовательский тип, потому что обработка jdbc sql должна быть лучше.

Когда я пытаюсь сохранить сущность со столбцом с именем 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.

Насколько я понимаю, мне нужно написать специальный / нестандартный тип sql с помощью метода setObject и типа Type.OTHER или Type.JAVA_OBJECT. Какой предпочтительный способ поместить тип sql диапазона в PreparedStatement?

Источник BasicBinder, где я заполняю подготовленный оператор jdbc, 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;

1 Ответ

0 голосов
/ 14 января 2019

Я нашел решение. tsrange можно записать в виде строки "[2019-01-14 13:06:26, 2019-01-14 13:12:39]" в PreparedStatement st с st.setObject(index, sqlString, Type.OTHER);

Вот рабочий TsRangeSqlTypeDescriptor класс:

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);
            }
        };
    }

}

Одна уродливая часть - это первый doExtract метод. Это не входит в сферу моего вопроса, но я не мог привести rs.getObject(name) к PGobject. Я также поместил org.postgresql:postgresql:jar:42.2.5 в мои зависимости, но я получил странные предупреждения о загрузке классов, и приведение было невозможно. Но грязный хак с отражением работает. Я использую wildfy 14.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...