140 votes

<span style="font-family: Arial, sans-serif; font-size: 12px;">annotation Hibernate appropriée pour byte[]</span>

J'ai une application utilisant Hibernate 3.1 et les annotations JPA. Elle contient quelques objets avec des attributs byte[] (taille de 1k à 200k). Elle utilise l'annotation JPA @Lob, et Hibernate 3.1 peut bien lire ces données sur toutes les bases de données principales -- il semble masquer les particularités des fournisseurs 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, quand nous avons découvert que Hibernate 3.5 casse (et ne répare pas) cette combinaison d'annotations dans postgresql (sans contournement). Je n'ai pas encore trouvé de solution claire jusqu'à présent, mais j'ai remarqué que si je supprime simplement le @Lob, il utilise le type postgresql bytea (qui fonctionne, mais seulement sur postgres).

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

une fois que vous utilisez @Type, @Lob semble ne plus être pertinent
note : oracle semble avoir déprécié le type "raw" depuis 8i.

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

  • Quel est le moyen portable pour annoter une propriété byte[] ?
  • Cela a-t-il été corrigé dans une version récente de Hibernate ?

Mise à jour : Après avoir lu ce blog, j'ai finalement compris quel était le contournement original dans l'issue 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 -- j'obtiens toujours des OID au lieu de bytea ; cependant, cela a fonctionné pour l'auteur de l'issue JIRA, qui semblait vouloir oid.

Après la réponse d'A. Garcia, j'ai donc essayé cette combinaison, qui fonctionne effectivement sur postgresql, mais pas sur oracle.

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

Ce dont j'ai vraiment besoin de faire est de contrôler quel @org.hibernate.annotations.Type la combinaison (@Lob + byte[] est mappée) vers (sur postgresql).


Voici l'extrait de 3.5.5.Final de MaterializedBlobType (type sql Blob). Selon le blog de Steve, postgresql veut que vous utilisiez des flux pour bytea (ne me demandez pas pourquoi) et le type Blob personnalisé de postgresql pour les oid. Notez également que l'utilisation de setBytes() sur JDBC concerne également le bytea (d'après mon expérience passée). Cela explique pourquoi use-streams n'a aucun effet car 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 donne :

ERREUR : la colonne "signature" est de type oid mais l'expression est de type bytea

Mise à jour La question logique suivante est : "pourquoi ne pas simplement changer manuellement les définitions de table en bytea" et conserver le (@Lob + byte[]) ? Cela fonctionne, JUSQU'À ce que vous essayiez de stocker un byte[] nul. Que le pilote postgreSQL considère comme une expression de type OID et que le type de colonne soit bytea -- ceci est dû au fait qu'hibernate (à juste titre) appelle JDBC.setNull() au lieu de JDBC.setBytes(null) comme attendu par le pilote PG.

ERREUR : la colonne "signature" est de type bytea mais l'expression est de type oid

Le système de types dans hibernate est actuellement un 'travail en cours' (selon le commentaire de dépréciation de 3.5.5). En fait, tellement de code de la version 3.5.5 est déprécié, qu'il est difficile de savoir à quoi il faut faire attention lors de la sous-classe de PostgreSQLDialect).

AFAKT, les types Types.BLOB/'oid' sur postgresql devraient être mappés vers un type personnalisé qui utilise un accès JDBC de style OID (c'est-à-dire un objet PostgresqlBlobType et PAS MaterializedBlobType). Je n'ai jamais réellement utilisé avec succès des Blobs avec postgresql, mais je sais que bytea fonctionne simplement comme prévu.

Je regarde actuellement l'exception BatchUpdateException -- il est possible que le pilote ne prenne pas en charge le mode batch.


Excellente citation de 2004 : "Pour résumer mes divagations, je dirais que nous devrions attendre que le pilote JDBC gère correctement les LOBs avant de modifier Hibernate."

Références :

0 votes

Cela semble avoir été corrigé dans 3.6, pas certain pour 3.5.6 ; la classe MaterializedBlobType a été totalement réécrite de 3.5.5 > 3.6. Le type OID fonctionne désormais depuis qu'ils ont changé l'implémentation.

0 votes

Bien ! Je me demande quel problème Jira suit cette réécriture, le cas échéant (peut-être que la réécriture est une conséquence d'un changement plus profond). Ce serait bien de rétroporter les modifications dans la version 3.5, si possible. Mauvaise nouvelle si ce n'est pas possible.

0 votes

En fait, mon test m'a donné un faux positif la première fois (je savais que j'aurais dû attendre!) - ce n'est toujours pas résolu, le bug s'est simplement déplacé vers BlobTypeDescriptor.

76voto

Pascal Thivent Points 295221

Quelle est la manière portable d'annoter une propriété byte[] ?

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

11.1.6 Annotation de Base

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

Et Hibernate le mappera "par défaut" vers un VARBINARY SQL (ou un LONGVARBINARY SQL en fonction de la taille de Column) que PostgreSQL manipule avec un bytea.

Mais si vous souhaitez que le byte[] soit stocké dans un Objet Large, vous devriez utiliser un @Lob. Selon la spécification :

11.1.24 Annotation Lob

Une annotation Lob spécifie qu'une propriété persistante ou un champ devrait être persisté en tant qu'objet large vers un type d'objet large pris en charge par la base de données. Les applications portables devraient utiliser l'annotation Lob lors du mappage vers un type Lob de base de données. L'annotation Lob peut être utilisée conjointement avec l'annotation Basic ou avec l'annotation ElementCollection lorsque la valeur de la collection d'éléments est de type basique. Un Lob peut être de type binaire ou de type caractère. Le type Lob est déduit du type du champ ou de la propriété persistante et, sauf pour les types de chaînes et de caractères, il défaut Blob.

Et Hibernate le mappera vers un BLOB SQL que PostgreSQL manipule avec un oid.

Est-ce que c'est corrigé dans une version récente de Hibernate ?

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

Mais d'après ce que je comprends des problèmes comme HHH-4876, HHH-4617 et de PostgreSQL et BLOBs (mentionné dans la javadoc du PostgreSQLDialect), vous devez 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é ceci ?

En tant qu'alternative, HHH-4876 suggère d'utiliser le PrimitiveByteArrayBlobType obsolète pour obtenir l'ancien comportement (avant Hibernate 3.5).

Références

  • Spécification JPA 2.0
    • Section 2.8 "Mapping Defaults for Non-Relationship Fields or Properties"
    • Section 11.1.6 "Basic Annotation"
    • Section 11.1.24 "Lob Annotation"

Ressources

0 votes

OMG, Je réalise que cette question a beaucoup changé depuis que j'ai commencé à répondre. Je lirai tous les changements plus tard et mettrai à jour mes réponses après avoir digéré les changements si nécessaire.

0 votes

Il est bon de voir la spécification, donc hibernate a tout à fait raison de mappé (@Lob + byte[]) vers un type d'objet pris en charge. Dans Postgresql il y en a 2 (bytea ou oid). Cependant, bien que hibernate 3.5 mappe vers oid (par défaut), il lit en utilisant getBytes() de JDBC, ce qui fait que le driver PGSQL retoune le oid de 6 octets au lieu des données. Notez également que l'auteur du blog a répondu de manière très utile (sur son blog) depuis que la question a été posée.

0 votes

@Justin Cependant, alors que Hibernate 3.5 est mappé sur l'oid (par défaut), il lit en utilisant JDBC getBytes(), ce qui fait que le driver PGSQL retourne l'oid de 6 octets au lieu des données - est-ce que cela se produit également lors de l'utilisation de hibernate.jdbc.use_streams_for_binary=false? (je vais vérifier ce que Steve a dit maintenant).

11voto

Arthur Ronald Points 19001

Ici ce que déclare 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 caractères.

Ici le code source de PostgreSQLDialect

public PostgreSQLDialect() {
    super();
    ...
    registerColumnType(Types.VARBINARY, "bytea");
    /**
      * Remarquez qu'il associe java.sql.Types.BLOB à oid
      */
    registerColumnType(Types.BLOB, "oid");
}

Alors voici ce que vous pouvez faire

Surchargez PostgreSQLDialect comme suit

public class CustomPostgreSQLDialect extends PostgreSQLDialect {

    public CustomPostgreSQLDialect() {
        super();

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

Maintenant définissez simplement votre dialecte personnalisé

Et utilisez votre annotation JPA portable @Lob

@Lob
public byte[] getValueBuffer() {

MISE À JOUR

Voici ce qui a été extrait ici

J'ai une application tournant sous hibernate 3.3.2 et l'application fonctionne parfaitement, avec tous les champs blob utilisant oid (byte[] en java)

...

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

ce qui peut être expliqué ici

Il ne s'agit généralement pas d'un bug dans PG JDBC, mais d'un changement d'implémentation par défaut de Hibernate dans la version 3.5. Dans ma situation, définir une propriété compatible sur la connexion n'a pas aidé.

...

Ce que j'ai vu en 3.5 - beta 2, et je ne sais pas si cela a été corrigé, c'est qu'Hibernate - sans l'annotation @Type - créera automatiquement une colonne de type oid, mais essaiera de la lire comme bytea

C'est intéressant car lorsqu'il mappe Types.BLOB en bytea (Voir CustomPostgreSQLDialect), il obtient

Impossible d'exécuter une mise à jour en lot JDBC

lors de l'insertion ou de la mise à jour

0 votes

Cette solution semble glorieuse, je vais la tester maintenant.

0 votes

Cela génère le DDL correct, mais échoue à l'exécution: J'obtiens une java.sql.BatchUpdateException lors de la tentative de mise à jour d'un objet avec une propriété blob.

0 votes

@Justin Essayez un scénario similaire en utilisant Oracle au lieu de PostgreSQL et voyez ce que vous obtenez. BatchUpdateException a à voir avec les erreurs qui surviennent lors d'une opération de mise à jour par lots.

9voto

Peter Butkovic Points 2403

Je utilise le Hibernate 4.2.7.SP1 avec Postgres 9.3 et ce qui suit fonctionne 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 que je considère, c'est que je peux garder les jars hibernate intacts.

Pour d'autres problèmes de compatibilité Postgres/Oracle avec Hibernate, consultez mon article de blog.

2 votes

A fonctionné pour moi en utilisant Hibernate 4.3.6 et Postgresql 9.3 en étendant Postgresql9Dialect. Merci!

0 votes

Fonctionne avec Hibernate 5.3.7.Final et Postgres95Dialect. Merci

7voto

Justin Points 2376

J'ai enfin réussi à faire fonctionner cela. Cela complète la solution de A. Garcia, cependant, puisque le problème réside dans le type Hibernate MaterializedBlob, il ne suffit pas de mapper Blob > bytea, nous avons besoin d'un remplacement pour MaterializedBlobType qui fonctionne avec le support blob cassé d'Hibernate. Cette implémentation ne fonctionne qu'avec bytea, mais peut-être que le gars de l'incident JIRA qui voulait OID pourrait contribuer à une implémentation OID.

Malheureusement, remplacer ces types à l'exécution est pénible, car ils devraient faire partie du Dialecte. Si seulement cette amélioration JIRA était incluse dans la version 3.6, cela serait possible.

public class PostgresqlMateralizedBlobType extends AbstractSingleColumnStandardBasicType {
 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 (est-ce que getBinder() a vraiment besoin d'une nouvelle instance ?), mais je ne comprends pas vraiment l'implémentation interne de Hibernate, donc c'est principalement du copier-coller + modification.

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

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

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

 public PostgresqlBlobBinder(JavaTypeDescriptor 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);
 }
}

0 votes

+1 pour votre recherche. Félicitations. Juste un conseil : Préférez éditer votre propre question/réponse jusqu'à 8 fois. Sinon, votre question/réponse deviendra communauté wiki et vous ne gagnerez plus de réputation et les votes UP ne seront plus pris en compte

0 votes

Vivre et apprendre, je suppose, j'ai tellement d'éditions, car j'oubliais toujours de faire une chose ou une autre avec mon environnement de test.

0 votes

Ici aussi, +1 pour la recherche et une solution pour votre situation.

0voto

Vinh Vo Points 11

J'ai réussi à le faire en remplaçant l'annotation par un fichier XML pour Postgres. L'annotation est conservée pour Oracle. À mon avis, dans ce cas, il serait préférable de remplacer la cartographie de cette entité problématique par une cartographie XML. Nous pouvons remplacer une ou plusieurs entités avec une cartographie XML. Ainsi, nous utiliserions l'annotation pour notre base de données principalement supportée, et un fichier XML pour chaque autre base de données.

Remarque : nous avons juste besoin de remplacer une seule classe, donc ce n'est pas une grosse affaire. En savoir plus à partir de mon exemple Exemple de remplacement de l'annotation par XML

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X