anotação de hibernação adequada para byte []

120

Eu tenho um aplicativo que usa anotações Hibernate 3.1 e JPA. Possui alguns objetos com atributos byte [] (1k - 200k de tamanho). Ele usa a anotação JPA @Lob, e o hibernate 3.1 pode lê-los perfeitamente em todos os principais bancos de dados - parece ocultar as peculiaridades do fornecedor do JDBC Blob (como deveria).

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

Tivemos que atualizar para 3.5, quando descobrimos que o hibernate 3.5 quebra (e não corrige) essa combinação de anotação no postgresql (sem solução alternativa). Não encontrei uma correção clara até agora, mas notei que se eu apenas remover o @Lob, ele usa o tipo bytea do postgresql (que funciona, mas apenas no 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.

Estou procurando uma maneira de ter uma única classe anotada (com uma propriedade blob) que seja portátil entre os principais bancos de dados.

  • Qual é a maneira portátil de anotar uma propriedade byte []?
  • Isso foi corrigido em alguma versão recente do hibernate?

Atualização: Depois de ler este blog , finalmente descobri qual era a solução alternativa original para o problema do JIRA: Aparentemente, você deve remover @Lob e anotar a propriedade como:

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

No entanto, isso não funciona para mim - ainda recebo OIDs em vez de bytea; no entanto, funcionou para o autor da edição do JIRA, que parecia querer oid.

Após a resposta de A. Garcia, tentei esta combinação, que na verdade funciona no postgresql, mas não no oracle.

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

O que eu realmente preciso fazer é controlar para qual @ org.hibernate.annotations.Type a combinação (@Lob + byte [] é mapeada) (no postgresql).


Aqui está o snippet de 3.5.5.Final de MaterializedBlobType (sql type Blob). De acordo com o blog de Steve, postgresql deseja que você use Streams para bytea (não me pergunte por quê) e o tipo de Blob personalizado do postgresql para oids. Observe também que o uso de setBytes () em JDBC também serve para bytea (por experiência anterior). Portanto, isso explica por que usar-streams não tem efeito, ambos assumem '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 );
 }
}

Isto resulta em:

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

Atualizar A próxima pergunta lógica é: "por que não mudar as definições da tabela manualmente para bytea" e manter o (@Lob + byte [])? Isso faz o trabalho, ATÉ tentar armazenar um byte nulo []. Que o driver postgreSQL pensa que é uma expressão de tipo OID e o tipo de coluna é bytea - isso ocorre porque hibernate (corretamente) chama JDBC.setNull () em vez de JDBC.setBytes (null) que o driver PG espera.

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

O sistema de tipo no hibernate é atualmente um 'trabalho em andamento' (de acordo com o comentário de depreciação 3.5.5). Na verdade, grande parte do código 3.5.5 está obsoleto, é difícil saber o que olhar ao subclassificar o PostgreSQLDialect).

AFAKT, Types.BLOB / 'oid' no postgresql deve ser mapeado para algum tipo personalizado que usa o acesso JDBC do estilo OID (ou seja, objeto PostgresqlBlobType e NOT MaterializedBlobType). Na verdade, nunca usei o Blobs com o postgresql com sucesso, mas sei que o bytea simplesmente funciona como um / seria de esperar.

No momento, estou olhando para BatchUpdateException - é possível que o driver não ofereça suporte ao envio em lote.


Excelente citação de 2004: "Para resumir minhas divagações, eu diria que devemos esperar que o driver JDBC faça os LOBs corretamente antes de alterar o Hibernate."

Referências:

Justin
fonte
Isso parece ter sido corrigido no 3.6, não tenho certeza sobre o 3.5.6; a classe MaterializedBlobType foi totalmente reescrita de 3.5.5> 3.6. O tipo OID agora funciona desde que mudaram a implementação.
Justin de
Agradável! Eu me pergunto qual problema Jira está rastreando esta reescrita, se houver (talvez a reescrita seja uma consequência de uma mudança mais profunda). Seria bom fazer o backport das mudanças no 3.5, se possível. Más notícias se não for possível.
Pascal Thivent de
Na verdade, meu teste me deu um falso positivo na primeira vez (sabia que deveria ter esperado!) - ainda não foi corrigido, o bug acabou de ser movido para BlobTypeDescriptor.
Justin
Obrigado. @Type (type = "org.hibernate.type.BinaryType") funcionou para mim para uma tabela que armazena arquivos PDF. Migrei um banco de dados Oracle para Postgres usando Oracle-To-PostgreSQL da Intelligent Converters, e ele converteu e inseriu automaticamente de BLOB para BYTEA, mas BlobType não funcionou para mim.
jmoran

Respostas:

68

Qual é a maneira portátil de anotar uma propriedade byte []?

Depende do que você quer. JPA pode persistir um não anotado byte[]. Da especificação JPA 2.0:

11.1.6 Anotação Básica

A Basicanotação é o tipo mais simples de mapeamento para uma coluna do banco de dados. A Basicanotação pode ser aplicado a uma propriedade ou instância variável persistente de qualquer um dos seguintes tipos: primitiva Java, tipos, envoltórios dos tipos de primitivas, 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[], enumerações, e qualquer outro tipo que implementa Serializable. Conforme descrito na Seção 2.8, o uso da Basicanotação é opcional para campos persistentes e propriedades desses tipos. Se a anotação Básica não for especificada para tal campo ou propriedade, os valores padrão da anotação Básica serão aplicados.

E o Hibernate irá mapeá-lo "por padrão" para um SQL VARBINARY(ou um SQL LONGVARBINARYdependendo do Columntamanho?) Que o PostgreSQL manipula com um bytea.

Mas se você quiser que o byte[]seja armazenado em um objeto grande, você deve usar a @Lob. Da especificação:

11.1.24 Anotação Lob

Uma Lobanotação especifica que uma propriedade ou campo persistente deve ser persistido como um objeto grande para um tipo de objeto grande suportado por banco de dados. Os aplicativos portáteis devem usar a Lobanotação ao mapear para um Lobtipo de banco de dados . A Lobanotação pode ser usada em conjunto com a anotação Básica ou com a ElementCollectionanotação quando o valor da coleção de elementos é do tipo básico. A Lobpode ser um tipo binário ou de caractere. O Lobtipo é inferido a partir do tipo de campo ou propriedade persistente e, exceto para tipos de string e caracteres, o padrão é Blob.

E o Hibernate irá mapeá-lo para um SQL BLOBque o PostgreSQL manipula com um oid .

Isso foi corrigido em alguma versão recente do hibernate?

Bem, o problema é que não sei exatamente qual é o problema. Mas posso pelo menos dizer que nada mudou desde 3.5.0-Beta-2 (que é onde uma mudança foi introduzida) no branch 3.5.x.

Mas meu entendimento de questões como HHH-4876 , HHH-4617 e de PostgreSQL e BLOBs (mencionados no javadoc do PostgreSQLDialect) é que você deve definir a seguinte propriedade

hibernate.jdbc.use_streams_for_binary=false

se quiser usar o oidie byte[]com @Lob(que é meu entendimento já VARBINARYque não é o que você quer com o Oracle). Você tentou isso?

Como alternativa, o HHH-4876 sugere o uso do obsoleto PrimitiveByteArrayBlobTypepara obter o comportamento antigo (pré Hibernate 3.5).

Referências

  • Especificação JPA 2.0
    • Seção 2.8 "Padrões de mapeamento para campos ou propriedades sem relação"
    • Seção 11.1.6 "Anotação básica"
    • Seção 11.1.24 "Anotação Lob"

Recursos

Pascal Thivent
fonte
OMG, eu percebo que essa pergunta mudou muito desde que comecei a responder. Lerei todas as alterações posteriormente e atualizarei minhas respostas após digerir as alterações, se necessário.
Pascal Thivent
É bom ver as especificações, então hibernar é totalmente correto para mapear (@Lob + byte []) para um tipo de objeto grande suportado. No Postgresql existem 2 (bytea ou oid). No entanto, enquanto o hibernate 3.5 mapeia para oid (por padrão), ele lê usando JDBC getBytes (), qual driver PGSQL retorna o oid de 6 bytes em vez dos dados. Observe também que o autor do blog respondeu da forma mais útil (em seu blog) desde que a pergunta foi feita.
Justin,
@Justin No entanto, enquanto o hibernate 3.5 mapeia para oid (por padrão) ele lê usando JDBC getBytes () qual driver PGSQL retorna o oid de 6 bytes em vez dos dados - isso ocorre ao usar hibernate.jdbc.use_streams_for_binary=falsetambém? (vou verificar o que Steve disse agora).
Pascal Thivent
Vou tentar especificá-lo no arquivo de propriedades, no entanto, PostgreSQLDialect tem useInputStreamToInsertBlob () retornando falso, então presumo que sim - já que não estou definindo explicitamente essa propriedade.
Justin,
Depois de definir esta propriedade (como verdadeiro ou falso), recebo uma exceção de tempo de execução: ERRO: a coluna "assinatura" é do tipo bytea, mas a expressão é do tipo oid ". Devo mencionar que estou usando o hibernate 3.5.5.Final + PG 8,2 motoristas.
Justin
10

Aqui vai o que O'reilly Enterprise JavaBeans 3.0 diz

JDBC possui tipos especiais para esses objetos muito grandes. O tipo java.sql.Blob representa dados binários e java.sql.Clob representa dados de caractere.

Aqui vai o código-fonte do PostgreSQLDialect

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

Então o que você pode fazer

Substitua PostgreSQLDialect da seguinte maneira

public class CustomPostgreSQLDialect extends PostgreSQLDialect {

    public CustomPostgreSQLDialect() {
        super();

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

Agora é só definir seu dialeto personalizado

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

E use sua anotação JPA @Lob portátil

@Lob
public byte[] getValueBuffer() {

ATUALIZAR

Aqui foi extraído aqui

Eu tenho um aplicativo em execução no hibernate 3.3.2 e ele funciona bem , com todos os campos de blob usando oid (byte [] em java)

...

Migrando para o hibernate 3.5, todos os campos de blob não funcionam mais e o log do servidor mostra: ERROR org.hibernate.util.JDBCExceptionReporter - ERROR: a coluna é do tipo oid, mas a expressão é do tipo bytea

que pode ser explicado aqui

Geralmente não é um bug no PG JDBC , mas uma mudança na implementação padrão do Hibernate na versão 3.5 . Na minha situação, definir a propriedade compatível na conexão não ajudou .

...

Muito mais isso que vi no 3.5 - beta 2, e não sei se isso foi corrigido é o Hibernate - sem a anotação @Type - vai criar coluna do tipo oid automaticamente, mas vou tentar ler isso como bytea

Interessante é porque quando ele mapeia Types.BOLB como bytea (Veja CustomPostgreSQLDialect), ele obtém

Não é possível executar a atualização em lote do JDBC

ao inserir ou atualizar

Arthur Ronald
fonte
Esta solução parece gloriosa, estou tentando agora.
Justin
Isso gera o DDL correto, mas falha no tempo de execução: recebo um java.sql.BatchUpdateException ao tentar um objeto com uma propriedade blob.
Justin,
@Justin Tente um cenário semelhante usando Oracle em vez de PostgreSQL e veja o que você obtém. BatchUpdateException tem a ver com erros que ocorrem durante uma operação de atualização em lote.
Arthur Ronald
Na verdade, o que eu realmente quero não é mapear BLOB para "bytea", mas sim mapear (byte [] + @Lob) a combinação de anotações para Types.VARBINARY!
Justin
7

Estou usando o Hibernate 4.2.7.SP1 com Postgres 9.3 e o seguinte funciona para mim:

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

já que o Oracle não tem problemas com isso, e para Postgres estou usando um dialeto personalizado:

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

considero a vantagem desta solução, que posso manter os potes de hibernação intocados.

Para mais problemas de compatibilidade do Postgres / Oracle com o Hibernate, veja minha postagem no blog .

Peter Butkovic
fonte
2
Funcionou para mim usando Hibernate 4.3.6 e Postgresql 9.3 com extensão Postgresql9Dialect. Obrigado!
Andrés Oviedo
Funciona com Hibernate 5.3.7.Final e Postgres95Dialect. Thx
Bernhard Kern
6

Eu finalmente consegui fazer isso funcionar. Ele expande a solução de A. Garcia, no entanto, como o problema está no tipo de hibernação MaterializedBlob, apenas o mapeamento de Blob> bytea não é suficiente, precisamos de uma substituição para MaterializedBlobType que funciona com o suporte de blob quebrado do hibernação. Essa implementação só funciona com bytea, mas talvez o cara do problema do JIRA que queria OID pudesse contribuir com uma implementação de OID.

Infelizmente, substituir esses tipos em tempo de execução é uma dor, pois eles deveriam fazer parte do dialeto. Se este aprimoramento do JIRA chegar ao 3.6, isso seria possível.

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

Muito disso provavelmente pode ser estático (getBinder () realmente precisa de uma nova instância?), Mas eu realmente não entendo a hibernação interna, então isso é principalmente copiar + colar + modificar.

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);
 }
}
Justin
fonte
1 para sua pesquisa. Parabéns. Apenas um conselho: prefira editar sua própria pergunta / resposta em até 8 vezes. Caso contrário, sua pergunta / resposta se tornará wiki da comunidade e você não ganhará reputação e o voto UP não será mais computado
Arthur Ronald
Viver e aprender, suponho, tive tantas edições, que sempre me esquecia de fazer uma coisa ou outra com meu ambiente de teste.
Justin
Mesmo aqui, +1 para a pesquisa e uma solução para sua situação.
Pascal Thivent,
alguma chance de solução com a versão 4.2.x Hibernate? O interior do Hibernate mudou um pouco (comentei o problema referido: hibernate.atlassian.net/browse/HHH-5584 ).
Peter Butkovic
2

Corrigi meu problema adicionando a anotação de @Lob que criará o byte [] no oracle como blob, mas essa anotação criará o campo como oid que não funciona corretamente. postgres como abaixo

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

Também é necessário substituir o parâmetro para o dialeto

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

mais dicas podem ser encontradas nela: https://dzone.com/articles/postgres-and-oracle

El Ghandor Yasser
fonte
0

Consegui funcionar substituindo a anotação por arquivo XML para Postgres. A anotação é mantida para Oracle. Em minha opinião, neste caso, seria melhor substituir o mapeamento desta entidade problemática com mapeamento xml. Podemos substituir entidades únicas / múltiplas com mapeamento xml. Portanto, usaríamos anotações para nosso banco de dados com suporte principal e um arquivo xml para cada banco de dados.

Nota: só precisamos substituir uma única classe, então não é um grande problema. Leia mais no meu exemplo de exemplo para substituir a anotação com XML

Vinh Vo
fonte
0

No Postgres @Lob está quebrando para byte [] ao tentar salvá-lo como oid, e para String também ocorre o mesmo problema. O código abaixo está quebrando no postgres que está funcionando bem no oracle.

@Lob
private String stringField;

e

@Lob
private byte[]   someByteStream;

A fim de corrigir acima no postgres, escrevemos abaixo hibernate.dialect personalizado

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

Agora configure o dialeto personalizado em hibernação

hibernate.dialect=X.Y.Z.PostgreSQLDialectCustom   

XYZ é o nome do pacote.

Agora está funcionando bem. NOTA- Minha versão Hibernate - 5.2.8.Final Postgres version- 9.6.3

Gajendra Kumar
fonte
0

Obrigado Justin, Pascal por me guiar na direção certa. Eu também estava enfrentando o mesmo problema com o Hibernate 3.5.3. Sua pesquisa e dicas para as aulas certas me ajudaram a identificar o problema e a consertar.

Para o benefício de quem ainda está preso ao Hibernate 3.5 e usando a combinação oid + byte [] + @LoB, a seguir está o que eu fiz para corrigir o problema.

  1. Eu criei um BlobType customizado estendendo MaterializedBlobType e substituindo os métodos set e get com o acesso de estilo 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. Registre o CustomBlobType com Hibernate. A seguir está o que eu fiz para conseguir isso.

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