annotazione di ibernazione corretta per byte []


120

Ho un'applicazione che utilizza hibernate 3.1 e annotazioni JPA. Ha alcuni oggetti con attributi byte [] (dimensioni da 1k a 200k). Utilizza l'annotazione JPA @Lob e hibernate 3.1 può leggerli bene su tutti i principali database - sembra nascondere le peculiarità del fornitore JDBC Blob (come dovrebbe fare).

@Entity
public class ConfigAttribute {
  @Lob
  public byte[] getValueBuffer() {
    return m_valueBuffer;
  }
}

Abbiamo dovuto aggiornare a 3.5, quando abbiamo scoperto che ibernazione 3.5 interrompe (e non risolverà) questa combinazione di annotazioni in postgresql (senza soluzione alternativa). Finora non ho trovato una soluzione chiara, ma ho notato che se rimuovo @Lob, viene utilizzato il tipo postgresql bytea (che funziona, ma solo su postgres).

annotation                   postgres     oracle      works on
-------------------------------------------------------------
byte[] + @Lob                oid          blob        oracle
byte[]                       bytea        raw(255)    postgresql
byte[] + @Type(PBA)          oid          blob        oracle
byte[] + @Type(BT)           bytea        blob        postgresql

once you use @Type, @Lob seems to not be relevant
note: oracle seems to have deprecated the "raw" type since 8i.

Sto cercando un modo per avere una singola classe annotata (con una proprietà blob) che sia portabile tra i principali database.

  • Qual è il modo portabile per annotare una proprietà byte []?
  • È stato risolto in qualche versione recente di ibernazione?

Aggiornamento: dopo aver letto questo blog ho finalmente capito quale fosse la soluzione originale nel problema JIRA: apparentemente dovresti eliminare @Lob e annotare la proprietà come:

@Type(type="org.hibernate.type.PrimitiveByteArrayBlobType") 
byte[] getValueBuffer() {...

Tuttavia, questo non funziona per me - ottengo ancora OID invece di bytea; tuttavia ha funzionato per l'autore del numero di JIRA, che sembrava volere oid.

Dopo la risposta di A. Garcia, ho quindi provato questa combinazione, che in realtà funziona su postgresql, ma non su Oracle.

@Type(type="org.hibernate.type.BinaryType") 
byte[] getValueBuffer() {...

Quello che ho davvero bisogno di fare è controllare quale @ org.hibernate.annotations. Digita la combinazione (@Lob + byte [] viene mappata) su (su postgresql).


Di seguito è riportato lo snippet di 3.5.5.Final da MaterializedBlobType (sql tipo Blob). Secondo il blog di Steve, postgresql vuole che tu usi Streams per bytea (non chiedermi perché) e il tipo Blob personalizzato di postgresql per oids. Nota anche che l'uso di setBytes () su JDBC è anche per bytea (dall'esperienza passata). Quindi questo spiega perché use-stream non ha alcun effetto che entrambi assumono "bytea".

public void set(PreparedStatement st, Object value, int index) {
 byte[] internalValue = toInternalFormat( value );
 if ( Environment.useStreamsForBinary() ) {
  // use streams = true
   st.setBinaryStream( index, 
    new ByteArrayInputStream( internalValue ), internalValue.length );
 }
 else {
  // use streams = false
  st.setBytes( index, internalValue );
 }
}

Questo risulta in:

ERROR: column "signature" is of type oid but expression is of type bytea

Aggiorna La prossima domanda logica è: "perché non modificare manualmente le definizioni della tabella in bytea" e mantenere (@Lob + byte [])? Questo funziona, FINO a quando non si tenta di memorizzare un byte nullo []. Che il driver postgreSQL pensa sia un'espressione di tipo OID e il tipo di colonna è bytea - questo perché hibernate (giustamente) chiama JDBC.setNull () invece di JDBC.setBytes (null) che il driver PG si aspetta.

ERROR: column "signature" is of type bytea but expression is of type oid

Il sistema di tipi in ibernazione è attualmente un "work in progress" (secondo il commento di deprecazione 3.5.5). In effetti gran parte del codice 3.5.5 è deprecato, è difficile sapere cosa guardare quando si sottoclasse PostgreSQLDialect).

AFAKT, Types.BLOB / 'oid' su postgresql dovrebbe essere mappato su un tipo personalizzato che utilizza l'accesso JDBC in stile OID (cioè l'oggetto PostgresqlBlobType e NOT MaterializedBlobType). In realtà non ho mai usato con successo Blob con postgresql, ma so che bytea funziona semplicemente come uno / mi aspetterei.

Al momento sto esaminando BatchUpdateException: è possibile che il driver non supporti il ​​batch.


Ottima citazione del 2004: "Per riassumere le mie divagazioni, direi che dovremmo aspettare che il driver JDBC esegua correttamente i LOB prima di cambiare Hibernate."

Riferimenti:


Questo sembra essere stato risolto nella 3.6, non sono sicuro della 3.5.6; la classe MaterializedBlobType è stata completamente riscritta da 3.5.5> 3.6. Il tipo OID ora funziona poiché hanno modificato l'implementazione.
Justin,

Bello! Mi chiedo quale problema Jira stia monitorando questa riscrittura, se presente (forse la riscrittura è una conseguenza di un cambiamento più profondo). Sarebbe bello eseguire il backport delle modifiche nella 3.5, se possibile. Cattive notizie se non è possibile.
Pascal Thivent

In realtà il mio test mi ha dato un falso positivo la prima volta (sapevo che avrei dovuto aspettare!) - Non è ancora stato risolto, il bug è appena stato spostato in BlobTypeDescriptor.
Justin

Grazie. @Type (type = "org.hibernate.type.BinaryType") ha funzionato per me per una tabella che memorizza i file PDF. Ho migrato un database da Oracle a Postgres utilizzando Oracle-To-PostgreSQL da Intelligent Converters, e automaticamente convertito e inserito da BLOB a BYTEA ma BlobType non ha funzionato per me.
jmoran

Risposte:


68

Qual è il modo portabile per annotare una proprietà byte []?

Dipende da cosa vuoi. JPA può persistere in un file non annotato byte[]. Dalle specifiche JPA 2.0:

11.1.6 Annotazione di base

L' Basicannotazione è il tipo più semplice di mappatura a una colonna del database. L' Basicannotazione può essere applicato a una variabile proprietà o un'istanza persistente di uno dei seguenti tipi: Java primitivi, tipi, confezioni dei tipi primitivi, java.lang.String, java.math.BigInteger, java.math.BigDecimal, java.util.Date, java.util.Calendar, java.sql.Date, java.sql.Time, java.sql.Timestamp, byte[],Byte[] , char[], Character[], enumerazioni e ogni altro tipo che implementa Serializable. Come descritto nella Sezione 2.8, l'uso Basicdell'annotazione è opzionale per i campi persistenti e le proprietà di questi tipi. Se l'annotazione di base non è specificata per tale campo o proprietà, verranno applicati i valori predefiniti dell'annotazione di base.

E Hibernate mapperà un it "per impostazione predefinita" a un SQL VARBINARY(o un SQL a LONGVARBINARYseconda delle Columndimensioni?) Che PostgreSQL gestisce con unbytea .

Ma se vuoi byte[]che venga memorizzato in un oggetto grande, dovresti usare un file @Lob. Dalle specifiche:

11.1.24 Annotazione Lob

A Lobspecifica annotazione che una proprietà persistente o campo deve essere mantenuta come un grande oggetto ad un grande tipo di oggetto di database supportato. Le applicazioni portatili dovrebbero utilizzare l' Lobannotazione durante la mappatura a un Lobtipo di database . L' Lobannotazione può essere utilizzata in combinazione con l'annotazione di base o con l' ElementCollectionannotazione quando il valore della raccolta di elementi è di tipo base. UNLob può essere un tipo binario o di carattere. Il Lobtipo viene dedotto dal tipo del campo o della proprietà persistente e, ad eccezione dei tipi di stringa e di carattere, il valore predefinito è BLOB.

E Hibernate lo mapperà su un SQL BLOB gestito da PostgreSQL con estensione oid .

È stato risolto in qualche versione recente di ibernazione?

Ebbene, il problema è che non so quale sia esattamente il problema. Ma posso almeno dire che nulla è cambiato dalla 3.5.0-Beta-2 (che è dove è stata introdotta una modifica) nel ramo 3.5.x.

Ma la mia comprensione di problemi come HHH-4876 , HHH-4617 e di PostgreSQL e BLOB (menzionati nel javadoc di PostgreSQLDialect) è che dovresti impostare la seguente proprietà

hibernate.jdbc.use_streams_for_binary=false

se vuoi usare oidie byte[]con @Lob(che è la mia comprensione poichéVARBINARY non è quello che vuoi con Oracle). Hai provato questo?

In alternativa, HHH-4876 suggerisce di utilizzare il deprecatoPrimitiveByteArrayBlobType per ottenere il vecchio comportamento (precedente a Hibernate 3.5).

Riferimenti

  • Specifiche JPA 2.0
    • Sezione 2.8 "Mappatura dei valori predefiniti per campi o proprietà non relazionali"
    • Sezione 11.1.6 "Annotazione di base"
    • Sezione 11.1.24 "Annotazione Lob"

risorse


OMG, mi rendo conto che questa domanda è cambiata molto da quando ho iniziato a rispondere. Leggerà tutte le modifiche più tardi e aggiornerà le mie risposte dopo aver digerito le modifiche, se necessario.
Pascal Thivent

È bello vedere le specifiche, quindi hibernate è totalmente corretto mappare (@Lob + byte []) su un tipo supportato da oggetti di grandi dimensioni. In Postgresql ce ne sono 2 (bytea o oid). Tuttavia, mentre l'ibernazione 3.5 mappa a oid (per impostazione predefinita) legge utilizzando JDBC getBytes (), il cui driver PGSQL restituisce l'oid a 6 byte invece dei dati. Nota anche che l'autore del blog ha risposto in modo molto utile (sul suo blog) da quando è stata posta la domanda.
Justin

@Justin Tuttavia, mentre hibernate 3.5 mappa a oid (per impostazione predefinita) legge utilizzando JDBC getBytes () quale driver PGSQL restituisce l'oid a 6 byte invece dei dati - si verifica anche quando lo si utilizza hibernate.jdbc.use_streams_for_binary=false? (andando a controllare cosa ha detto Steve ora).
Pascal Thivent

Proverò a specificarlo nel file delle proprietà, tuttavia PostgreSQLDialect ha useInputStreamToInsertBlob () che restituisce false, quindi presumo che lo sia, poiché non sto impostando esplicitamente questa proprietà.
Justin

Dopo aver impostato questa proprietà (su true o false), ottengo un'eccezione di runtime: ERRORE: la colonna "signature" è di tipo bytea ma l'espressione è di tipo oid ". Devo menzionare che sto usando hibernate 3.5.5.Final + PG 8.2 piloti
Justin

10

Ecco cosa dice O'reilly Enterprise JavaBeans, 3.0

JDBC ha tipi speciali per questi oggetti molto grandi. Il tipo java.sql.Blob rappresenta i dati binari e java.sql.Clob rappresenta i dati dei caratteri.

Ecco il codice sorgente di PostgreSQLDialect

public PostgreSQLDialect() {
    super();
    ...
    registerColumnType(Types.VARBINARY, "bytea");
    /**
      * Notice it maps java.sql.Types.BLOB as oid
      */
    registerColumnType(Types.BLOB, "oid");
}

Allora cosa puoi fare

Eseguire l'override di PostgreSQLDialect come segue

public class CustomPostgreSQLDialect extends PostgreSQLDialect {

    public CustomPostgreSQLDialect() {
        super();

        registerColumnType(Types.BLOB, "bytea");
    }
}

Ora definisci il tuo dialetto personalizzato

<property name="hibernate.dialect" value="br.com.ar.dialect.CustomPostgreSQLDialect"/>

E usa la tua annotazione JPA @Lob portatile

@Lob
public byte[] getValueBuffer() {

AGGIORNARE

Qui è stato estratto qui

Ho un'applicazione in esecuzione in ibernazione 3.3.2 e le applicazioni funzionano bene , con tutti i campi blob che usano oid (byte [] in java)

...

Migrando a Hibernate 3.5 tutti i campi BLOB non funzionano più e il log del server mostra: ERROR org.hibernate.util.JDBCExceptionReporter - ERRORE: la colonna è di tipo oid ma l'espressione è di tipo bytea

che può essere spiegato qui

Questo in generale non è un bug in PG JDBC , ma una modifica dell'implementazione predefinita di Hibernate nella versione 3.5 . Nella mia situazione l' impostazione della proprietà compatibile sulla connessione non ha aiutato .

...

Molto di più quello che ho visto in 3.5 - beta 2, e non so se è stato risolto è Hibernate - senza annotazione @Type - creerà automaticamente una colonna di tipo oid, ma proverà a leggerlo come bytea

È interessante perché quando mappa Types.BOLB come bytea (vedere CustomPostgreSQLDialect)

Impossibile eseguire l'aggiornamento batch JDBC

durante l'inserimento o l'aggiornamento


Questa soluzione sembra gloriosa, la sto provando ora.
Justin

Questo genera il DDL corretto, ma non riesce in fase di esecuzione: ottengo un'eccezione java.sql.BatchUpdateException quando provo a un oggetto con una proprietà BLOB.
Justin

@Justin Prova uno scenario simile usando Oracle invece di PostgreSQL e guarda cosa ottieni. BatchUpdateException ha a che fare con errori che si verificano durante un'operazione di aggiornamento batch.
Arthur Ronald

In realtà quello che voglio veramente non è mappare BLOB a "bytea" ma mappare invece la combinazione di annotazioni (byte [] + @Lob) su Types.VARBINARY!
Justin


7

Sto usando Hibernate 4.2.7.SP1 con Postgres 9.3 e per me funziona:

@Entity
public class ConfigAttribute {
  @Lob
  public byte[] getValueBuffer() {
    return m_valueBuffer;
  }
}

poiché Oracle non ha problemi con questo, e per Postgres sto usando un dialetto personalizzato:

public class PostgreSQLDialectCustom extends PostgreSQL82Dialect {

    @Override
    public SqlTypeDescriptor remapSqlTypeDescriptor(SqlTypeDescriptor sqlTypeDescriptor) {
    if (sqlTypeDescriptor.getSqlType() == java.sql.Types.BLOB) {
      return BinaryTypeDescriptor.INSTANCE;
    }
    return super.remapSqlTypeDescriptor(sqlTypeDescriptor);
  }
}

il vantaggio di questa soluzione lo considero, che posso mantenere intatti i vasi ibernati.

Per ulteriori problemi di compatibilità Postgres / Oracle con Hibernate, vedere il mio post sul blog .


2
Ha funzionato per me usando Hibernate 4.3.6 e Postgresql 9.3 con l'estensione Postgresql9Dialect. Grazie!
Andrés Oviedo

Funziona con Hibernate 5.3.7.Final e Postgres95Dialect. Thx
Bernhard Kern

6

Finalmente ho funzionato. Si espande sulla soluzione di A. Garcia, tuttavia, poiché il problema risiede nel tipo di ibernazione MaterializedBlob, ma la mappatura di Blob> bytea non è sufficiente, abbiamo bisogno di un sostituto per MaterializedBlobType che funziona con il supporto per i blob interrotti di ibernazione. Questa implementazione funziona solo con bytea, ma forse il tizio del problema JIRA che voleva OID potrebbe contribuire a un'implementazione OID.

Purtroppo sostituire questi tipi in fase di esecuzione è un problema, poiché dovrebbero far parte del dialetto. Se solo questo miglioramento JIRA entrasse in 3.6 sarebbe possibile.

public class PostgresqlMateralizedBlobType extends AbstractSingleColumnStandardBasicType<byte[]> {
 public static final PostgresqlMateralizedBlobType INSTANCE = new PostgresqlMateralizedBlobType();

 public PostgresqlMateralizedBlobType() {
  super( PostgresqlBlobTypeDescriptor.INSTANCE, PrimitiveByteArrayTypeDescriptor.INSTANCE );
 }

  public String getName() {
   return "materialized_blob";
  }
}

Molto di questo potrebbe probabilmente essere statico (getBinder () ha davvero bisogno di una nuova istanza?), Ma non capisco davvero l'ibernazione interna, quindi questo è principalmente copia + incolla + modifica.

public class PostgresqlBlobTypeDescriptor extends BlobTypeDescriptor implements SqlTypeDescriptor {
  public static final BlobTypeDescriptor INSTANCE = new PostgresqlBlobTypeDescriptor();

  public <X> ValueBinder<X> getBinder(final JavaTypeDescriptor<X> javaTypeDescriptor) {
   return new PostgresqlBlobBinder<X>(javaTypeDescriptor, this);
  }
  public <X> ValueExtractor<X> getExtractor(final JavaTypeDescriptor<X> javaTypeDescriptor) {
   return new BasicExtractor<X>( javaTypeDescriptor, this ) {
    protected X doExtract(ResultSet rs, String name, WrapperOptions options) throws SQLException { 
      return (X)rs.getBytes(name);
    }
   };
  }
}

public class PostgresqlBlobBinder<J> implements ValueBinder<J> {
 private final JavaTypeDescriptor<J> javaDescriptor;
 private final SqlTypeDescriptor sqlDescriptor;

 public PostgresqlBlobBinder(JavaTypeDescriptor<J> javaDescriptor, SqlTypeDescriptor sqlDescriptor) { 
  this.javaDescriptor = javaDescriptor; this.sqlDescriptor = sqlDescriptor;
 }  
 ...
 public final void bind(PreparedStatement st, J value, int index, WrapperOptions options) 
 throws SQLException {
  st.setBytes(index, (byte[])value);
 }
}

+1 per la tua ricerca. Congratulazioni. Solo un consiglio: preferisci modificare la tua domanda / risposta fino a 8 volte. Altrimenti, la tua domanda / risposta diventerà un wiki della comunità e non guadagnerai reputazione e il voto UP non verrà più calcolato
Arthur Ronald,

Vivi e impara suppongo, ho avuto così tante modifiche, mentre continuavo a dimenticarmi di fare una cosa o l'altra con il mio ambiente di test.
Justin,

Lo stesso qui, +1 per la ricerca e una soluzione per la tua situazione.
Pascal Thivent

qualche possibilità di soluzione con la versione 4.2.x Hibernate? Gli interni di Hibernate sono cambiati un po '(ho commentato il problema riferito: hibernate.atlassian.net/browse/HHH-5584 ).
Peter Butkovic

2

ho risolto il problema aggiungendo l'annotazione di @Lob che creerà il byte [] in Oracle come blob, ma questa annotazione creerà il campo come oid che non funziona correttamente, Per rendere byte [] creato come bytea ho creato il dialetto del cliente per postgres come sotto

Public class PostgreSQLDialectCustom extends PostgreSQL82Dialect {
    public PostgreSQLDialectCustom() {
        System.out.println("Init PostgreSQLDialectCustom");
        registerColumnType( Types.BLOB, "bytea" );

      }

    @Override
    public SqlTypeDescriptor remapSqlTypeDescriptor(SqlTypeDescriptor sqlTypeDescriptor) {
    if (sqlTypeDescriptor.getSqlType() == java.sql.Types.BLOB) {
      return BinaryTypeDescriptor.INSTANCE;
    }
    return super.remapSqlTypeDescriptor(sqlTypeDescriptor);
  }
 }

Inoltre è necessario sovrascrivere il parametro per il dialetto

spring.jpa.properties.hibernate.dialect = com.ntg.common.DBCompatibilityHelper.PostgreSQLDialectCustom

più indizio può essere trovato in lei: https://dzone.com/articles/postgres-and-oracle


0

Ho funzionato sovrascrivendo l'annotazione con il file XML per Postgres. L'annotazione viene mantenuta per Oracle. A mio parere, in questo caso sarebbe meglio sovrascrivere la mappatura di questa entità problematica con la mappatura xml. Possiamo sovrascrivere entità singole / multiple con mapping xml. Quindi useremmo l'annotazione per il nostro database principalmente supportato e un file xml per ogni altro database.

Nota: abbiamo solo bisogno di sovrascrivere una singola classe, quindi non è un grosso problema. Leggi di più dal mio esempio Esempio per sovrascrivere l'annotazione con XML


0

Su Postgres @Lob si interrompe per byte [] mentre cerca di salvarlo come oid, e anche per String si verifica lo stesso problema. Di seguito il codice si sta interrompendo su postgres che funziona bene su Oracle.

@Lob
private String stringField;

e

@Lob
private byte[]   someByteStream;

Per correggere sopra su postgres ho scritto di seguito hibernate.dialect personalizzato

public class PostgreSQLDialectCustom extends PostgreSQL82Dialect{

public PostgreSQLDialectCustom()
{
    super();
    registerColumnType(Types.BLOB, "bytea");
}

 @Override
 public SqlTypeDescriptor remapSqlTypeDescriptor(SqlTypeDescriptor sqlTypeDescriptor) {
    if (Types.CLOB == sqlTypeDescriptor.getSqlType()) {
      return LongVarcharTypeDescriptor.INSTANCE;
    }
    return super.remapSqlTypeDescriptor(sqlTypeDescriptor);
  }
}

Ora configura il dialetto personalizzato in ibernazione

hibernate.dialect=X.Y.Z.PostgreSQLDialectCustom   

XYZ è il nome del pacchetto.

Ora funziona bene. NOTA - La mia versione di Hibernate - 5.2.8 Versione finale di Postgres - 9.6.3


0

Grazie Justin, Pascal per avermi guidato nella giusta direzione. Stavo anche affrontando lo stesso problema con Hibernate 3.5.3. La tua ricerca e i tuoi suggerimenti ai corsi giusti mi hanno aiutato a identificare il problema e a risolverlo.

A vantaggio di coloro che sono ancora bloccati con Hibernate 3.5 e utilizzano la combinazione oid + byte [] + @LoB, di seguito è riportato ciò che ho fatto per risolvere il problema.

  1. Ho creato un BlobType personalizzato che estende MaterializedBlobType e sovrascrive il set e i metodi get con l'accesso in stile oid.

    public class CustomBlobType extends MaterializedBlobType {
    
    private static final String POSTGRESQL_DIALECT = PostgreSQLDialect.class.getName();
    
    /**
     * Currently set dialect.
     */
    private String dialect = hibernateConfiguration.getProperty(Environment.DIALECT);
    
    /*
     * (non-Javadoc)
     * @see org.hibernate.type.AbstractBynaryType#set(java.sql.PreparedStatement, java.lang.Object, int)
     */
    @Override
    public void set(PreparedStatement st, Object value, int index) throws HibernateException, SQLException {
        byte[] internalValue = toInternalFormat(value);
    
        if (POSTGRESQL_DIALECT.equals(dialect)) {
            try {
    
    //I had access to sessionFactory through a custom sessionFactory wrapper.
    st.setBlob(index, Hibernate.createBlob(internalValue, sessionFactory.getCurrentSession()));
                } catch (SystemException e) {
                    throw new HibernateException(e);
                }
            } else {
                st.setBytes(index, internalValue);
            }
        }
    
    /*
     * (non-Javadoc)
     * @see org.hibernate.type.AbstractBynaryType#get(java.sql.ResultSet, java.lang.String)
     */
    @Override
    public Object get(ResultSet rs, String name) throws HibernateException, SQLException {
        Blob blob = rs.getBlob(name);
        if (rs.wasNull()) {
            return null;
        }
        int length = (int) blob.length();
        return toExternalFormat(blob.getBytes(1, length));
      }
    }
    1. Registra CustomBlobType con Hibernate. Di seguito è quello che ho fatto per raggiungere questo obiettivo.

      hibernateConfiguration= new AnnotationConfiguration();
      Mappings mappings = hibernateConfiguration.createMappings();
      mappings.addTypeDef("materialized_blob", "x.y.z.BlobType", null);
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.