简体   繁体   English

如何将带有 jdbc 准备好的语句的 SqlDescriptor 写入 tsrange 列?

[英]How to write in a SqlDescriptor with a jdbc prepared statement into a tsrange column?

I followed the excellent guide How to map Java and SQL arrays with JPA and Hibernate to map the special sql type tsrange to hibernate.我按照优秀指南如何使用 JPA 和 Hibernate 映射 Java 和 SQL 数组将特殊的 sql 类型tsrange到 hibernate。 I decided to use Java and SQL descriptors and not a user type, because the jdbc sql handling should be better.我决定使用 Java 和 SQL 描述符而不是用户类型,因为 jdbc sql 处理应该更好。

When I try to persist an entity with a column named time range and the type tsrange, I got always: 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.当我尝试使用名为时间范围和类型 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.

My understanding is, that I need to write a special/non standard sql type with the setObject method and the type Type.OTHER or Type.JAVA_OBJECT.我的理解是,我需要使用 setObject 方法和类型 Type.OTHER 或 Type.JAVA_OBJECT 编写一个特殊/非标准的 sql 类型。 What is the prefered way, to put a range sql type into a PreparedStatement?将范围 sql 类型放入 PreparedStatement 的首选方法是什么?

The BasicBinder source, where I fill the jdbc prepared statement, sqlString contains "[2019-01-14 13:06:26, 2019-01-14 13:12:39]" : 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));
        }
    };
}

Here are the column definition of the entity:以下是实体的列定义:

@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;

The Hibernate Types project offers a PostgreSQLRangeType supports the PostgreSQL TSRANGE column type. Hibernate Types 项目提供了一个PostgreSQLRangeType支持 PostgreSQL TSRANGE列类型。

The first thing you need to do is to use the following Maven dependency:您需要做的第一件事是使用以下 Maven 依赖项:

<dependency>
    <groupId>com.vladmihalcea</groupId>
    <artifactId>hibernate-types-52</artifactId>
    <version>${hibernate-types.version}</version>
</dependency>

Afterward, you can map your PostgreSQL ranges like this:之后,您可以像这样映射 PostgreSQL 范围:

@Entity(name = "Book")
@Table(name = "book")
@TypeDef(
    typeClass = PostgreSQLRangeType.class,
    defaultForType = Range.class
)
public class Book {
 
    @Id
    @GeneratedValue
    private Long id;
 
    @NaturalId
    private String isbn;
 
    private String title;
 
    @Column(
        name = "price_cent_range",
        columnDefinition = "numrange"
    )
    private Range<BigDecimal> priceRange;
 
    @Column(
        name = "discount_date_range",
        columnDefinition = "daterange"
    )
    private Range<LocalDate> discountDateRange;
 
    //Getters and setters omitted for brevity
}

The Range and PostgreSQLRangeType classes are from the Hibernate Types project. RangePostgreSQLRangeType类来自 Hibernate Types 项目。

I found a solution.我找到了解决方案。 The tsrange can be written as string of the form "[2019-01-14 13:06:26, 2019-01-14 13:12:39]" into the PreparedStatement st with st.setObject(index, sqlString, Type.OTHER);tsrange可以写成以下形式的字符串"[2019-01-14 13:06:26, 2019-01-14 13:12:39]"进入PreparedStatement stst.setObject(index, sqlString, Type.OTHER);

Here is the working TsRangeSqlTypeDescriptor class:这是有效的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);
            }
        };
    }

}

One ugly part is the first doExtract method.一个丑陋的部分是第一个doExtract方法。 This is not in the scope of my question, but it was impossible for me to cast rs.getObject(name) to PGobject.这不在我的问题范围内,但我不可能将rs.getObject(name)为 PGobject。 I putted also org.postgresql:postgresql:jar:42.2.5 to my dependencies but I got strange classloading warnings and casting was impossible.我也将org.postgresql:postgresql:jar:42.2.5了我的依赖项中,但是我收到了奇怪的类加载警告并且无法进行转换。 But the dirty hack with reflection works.但是带有反射的肮脏黑客有效。 I use wildfy 14.我使用野化14。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM