JPA移植到PostgreSQL時關於CLOB, BLOB及JSON類型的處理

來源:互聯網
上載者:User

標籤:get   原因   post   問題   extends   java   rac   針對   sig   

一、綜述

目前的項目最初基於Oracle開發,現在要移植到PostgreSQL。鑒於已經使用JPA來實現對象的持久化,領導總以為遷移任務很easy,但實際過程中還是出現了很多問題。

這其中有一些問題是定義EJB時不規範引起的,如把Number(1)映射為boolean、Number(n)映射為String、Date映射為String等等。因為Oracle擁有強大的自動類型轉換能力,只要資料符合格式,Oracle不會報錯;一旦移植到PostgreSQL環境,各種類型不符的Exception拋來拋去。不過只要按規範一一修正過來,這些問題還是容易解決的。

另外幾個問題就很令人頭疼,特別是關於CLOB, BLOB及JSON類型的處理。移植到PostgreSQL時,絕不是簡單地將CLOB替換為TEXT、BLOB替換為BYTEA、Varchar2(...) CONSTRAINT ... CHECK (... IS JSON) 替換為JSON後,餘下的交給JPA就能搞定。接下來的麻煩得自己去一一去解決。

本文即是根據搜尋到的資料,加上自己操作過程中的經驗,進行一些實踐上的總結。

二、CLOB和BLOB的處理1 現象

對於CLOB(PostgreSQL對應的是TEXT,後文不作區分)類型,寫入時不拋例外,但實際上儲存的是一個數值,而不是byte[]內容;讀取時,部分行正確,部分行拋例外:column xxx is of type text but expression is of type bigint ...;對於BLOB(對應的是BYTEA,不作區分)類型,乾脆寫入時就報錯:column xxx is type of bytea but expression is type of bigint ...。

2 PostgreSQL處理LOB資料的兩種方式

要解釋原因,首先需知道PostgreSQL處理LOB資料的兩種方式:oid + largeobject 和byte[],詳細說明參閱:

參考資料1:https://jdbc.postgresql.org/documentation/80/binary-data.html

很明顯,JPA把期望的位元組方式當作oid+largeobject方式傳遞給了PostgreSQL,於是當遇到寫入CLOB或BLOB時,相應欄位存入的實際上是oid的值(BigInt類型),而byte[]的值則被寫入到公用的pg_largeobject表。區別在於,BigInt類型的oid自動轉換到TEXT時成功了,轉換到BYTEA時失敗。

為驗證這一說法,用已經存入TEXT欄位的數值去pg_largeobject查詢,確實是期望的byte[]的值,這也是JPA讀取時有些能成功的原因;至於不成功的那些記錄,猜測可能與位元組數有關,因為位元組數超過1M的都成功而在K層級的都失敗(臨界值未知)。鑒於篇幅,這些內容不展開,有興趣者請自行驗證。

oid + largeobject 方式除了效能上有些優勢外,至少有三個缺點:1 公用的pg_largeobject存在許可權問題;2 pg_largeobject的相應記錄不會隨源記錄刪除而自動刪除;3 對事務有較嚴格限制。因此並不符合項目要求,但為什麼JPA總是按oid + largeobject方式來處理?

3 Hibernate與PostgreSQL的不統一之處

以BYTEA為例,PostgreSQL的兩種處理方式是通過分別調用JDBC的setBinaryStream()和setBlob()介面來實現的。期望的邏輯應該是Hibernate能針對PostgreSQL的這個特點來正確區分、正確調用,但不幸的是:Hibernate以為所有資料庫都是調用setBinaryStream()來寫入BYTEA,出於某種原因並不打算照顧PostgreSQL的特殊情況(貌似一段時間內不會改觀),於是前面提到的錯誤現象發生了。

詳細的解釋請參閱:

參考資料2:http://www.codeweblog.com/postgresql-hibernate-on-bytea-and-oid-mapping-problem/

 至於TEXT,情況大致類似,只是調用的是另外兩個JDBC介面,不再展開。

4 解決辦法

還是在參考資料2,提出兩種解決針對BYTEA的解決思路:

  1. 在定義EJB時,將blob類型改為byte[];
  2. 重載Hibernate中的PostgresDialect類的useInputStreamToInsertBlob()方法。

經實際測試,兩種思路均不甚成功,可能是與版本差異和環境差異有關,還需要修改一些其它因素才行。未繼續深入研究,部分原因是因為時間緊迫,部分原因是在解決TEXT時順帶解決了(見下)。

對於TEXT,資料3提出三種解決思路:

參考資料3:https://stackoverflow.com/questions/28588311/correct-jpa-annotation-for-postgresqls-text-type-without-hibernate-annotations

  1. 定義EJB時,取消@Lob標註,按String對待;
  2. 定義EJB時,保留@Lob標註,增加 @Type(type = "org.hibernate.type.TextType")標註;
  3. 不修改EJB,重載PostgresDialect類remapSqlTypeDescriptor()方法,將CLOB當longvarchar處理。

經實際測試,三種思路均可達到目的。由於上級領導不贊成修改標註的方式(理由是與Oracle環境的版本不一致),遂採用思路3,順帶著將BLOB按longVarBinary處理。

因項目中PostgreSQL是9.4版,故選擇從PostgreSQL94Dialect繼承,一般情況下可選擇PostgreSQL9Dialect。代碼為:

package com.xxx.pgdialect;import java.sql.Types;import org.hibernate.dialect.PostgreSQL94Dialect;import org.hibernate.type.descriptor.sql.LongVarbinaryTypeDescriptor;import org.hibernate.type.descriptor.sql.LongVarcharTypeDescriptor;import org.hibernate.type.descriptor.sql.SqlTypeDescriptor;public class PgDialect extends PostgreSQL94Dialect{    @Override    public SqlTypeDescriptor remapSqlTypeDescriptor(SqlTypeDescriptor sqlTypeDescriptor)    {        switch (sqlTypeDescriptor.getSqlType())        {        case Types.CLOB:            return LongVarcharTypeDescriptor.INSTANCE;        case Types.BLOB:            return LongVarbinaryTypeDescriptor.INSTANCE;        }        return super.remapSqlTypeDescriptor(sqlTypeDescriptor);    }}

然後在persistence.xml中用這個類(com.xxx.pgdialect.PgDialect)替換PostgreSQL94Dialect即可。

 三、JSON的處理

 JSON類型的特殊之處在於:首先Oracle實際上是按特殊的varchar2或clob來對待;其次是Hibernate及JDBC都沒有定義json類型。因此,在遇到“column xxx is type of json but expression is type of character varying ...”例外時,不能簡單地照搬前述方法。

經Google,發現一篇很有價值的資料:

參考資料4:https://stackoverflow.com/questions/15974474/mapping-postgresql-json-column-to-hibernate-value-type

其中給出了很多種解決思路,現簡單總結如下:

  1. 定義PostgreSql表結構時,將JSON改為TEXT,即仿照Oracle的做法;
  2. 擴充Hibernate中的Type,增加關於json的自訂類型,同時增加(或重載)處理JSON的相應方法;
  3. 更換JDBC驅動為pgjdbc-ng,它提供了可以處理JSON與TEXT轉換的@Conveter標註;
  4. 在PostgreSql資料庫,建立隱式或顯式的類型轉換方法或函數,使得PostgreSQL接受JSON與TEXT的自動轉換。

思路1需要應用程式保證資料符合json規範,風險較大,被否決。思路2有很多種具體實現方式(有興趣者自行鑽研),但其共同點都是需要修改EJB標註,被領導否決。思路3過於依賴某一產品,且跟2一樣也要修改標註,也被否決。只剩下思路4,而事實上它也確實是最簡便的方式。

在psql命令列,簡單建立TEXT與JSON、Varchar與JSON互相轉換的四個CAST即可:

CREATE CAST (text AS json)  WITH INOUT  AS ASSIGNMENT;CREATE CAST (json AS text)  WITH INOUT  AS ASSIGNMENT;CREATE CAST (varchar AS json)  WITH INOUT  AS ASSIGNMENT;CREATE CAST (json AS varchar)  WITH INOUT  AS ASSIGNMENT;

執行之後,再無“column xxx is type of json but expression is type of character varying ...”例外。

進一步猜測,XML類型也可以按類似方法來處理。

四、總結
  • 定義EJB時一定要規範,可以避免大多數簡單的類型不符錯誤;
  • 對於CLOB和BLOB,把它們按LongVarchar和LongVarBinary處理;
  • 對於JSON,增加隱式或顯式的類型轉換方法。

JPA移植到PostgreSQL時關於CLOB, BLOB及JSON類型的處理

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.