PostgreSQL拋錯“不良的類型值: long”之解決


PostgreSQL拋錯“不良的類型值: long”之解決

一、前言

項目中有一個獨立程序,負責從主庫同步部分數據到分庫。由於混合使用了JPA和JDBC兩種操作方式,該程序移植到后PostgreSQL錯誤不斷且不好診斷,其中耗時耗力最多的就是:“org.postgresql.util.PSQLException: 不良的類型值 long ”。

二、原因分析

以下是PostgreSQL拋出例外處的日志片段:

Caused by: org.postgresql.util.PSQLException: 不良的類型值 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(BlobTypeDescriptor.java:48) 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(EntityReferenceInitializerImpl.java:305) ... 64 more

可以看出,這是在讀取BLOB(即BYTEA)類型數據時出的錯,具體是把想byte[]當作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()接口,分別將CLOB和BLOB按LongVarchar和LongVarBinary類型來處理,效果良好,解決了"column xxx is of type text but expression is of type bigint"的錯誤。

一開始以同樣的思路期待解決問題,但錯誤依然存在,讓人頭疼不已。后來想到,該獨立程序的某些操作在底層可能沒用到remapSqlTypeDescriptor()接口,最終仍按默認的oid + bigobject方式來調用setBlob(),因此還需重寫其它接口。但前前后后試了好幾天,還是沒有進展。

不得已分析hibernate-core源碼(https://github.com/hibernate/hibernate-orm/tree/master/hibernate-core),發現org.hibernate.type.descriptor.sql.BlobTypeDescriptor.java里有一段邏輯,大致是當某變量設置為BLOB_BIND時調用setBlob(),設置為PRIMARY_ARRAY_BINDING時調用setBytes(),設置為STREAM_BINDING時調用setBinaryStream()。CLOB的情況也類似。有戲!

  1 ----PostgreSQL81Dialect
  2 --注釋:  PostgreSQL81Dialect 該文件在 hibernate5.0以上的版本中
  3 <dependency>
  4     <groupId>org.hibernate</groupId>
  5     <artifactId>hibernate-core</artifactId>
  6     <version>5.2.2.Final</version>
  7 </dependency>
  8 
  9 
 10 


再回到org.hibernate.dialect.PostgreSQLxxDialect,經一層層追溯,終於在最底層的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;
  • 如果拋錯信息“不良的類型值”后是long,幾乎可斷定是LOB調用模式的問題;如果是其它類型,需進一步分析。
  • 如果直接用JDBC,對LOB的調用接口是setBinaryStream()、setCharacterStream()等。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM