web-dev-qa-db-fra.com

bonne annotation en hibernation pour byte []

J'ai une application utilisant les annotations Hibernate 3.1 et JPA. Il a quelques objets avec des attributs byte [] (taille 1k - 200k). Il utilise l'annotation JPA @Lob, et hibernate 3.1 peut les lire parfaitement sur toutes les bases de données majeures - il semble masquer les particularités du fournisseur JDBC Blob (comme il se doit).

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

Nous avons dû passer à la version 3.5, lorsque nous avons découvert que hibernate 3.5 était cassé (et ne corrigeait pas) cette combinaison d'annotations dans postgresql (sans solution de contournement). Je n'ai pas trouvé de solution claire jusqu'à présent, mais j'ai remarqué que si je supprime simplement @Lob, il utilise le type bytea de postgresql (qui fonctionne, mais uniquement sur 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.

Je cherche un moyen d'avoir une seule classe annotée (avec une propriété blob) portable dans les principales bases de données.

  • Quel est le moyen portable d'annoter une propriété byte []?
  • Est-ce corrigé dans une version récente de Hibernate?

Mise à jour: Après avoir lu ce blog J'ai enfin compris quelle était la solution de contournement d'origine dans le problème JIRA: Apparemment, vous êtes censé supprimer @Lob et annoter la propriété comme suit:

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

Cependant, cela ne fonctionne pas pour moi _ - Je reçois toujours des OID au lieu de bytea; cela a cependant fonctionné pour l'auteur de la question JIRA, qui semblait vouloir en avoir une.

Après la réponse de A. Garcia, j’ai ensuite essayé ce combo, qui fonctionne réellement sur postgresql, mais pas sur Oracle.

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

Ce que j'ai vraiment besoin de faire, c'est de contrôler lequel @ org.hibernate.annotations.Type la combinaison (@Lob + octet [] est mappée) à (sur postgresql).


Voici l'extrait de 3.5.5.Final de MaterializedBlobType (type blob). Selon le blog de Steve, postgresql souhaite que vous utilisiez Streams pour bytea (ne me demandez pas pourquoi) et le type de blob personnalisé de postgresql pour les oids. Notez également que l’utilisation de setBytes () sur JDBC s’applique également à bytea (à partir de l’expérience passée). Donc, cela explique pourquoi use-streams n'a aucun effet, ils supposent tous les deux "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 );
 }
}

Cela se traduit par:

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

Update La prochaine question logique est: "pourquoi ne pas simplement changer manuellement les définitions de la table en bytea" et conserver le (@Lob + octet [])? Ceci fait fonctionne,JUSQU'Àvous essayez de stocker un octet nul []. Le pilote postgreSQL considère qu'il s'agit d'une expression de type OID et que le type de colonne est bytea - car hibernate (à juste titre) appelle JDBC.setNull () au lieu de JDBC.setBytes (null) attendu par le pilote PG.

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

Le système de types dans hibernate est actuellement un «travail en cours» (selon le commentaire de dépréciation 3.5.5). En fait, une grande partie du code 3.5.5 est obsolète, il est difficile de savoir quoi regarder lors de la sous-classification de PostgreSQLDialect).

AFAKT, Types.BLOB/'oid' sur postgresql doit être mappé sur un type personnalisé qui utilise un accès JDBC de style OID (c'est-à-dire un objet PostgresqlBlobType et NOT MaterializedBlobType). Je n’ai jamais utilisé Blobs avec postgresql avec succès, mais je sais que bytea fonctionne tout simplement comme je l’espérais.

J'examine actuellement l'exception BatchUpdateException - il est possible que le pilote ne prenne pas en charge le traitement par lots.


Excellente citation de 2004: "Pour résumer mes divagations, je dirais qu’ils devraient attendre que le pilote JDBC exécute correctement les LOB avant de changer Hibernate."

Références:

102
Justin

Quel est le moyen portable d'annoter une propriété byte []?

Cela dépend de ce que vous voulez. JPA peut conserver un byte[] non annoté. A partir de la spécification JPA 2.0: 

11.1.6 Annotation de base

L'annotation Basic est la plus simple type de mappage sur une colonne de base de données . L'annotation Basic peut être appliquée à une propriété ou une instance persistante variable de l’un des éléments suivants types: primitive Java, types, wrappers des types primitifs, 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[], enums et tout autre type qui implémente Serializable. Comme décrit dans la section 2.8, l'utilisation de l'annotation Basic est facultatif pour les champs et propriétés persistants de ces types. Si le Basic l'annotation n'est pas spécifiée pour un tel champ ou propriété, les valeurs par défaut de l'annotation de base s'appliquera.

Et Hibernate mappera un "par défaut" sur un SQL VARBINARY (ou un SQL LONGVARBINARY en fonction de la taille Column?) Que PostgreSQL gère avec un bytea.

Mais si vous souhaitez que le byte[] soit stocké dans un objet volumineux, vous devez utiliser un @Lob. De la spec:

11.1.24 Annotation Lob

Une annotation Lob spécifie qu'un La propriété ou le champ persistant doit être persisté comme un gros objet à un type d'objet volumineux pris en charge par la base de données . Les applications portables doivent utiliser le L'annotation Lob lors du mappage vers un base de données Lob type. L'annotation Lob peut être utilisé en conjonction avec le Annotation de base ou avec le ElementCollection annotation lorsque le La valeur de la collection d'éléments est basique type. Lob peut être soit un binaire, soit un type de caractère. Le type Lob est déduit du type de champ ou propriété persistant et, sauf pour les types de chaîne et de caractère, La valeur par défaut est Blob.

Et Hibernate le mappera sur un BLOB SQL que PostgreSQL manipulera avec un oid.

Est-ce que cela est corrigé dans une version récente d'Hibernate?

Eh bien, le problème est que je ne sais pas quel est le problème exactement. Mais je peux au moins dire que rien n’a changé depuis la version 3.5.0-Beta-2 (où une modification a été introduite) dans la branche 3.5.x.

Mais ma compréhension de questions comme HHH-4876 , HHH-4617 et de PostgreSQL et BLOBs (mentionné dans le javadoc de la PostgreSQLDialect) est que vous êtes censé définir la propriété suivante

hibernate.jdbc.use_streams_for_binary=false

si vous voulez utiliser oid c'est-à-dire byte[] avec @Lob (ce que je comprends, puisque VARBINARY n'est pas ce que vous voulez avec Oracle). Avez-vous essayé cela?

Au lieu de cela, HHH-4876 suggère d'utiliser le paramètre obsolète PrimitiveByteArrayBlobType pour obtenir l'ancien comportement (antérieur à Hibernate 3.5).

Références

  • Spécifications JPA 2.0
    • Section 2.8 "Mappage des valeurs par défaut pour les champs ou propriétés non relationnels"
    • Section 11.1.6 "Annotation de base"
    • Section 11.1.24 "Annotation Lob"

Ressources

59
Pascal Thivent

Voici ce que dit O'reilly Enterprise JavaBeans 3.0

JDBC a des types spéciaux pour ces très gros objets. Le type Java.sql.Blob représente des données binaires et Java.sql.Clob représente des données de type caractères.

Voici le code source de PostgreSQLDialect

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

Alors qu'est-ce que tu peux faire

Remplacer PostgreSQLDialect comme suit

public class CustomPostgreSQLDialect extends PostgreSQLDialect {

    public CustomPostgreSQLDialect() {
        super();

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

Il suffit maintenant de définir votre dialecte personnalisé

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

Et utilisez votre annotation JPA @Lob portable

@Lob
public byte[] getValueBuffer() {

UPDATE

Ici a été extrait ici

J'ai une application en cours d'exécution dans hibernate 3.3.2 et les applications fonctionnent très bien , avec tous les champs blob utilisant oid (byte [] en Java)

...

Migrer vers hibernate 3.5, tous les champs blob ne fonctionnent plus , et le journal du serveur indique: ERROR org.hibernate.util.JDBCExceptionReporter - ERROR: la colonne est de type oid mais l'expression est de type bytea

qui peut être expliqué ici

En règle générale, ceci n’est pas un bogue dans PG JDBC , , mais une modification de l’implémentation par défaut d’Hibernate dans la version 3.5 . Dans ma situation la définition de la propriété compatible sur la connexion n'a pas aidé .

...

Bien plus, c’est ce que j’ai vu dans la version 3.5 - bêta 2, et je ne sais pas si cela a été corrigé est Hibernate - sans annotation @Type - créera automatiquement une colonne de type oid, mais essaiera lire ceci comme bytea

Intéressant, c’est parce qu’il mappe Types.BOLB en bytea (voir CustomPostgreSQLDialect).

Impossible d'exécuter la mise à jour par lots JDBC

lors de l'insertion ou de la mise à jour

9
Arthur Ronald

J'ai enfin ce travail. Cependant, comme le problème réside dans le type hibernate MaterializedBlob, il suffit de mapper Blob> bytea, nous avons besoin d’un remplacement pour MaterializedBlobType qui fonctionne avec le support blob brisé hiberné. Cette implémentation fonctionne uniquement avec bytea, mais peut-être que le responsable du problème JIRA qui souhaitait OID pourrait contribuer à une implémentation OID.

Malheureusement, remplacer ces types à l'exécution est une tâche ardue, car ils devraient faire partie du Dialect . Si seulement cette amélioration de JIRA passe à la version 3.6, cela serait possible.

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

Une grande partie de cela pourrait probablement être statique (getBinder () a-t-il vraiment besoin d'une nouvelle instance?), Mais je ne comprends pas vraiment le mode hibernate interne, il s'agit donc principalement de copier-coller + modifier.

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);
 }
}
6
Justin

J'utilise Hibernate 4.2.7.SP1 avec Postgres 9.3 et les suivants fonctionnent pour moi:

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

comme Oracle n’a aucun problème avec cela, et pour Postgres, j’utilise un dialecte personnalisé:

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

l’avantage de cette solution, j’estime que je peux garder intacts les bocaux en veille prolongée.

Pour plus de problèmes de compatibilité Postgres/Oracle avec Hibernate, consultez mon blog post .

4
Peter Butkovic

j'ai résolu mon problème en ajoutant l'annotation de @Lob qui créera l'octet [] dans Oracle sous forme de blob, mais cette annotation créera le champ sous oid qui ne fonctionnera pas correctement postgres comme ci-dessous 

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

Aussi besoin de remplacer le paramètre pour le dialecte

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

plus d'indices peuvent être trouvés elle: https://dzone.com/articles/postgres-and-Oracle

1

Je l'ai obtenu en redéfinissant les annotations avec un fichier XML pour Postgres. L'annotation est conservée pour Oracle. À mon avis, dans ce cas, il serait préférable de remplacer le mappage de cette enité problématique avec un mappage xml. Nous pouvons remplacer des entités uniques/multiples avec le mappage XML. Nous utiliserions donc des annotations pour notre base de données principalement prise en charge et un fichier xml pour chaque autre base de données.

Remarque: nous avons juste besoin de remplacer une seule classe, ce n'est donc pas grave . Lire la suite de mon exemple Exemple pour remplacer une annotation avec XML

0
Vinh Vo

Sur Postgres, @Lob se casse pour byte [] alors qu'il tente de l'enregistrer en tant que oid. Pour String, le même problème se produit. Le code ci-dessous se casse sur postgres, ce qui fonctionne très bien sur Oracle.

@Lob
private String stringField;

et 

@Lob
private byte[]   someByteStream;

Afin de corriger ci-dessus postgres ont écrit ci-dessous custom hibernate.dialect 

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

Maintenant, configurez le dialecte personnalisé en veille prolongée 

hibernate.dialect=X.Y.Z.PostgreSQLDialectCustom   

X.Y.Z est le nom du paquet. 

Maintenant cela fonctionne bien. NOTE - Ma version d'Hibernate - 5.2.8.Final Version Postgres - 9.6.3

0
gajendra kumar

Merci Justin, Pascal de m'avoir guidé dans la bonne direction. Je faisais également face au même problème avec Hibernate 3.5.3. Vos recherches et vos indications sur les bonnes classes m'avaient aidé à identifier le problème et à remédier à la situation.

Pour le bénéfice de ceux qui sont toujours bloqués avec Hibernate 3.5 et qui utilisent la combinaison oid + octet [] + @LoB, voici ce que j'ai fait pour résoudre le problème.

  1. J'ai créé un BlobType personnalisé, étendant MaterializedBlobType et redéfinissant les méthodes set et get avec un accès de style 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. Enregistrez le CustomBlobType auprès d'Hibernate. Voici ce que j'ai fait pour y parvenir.

      hibernateConfiguration= new AnnotationConfiguration();
      Mappings mappings = hibernateConfiguration.createMappings();
      mappings.addTypeDef("materialized_blob", "x.y.z.BlobType", null);
      
0