PostgreSQL抛错“不良的类型值: long”原因分析和解决办法

2019-03-2821:04:58数据库教程1 16,770 views1字数 3228阅读模式

项目中有一个独立程序,负责从主库同步部分数据到分库。由于混合使用了JPA和JDBC两种操作方式,该程序移植到后PostgreSQL错误不断且不好诊断,其中耗时耗力最多的就是:“: 不良的类型值 long ”。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/sjk/10529.html

原因分析

以下是PostgreSQL抛出例外处的日志片段:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/sjk/10529.html

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[]当作long来读取。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/sjk/10529.html

在另一篇随笔《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()等。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/sjk/10529.html

至此原因已经基本明朗,该独立程序在读取主库的LOB数据(二进制数组方式)时,仍然按oid + bigobject方式进行,由此导致出错。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/sjk/10529.html

解决方法

也在那篇随笔中,解决方法是重写PostgreSQL94Dialect的remapSqlTypeDescriptor()接口,分别将CLOB和BLOB按LongVarchar和LongVarBinary类型来处理,效果良好,解决了"column xxx is of type text but expression is of type bigint"的错误。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/sjk/10529.html

一开始以同样的思路期待解决问题,但错误依然存在,让人头疼不已。后来想到,该独立程序的某些操作在底层可能没用到remapSqlTypeDescriptor()接口,最终仍按默认的oid + bigobject方式来调用setBlob(),因此还需重写其它接口。但前前后后试了好几天,还是没有进展。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/sjk/10529.html

不得已分析hibernate-core源码(),发现里有一段逻辑,大致是当某变量设置为BLOB_BIND时调用setBlob(),设置为PRIMARY_ARRAY_BINDING时调用setBytes(),设置为STREAM_BINDING时调用setBinaryStream()。CLOB的情况也类似。有戏!文章源自菜鸟学院-https://www.cainiaoxueyuan.com/sjk/10529.html

再回到,经一层层追溯,终于在最底层的PostgreSQL81Dialect(藏的太深了),在getSqlTypeDescriptorOverride()接口中找到对应的内容,而且发现默认的oid + bigobject方式是在此定义的。于是重写该接口:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/sjk/10529.html

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

问题终于得到解决!文章源自菜鸟学院-https://www.cainiaoxueyuan.com/sjk/10529.html

PS:

  • 推荐使用二进制数组方式存取LOB,而不是默认的oid + bigobject;
  • 如果抛错信息“不良的类型值”后是long,几乎可断定是LOB调用模式的问题;如果是其它类型,需进一步分析。
  • 如果直接用JDBC,对LOB的调用接口是setBinaryStream()、setCharacterStream()等。
文章源自菜鸟学院-https://www.cainiaoxueyuan.com/sjk/10529.html
  • 本站内容整理自互联网,仅提供信息存储空间服务,以方便学习之用。如对文章、图片、字体等版权有疑问,请在下方留言,管理员看到后,将第一时间进行处理。
  • 转载请务必保留本文链接:https://www.cainiaoxueyuan.com/sjk/10529.html
Comments  1  访客  1
    • Yoyo 0

      你好,請問我該如何重寫 getSqlTypeDescriptorOverride 接口? 是要直接改 hibernate source code 然後重新編譯 hibernate-core…jar 嗎? 還是可以在我的 project 裏面直接 implement 之類的,麻煩版主指導一下,謝謝

    Comment

    匿名网友 填写信息

    :?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

    确定