annotation d'hibernation appropriée pour l'octet []


120

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 très bien sur toutes les principales bases de données - il semble cacher 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 la mise en veille prolongée 3.5 casse (et ne corrige pas) cette combinaison d'annotations dans postgresql (sans solution de contournement). Je n'ai pas trouvé de correctif clair jusqu'à présent, mais j'ai remarqué que si je supprimais simplement le @Lob, il utilise le type postgresql bytea (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 recherche un moyen d'avoir une seule classe annotée (avec une propriété blob) qui soit portable dans les principales bases de données.

  • Quelle est la manière portable d'annoter une propriété byte []?
  • Est-ce corrigé dans une version récente de la mise en veille prolongée?

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 - j'obtiens toujours des OID au lieu de bytea; cela a cependant fonctionné pour l'auteur du numéro JIRA, qui semblait vouloir l'oid.

Après la réponse d'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 je dois vraiment faire est de contrôler à quel @ org.hibernate.annotations.Type la combinaison (@Lob + byte [] est mappée) à (sur postgresql).


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

Mise à jour La question logique suivante est: "pourquoi ne pas simplement changer les définitions de table manuellement en bytea" et conserver le (@Lob + byte [])? Cela fait le travail, JUSQU'AU vous essayez de stocker un octet nul []. Ce que le pilote postgreSQL pense être une expression de type OID et le type de colonne est bytea - c'est parce que hibernate appelle (à juste titre) JDBC.setNull () au lieu de JDBC.setBytes (null) que le pilote PG attend.

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

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

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

Je regarde 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 fasse correctement les LOB avant de changer Hibernate."

Références:


Cela semble avoir été corrigé dans la version 3.6, pas sûr à propos de la version 3.5.6; la classe MaterializedBlobType a été totalement réécrite de 3.5.5> 3.6. Le type OID fonctionne maintenant depuis qu'ils ont changé d'implémentation.
Justin le

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

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 corrigé, le bogue vient de passer à BlobTypeDescriptor.
Justin le

Merci. @Type (type = "org.hibernate.type.BinaryType") a fonctionné pour moi pour une table qui stockait des fichiers PDF. J'ai migré une base de données d'Oracle vers Postgres en utilisant Oracle-To-PostgreSQL à partir d'Intelligent Converters, et elle a été automatiquement convertie et insérée de BLOB vers BYTEA mais BlobType n'a pas fonctionné pour moi.
jmoran

Réponses:


68

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

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

11.1.6 Annotation de base

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

Et Hibernate mappera un it "par défaut" vers un SQL VARBINARY(ou un SQL LONGVARBINARYselon la Columntaille?) Que PostgreSQL gère avec un bytea.

Mais si vous voulez que le byte[]soit stocké dans un grand objet, vous devez utiliser un fichier @Lob. De la spécification:

11.1.24 Annotation de Lob

Une Lobannotation spécifie qu'une propriété ou un champ persistant doit être conservé en tant qu'objet volumineux dans un type d'objet volumineux pris en charge par la base de données. Les applications portables doivent utiliser l' Lobannotation lors du mappage vers un Lobtype de base de données . L' Lobannotation peut être utilisée conjointement avec l'annotation de base ou avec l' ElementCollectionannotation lorsque la valeur de la collection d'élément est de type de base. A Lobpeut être un type binaire ou caractère. Le Lobtype est déduit du type du champ ou de la propriété persistante et, à l'exception des types chaîne et caractère, est par défaut Blob.

Et Hibernate le mappera à un SQL BLOBque PostgreSQL gère avec un oid .

Est-ce corrigé dans une version récente de la mise en veille prolongée?

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 (où un changement a été introduit) dans la branche 3.5.x.

Mais ma compréhension des problèmes tels que HHH-4876 , HHH-4617 et PostgreSQL et BLOB (mentionnés dans le javadoc du PostgreSQLDialect) est que vous êtes censé définir la propriété suivante

hibernate.jdbc.use_streams_for_binary=false

si vous voulez utiliser oidie byte[]avec @Lob(ce que je comprends puisque ce VARBINARYn'est pas ce que vous voulez avec Oracle). Avez-vous essayé cela?

Comme alternative, HHH-4876 suggère d'utiliser la version obsolète PrimitiveByteArrayBlobTypepour obtenir l'ancien comportement (avant Hibernate 3.5).

Références

  • Spécification JPA 2.0
    • Section 2.8 «Mappage des valeurs par défaut pour les champs ou propriétés sans relation»
    • Section 11.1.6 "Annotation de base"
    • Section 11.1.24 "Annotation Lob"

Ressources


OMG, je me rends compte que cette question a beaucoup changé depuis que j'ai commencé à y répondre. Lira tous les changements plus tard et mettra à jour mes réponses après avoir digéré les changements si nécessaire.
Pascal Thivent

Il est bon de voir la spécification, donc hibernate est tout à fait correct de mapper (@Lob + byte []) à un type de grand objet pris en charge. Dans PostgreSQL, il y en a 2 (bytea ou oid). Cependant, alors que hibernate 3.5 correspond à oid (par défaut), il lit en utilisant JDBC getBytes () quel pilote PGSQL renvoie l'oid de 6 octets au lieu des données. Notez également que l'auteur du blog a répondu le plus utilement (sur son blog) depuis que la question a été posée.
Justin le

@Justin Cependant, alors que hibernate 3.5 correspond à oid (par défaut), il lit en utilisant JDBC getBytes () quel pilote PGSQL renvoie l'oid de 6 octets au lieu des données - cela se produit-il également lors de l'utilisation hibernate.jdbc.use_streams_for_binary=false? (va vérifier ce que Steve a dit maintenant).
Pascal Thivent

Je vais essayer de le spécifier dans le fichier de propriétés, mais PostgreSQLDialect a useInputStreamToInsertBlob () renvoyant false donc je suppose que je le suis - car je ne définit pas explicitement cette propriété.
Justin le

Après avoir défini cette propriété (sur true ou false), j'obtiens une exception d'exécution: ERREUR: la colonne "signature" est de type bytea mais l'expression est de type oid ". Je dois mentionner que j'utilise hibernate 3.5.5.Final + PG 8.2 pilotes.
Justin

10

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 caractère.

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

Remplacez PostgreSQLDialect comme suit

public class CustomPostgreSQLDialect extends PostgreSQLDialect {

    public CustomPostgreSQLDialect() {
        super();

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

Maintenant, définissez simplement votre dialecte personnalisé

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

Et utilisez votre annotation JPA @Lob portable

@Lob
public byte[] getValueBuffer() {

METTRE À JOUR

Ici a été extrait ici

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

...

La migration vers hibernate 3.5 tous les champs blob ne fonctionne plus et le journal du serveur affiche: ERREUR org.hibernate.util.JDBCExceptionReporter - ERREUR: la colonne est de type oid mais l'expression est de type bytea

qui peut être expliqué ici

Ce n'est généralement pas un bogue dans PG JDBC , mais un changement de l'implémentation par défaut d'Hibernate en version 3.5 . Dans ma situation, la configuration d'une propriété compatible lors de la connexion n'a pas aidé .

...

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

Intéressant est parce que quand il mappe Types.BOLB comme bytea (Voir CustomPostgreSQLDialect) Il obtient

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

lors de l'insertion ou de la mise à jour


Cette solution semble glorieuse, je l’essaye maintenant.
Justin le

Cela génère le DDL correct, mais échoue à l'exécution: j'obtiens une exception java.sql.BatchUpdateException lorsque j'essaye d'accéder à un objet avec une propriété blob.
Justin le

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

En fait, ce que je veux vraiment, ce n'est pas de mapper BLOB sur "bytea", mais plutôt de mapper la combinaison d'annotations (octet [] + @Lob) vers Types.VARBINARY!
Justin le


7

J'utilise 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;
  }
}

car 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, que je peux garder les pots de mise en veille prolongée intacts.

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


2
J'ai travaillé pour moi en utilisant Hibernate 4.3.6 et Postgresql 9.3 avec l'extension de Postgresql9Dialect. Je vous remercie!
Andrés Oviedo

Fonctionne avec Hibernate 5.3.7.Final et Postgres95Dialect. Thx
Bernhard Kern

6

J'ai enfin réussi à faire fonctionner ça. Il étend la solution de A. Garcia, cependant, puisque le problème réside dans le type de mise en veille prolongée de type MaterializedBlob, il ne suffit pas de mapper Blob> bytea, nous avons besoin d'un remplacement pour MaterializedBlobType qui fonctionne avec le support des blob cassés en veille prolongée. Cette implémentation ne fonctionne qu'avec bytea, mais peut-être que le gars du problème JIRA qui voulait OID pourrait contribuer à une implémentation OID.

Malheureusement, le remplacement de ces types au moment de l'exécution est une douleur, car ils devraient faire partie du dialecte. Si seulement cette amélioration JIRA entre dans la version 3.6, ce 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 la mise en veille prolongée, donc il s'agit 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);
 }
}

+1 pour votre recherche. Toutes nos félicitations. Juste un conseil: préférez modifier votre propre question / réponse jusqu'à 8 fois. Sinon, votre question / réponse deviendra le wiki de la communauté et vous ne gagnerez pas de réputation et le vote UP ne sera plus calculé
Arthur Ronald

Vivre et apprendre, je suppose, j'ai eu tellement de modifications, car j'oubliais de faire une chose ou une autre avec mon environnement de test.
Justin le

Idem ici, +1 pour la recherche et une solution à votre situation.
Pascal Thivent

une chance de solution avec la version 4.2.x Hibernate? Les composants internes d'Hibernate ont un peu changé (j'ai commenté le problème référencé: hibernate.atlassian.net/browse/HHH-5584 ).
Peter Butkovic

2

J'ai résolu mon problème en ajoutant l'annotation de @Lob qui créera l'octet [] dans l'oracle en tant que blob, mais cette annotation créera le champ comme oid qui ne fonctionnera pas correctement, Pour rendre l'octet [] créé en tant que bytea, j'ai créé un dialecte client pour 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);
  }
 }

Besoin également 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 sur elle: https://dzone.com/articles/postgres-and-oracle


0

Je l'ai fait fonctionner en remplaçant l'annotation 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 entité problématique avec le mappage xml. Nous pouvons remplacer des entités simples / multiples avec un 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, donc ce n'est pas un gros problème. Lire la suite de mon exemple Exemple pour remplacer l'annotation avec XML


0

Sur Postgres, @Lob est interrompu pour l'octet [] alors qu'il tente de l'enregistrer en tant qu'oid, et pour String, le même problème se produit. Le code ci-dessous se brise sur postgres qui fonctionne bien sur oracle.

@Lob
private String stringField;

et

@Lob
private byte[]   someByteStream;

Afin de corriger ci-dessus sur postgres, j'ai écrit ci-dessous hibernate.dialect personnalisé

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   

XYZ est le nom du package.

Maintenant, cela fonctionne bien. REMARQUE - My Hibernate version - 5.2.8.Final Postgres version - 9.6.3


0

Merci Justin, Pascal de m'avoir guidé dans la bonne direction. J'étais également confronté au même problème avec Hibernate 3.5.3. Vos recherches et vos pointeurs vers les bonnes classes m'ont aidé à identifier le problème et à résoudre le problème.

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

  1. J'ai créé un BlobType personnalisé étendant MaterializedBlobType et remplaçant l'ensemble et les méthodes get avec l'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 avec 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);
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.