Code前端首页关于Code前端联系我们

PostgreSQL 抛出错误“bad type value: long” 原因分析及解决方案

terry 2年前 (2023-09-26) 阅读数 49 #数据库

项目中有一个独立的程序,负责将主库的部分数据同步到分支数据库。由于JPA和JDBC的混合使用,导致程序移植后PostgreSQL错误依然存在且难以诊断。其中,费时费力的是:“:bad type value long”。

原因分析

以下是PostgreSQL抛出异常的日志片段:

Caused by: : 不良的类型值 long : \x0040010346504d4e00000001000003900101000000000000000002800000028001f4007d000202040000000200000000000000000000000000000000000000005041
        at org.postgresql.jdbc.PgResultSet.toLong(PgResultSet.java:2860)
        at org.postgresql.jdbc.PgResultSet.getLong(PgResultSet.java:2114)
        at org.postgresql.jdbc.PgResultSet.getBlob(PgResultSet.java:418)
        at org.postgresql.jdbc.PgResultSet.getBlob(PgResultSet.java:405)
        at org.apache.commons.dbcp.DelegatingResultSet.getBlob(DelegatingResultSet.java:565)
        at org.apache.commons.dbcp.DelegatingResultSet.getBlob(DelegatingResultSet.java:565)
        at org.hibernate.type.descriptor.sql.BlobTypeDescriptor$1.doExtract()
        at org.hibernate.type.descriptor.sql.BasicExtractor.extract(BasicExtractor.java:47)
        at org.hibernate.type.AbstractStandardBasicType.nullSafeGet(AbstractStandardBasicType.java:258)
        at org.hibernate.type.AbstractStandardBasicType.nullSafeGet(AbstractStandardBasicType.java:254)
        at org.hibernate.type.AbstractStandardBasicType.nullSafeGet(AbstractStandardBasicType.java:244)
        at org.hibernate.type.AbstractStandardBasicType.hydrate(AbstractStandardBasicType.java:327)
        at org.hibernate.persister.entity.AbstractEntityPersister.hydrate(AbstractEntityPersister.java:2775)
        at org.hibernate.loader.plan.exec.process.internal.EntityReferenceInitializerImpl.loadFromResultSet()
        ... 64 more

可以看出这是在读取BLOB(即BYTEA)类型数据时出错,具体是 byte[] Read as long 。

在另一篇文章《JPA/Hibernate移植到PostgreSQL时关于CLOB, BLOB及JSON类型的处理》中他解释了PostgreSQL处理LOB数据的两种方式:oid + bigobject方法和二进制数组方法。 oid + bigobject方法访问LOB字段中的oid(BIGINT类型)值,并将实际的byte[]数据存储在公共pg_largeobject中。 PostgreSQL的JDBC中的接口有setBlob()/getBlob()、setClob()/getClob();而二进制数组方法直接访问byte[],JDBC中的接口有setBinaryStream()、setCharacterStream()等。独立程序从主库读取LOB数据(二进制数组模式)时,仍然以oid+bigobject模式进行,导致错误。

解决方案

在那篇文章中,解决方案也是重写PostgreSQL94Dialect的RemapSqlTypeDescriptor()接口,并分别根据LongVarchar和LongVarBinary类型处理CLOB和BLOB。效果不错,解决了“xxx列是text类型但表达式是bigint类型”错误的问题。

一开始我以为能用同样的思路解决问题,但是错误依然存在,引起了很多头痛。后来想到这个独立程序的一些操作不能使用下层的RemapSqlTypeDescriptor()接口。最终还是默认的oid+bigobject方法中调用了setBlob(),所以其他接口都得重写。但尝试了几天,仍然没有任何进展。

无奈,我分析了hibernate内核源码(),发现里面有一段逻辑。大致来说,如果变量设置为BLOB_BIND,则调用setBlob(),如果设置为PRIMARY_ARRAY_BINDING,则调用setBytes(),如果设置为STREAM_BINDING,则调用setBinaryStream()。 CLOB 的情况类似。有一个梦想!

再次回来,一层层拉回来后,终于在下层PostgreSQL81Dialect(隐藏太深)的getSqlTypeDescriptorOverride()接口中找到了对应的内容,发现这里定义了标准的oid + bigobject方法。于是重写了界面:

    @Override
    public SqlTypeDescriptor getSqlTypeDescriptorOverride(int sqlCode)
    {
        SqlTypeDescriptor descriptor;
        switch (sqlCode)
        {
        case Types.BLOB:
            // Force BLOB binding. Otherwise, byte[] fields annotated
            // with @Lob will attempt to use
            // BlobTypeDescriptor.PRIMITIVE_ARRAY_BINDING. Since the
            // dialect uses oid for Blobs, byte arrays cannot be used.
            //descriptor = BlobTypeDescriptor.BLOB_BINDING;
            descriptor = BlobTypeDescriptor.STREAM_BINDING;
            break;
        case Types.CLOB:
            //descriptor = ClobTypeDescriptor.CLOB_BINDING;
            descriptor = ClobTypeDescriptor.STREAM_BINDING;
            break;
        default:
            descriptor = super.getSqlTypeDescriptorOverride(sqlCode);
            break;
        }
        return descriptor;
    }

问题终于解决了!

PS:

  • 建议使用二进制数组的方式来访问LOB,而不是默认的oid + bigobject;
  • 如果错误信息“Bad type value”后面带有long,几乎可以断定是LOB调用模式。问题;如果还有其他类型,还需要进一步分析。
  • 直接使用JDBC时,LOB的调用接口有setBinaryStream()、setCharacterStream()等。

版权声明

本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。

发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

热门