правильная аннотация гибернации для byte []

120

У меня есть приложение, использующее аннотации hibernate 3.1 и JPA. В нем есть несколько объектов с атрибутами byte [] (размером от 1k до 200k). Он использует аннотацию JPA @Lob, и hibernate 3.1 может нормально читать их во всех основных базах данных - похоже, он скрывает особенности поставщика JDBC Blob (как и должно быть).

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

Нам пришлось перейти на 3.5, когда мы обнаружили, что hibernate 3.5 нарушает (и не исправляет) эту комбинацию аннотаций в postgresql (без обходного пути). Я пока не нашел четкого исправления, но заметил, что если я просто удалю @Lob, он будет использовать bytea типа postgresql (который работает, но только на 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.

Я ищу способ иметь один аннотированный класс (со свойством blob), который можно переносить в основные базы данных.

  • Каков переносимый способ аннотировать свойство byte []?
  • Исправлено ли это в какой-нибудь последней версии гибернации?

Обновление: после прочтения этого блога я, наконец, понял, каким было исходное решение проблемы JIRA: по-видимому, вы должны удалить @Lob и аннотировать свойство как:

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

Однако у меня это не работает - я все равно получаю OID вместо bytea; Тем не менее, это сработало для автора проблемы JIRA, который, похоже, захотел oid.

После ответа А. Гарсиа я попробовал эту комбинацию, которая действительно работает на postgresql, но не на oracle.

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

Что мне действительно нужно сделать, так это контролировать, какие @ org.hibernate.annotations. Введите комбинацию (@Lob + byte [] будет отображена) на (в postgresql).


Вот фрагмент из 3.5.5.Final из MaterializedBlobType (sql type Blob). Согласно блогу Стива, postgresql хочет, чтобы вы использовали Streams для bytea (не спрашивайте меня, почему) и пользовательский тип Blob postgresql для oids. Также обратите внимание, что использование setBytes () в JDBC также предназначено для bytea (из прошлого опыта). Итак, это объясняет, почему потоки использования не влияют на них, они оба принимают «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 );
 }
}

Это приводит к:

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

Обновление Следующий логичный вопрос: «Почему бы просто не изменить определения таблиц вручную на bytea» и сохранить (@Lob + byte [])? Это делает работу, ПОКА вы пытаетесь сохранить нулевые байты []. Драйвер postgreSQL считает, что это выражение типа OID, а тип столбца - bytea - это потому, что hibernate (правильно) вызывает JDBC.setNull () вместо JDBC.setBytes (null), который ожидает драйвер PG.

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

Система типов в спящем режиме в настоящее время находится в стадии разработки (согласно устаревшему комментарию 3.5.5). Фактически, так много кода 3.5.5 устарело, трудно понять, на что обращать внимание при подклассе PostgreSQLDialect).

AFAKT, Types.BLOB / 'oid' в postgresql должны быть сопоставлены с некоторым настраиваемым типом, который использует доступ JDBC в стиле OID (например, объект PostgresqlBlobType и НЕ MaterializedBlobType). Я никогда успешно не использовал Blobs с postgresql, но я знаю, что bytea просто работает так, как я ожидал.

В настоящее время я смотрю на исключение BatchUpdateException - возможно, драйвер не поддерживает пакетную обработку.


Замечательная цитата из 2004 года: «Подводя итог своим рассуждениям, я бы сказал, что нам следует дождаться, пока драйвер JDBC будет правильно выполнять большие объекты, прежде чем менять Hibernate».

Ссылки:

Джастин
источник
Кажется, это было исправлено в версии 3.6, не уверен, что насчет 3.5.6; класс MaterializedBlobType был полностью переписан с 3.5.5> 3.6. Тип OID теперь работает, так как они изменили реализацию.
Джастин,
Ницца! Интересно, какая проблема Jira отслеживает эту перезапись, если таковая имеется (возможно, перезапись является следствием более глубокого изменения). Было бы неплохо, если возможно, выполнить резервное копирование изменений в 3.5. Плохие новости, если это невозможно.
Паскаль Тивент,
На самом деле мой тест дал мне ложное срабатывание в первый раз (знал, что мне следовало подождать!) - он все еще не исправлен, ошибка только что переместилась в BlobTypeDescriptor.
Джастин
Спасибо. @Type (type = "org.hibernate.type.BinaryType") работал у меня для таблицы, в которой хранятся файлы PDF. Я перенес базу данных из Oracle в Postgres, используя Oracle-To-PostgreSQL из Intelligent Converters, и она автоматически преобразовала и вставила из BLOB в BYTEA, но BlobType у меня не работал.
jmoran

Ответы:

68

Каков переносимый способ аннотировать свойство byte []?

Все зависит от того, чего вы хотите. JPA может сохранять не аннотированный byte[]. Из спецификации JPA 2.0:

11.1.6 Базовая аннотация

BasicАннотацию простейшего типа отображения для столбца базы данных. BasicАннотации могут быть применены к постоянной собственности или переменному экземпляра любым из следующих типов: Java - примитив, типов, обертки примитивных типов, 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[], перечисления и любой другой типа , который реализует Serializable, Как описано в разделе 2.8, использование Basicаннотации необязательно для постоянных полей и свойств этих типов. Если аннотация Basic не указана для такого поля или свойства, будут применяться значения по умолчанию аннотации Basic.

И Hibernate отобразит его «по умолчанию» на SQL VARBINARY(или SQL, в LONGVARBINARYзависимости от Columnразмера?), Который PostgreSQL обрабатывает с расширением bytea.

Но если вы хотите, byte[]чтобы файл хранился в большом объекте, вам следует использовать файл @Lob. Из спецификации:

11.1.24 Аннотации Lob

A Lobаннотаций указывает , что постоянное свойство или поле должно быть сохранено в качестве крупного объекта к большому типу объектов базы данных , поддерживаемые. Переносимые приложения должны использовать Lobаннотацию при сопоставлении с Lobтипом базы данных . LobАннотации могут быть использованы в сочетании с базовой аннотацией или с ElementCollectionаннотациями , когда значение элемента коллекции имеет базовый типа. Тип A Lobможет быть двоичным или символьным. LobТип выводится из типа постоянного поля или свойства, а для символьных строк и типов, по умолчанию , за исключением Blob.

И Hibernate отобразит его на SQL, BLOBкоторый PostgreSQL обрабатывает с расширением oid .

Исправлено ли это в какой-нибудь последней версии гибернации?

Что ж, проблема в том, что я не знаю, в чем именно проблема. Но я могу по крайней мере сказать, что ничего не изменилось с версии 3.5.0-Beta-2 (в которую были внесены изменения) в ветке 3.5.x.

Но мое понимание проблем , как HHH-4876 , HHH-4617 и в PostgreSQL и BLOB - (упоминается в Javadoc из PostgreSQLDialect) является то , что вы должны установить следующее свойство

hibernate.jdbc.use_streams_for_binary=false

если вы хотите использовать oidто есть byte[]с @Lob(это я понимаю, поскольку VARBINARYэто не то, что вы хотите с Oracle). Вы пробовали это?

В качестве альтернативы HHH-4876 предлагает использовать устаревшее, PrimitiveByteArrayBlobTypeчтобы получить старое поведение (до Hibernate 3.5).

Ссылки

  • JPA 2.0 Спецификация
    • Раздел 2.8 «Отображение значений по умолчанию для полей или свойств, не связанных с отношениями»
    • Раздел 11.1.6 «Базовая аннотация»
    • Раздел 11.1.24 «Аннотация Lob»

Ресурсы

Паскаль Тивент
источник
OMG, я понимаю, что этот вопрос сильно изменился с тех пор, как я начал отвечать. Позже прочитаю все изменения и при необходимости обновлю свои ответы после переваривания изменений.
Паскаль Тивент
Приятно видеть спецификацию, поэтому спящий режим совершенно правильно отображать (@Lob + byte []) в большой поддерживаемый тип объекта. В Postgresql их 2 (bytea или oid). Однако, хотя hibernate 3.5 сопоставляется с oid (по умолчанию), он читает с помощью JDBC getBytes (), драйвер PGSQL которого возвращает 6-байтовый oid вместо данных. Также обратите внимание, что автор блога ответил наиболее полезно (в своем блоге) с тех пор, как был задан вопрос.
Джастин
@Justin Однако, в то время как hibernate 3.5 сопоставляется с oid (по умолчанию), он читает с помощью JDBC getBytes (), какой драйвер PGSQL возвращает 6-байтовый oid вместо данных - происходит ли это при использовании hibernate.jdbc.use_streams_for_binary=falseтоже? (сейчас проверю, что сказал Стив).
Паскаль Тивент
Я попытаюсь указать это в файле свойств, однако PostgreSQLDialect имеет функцию useInputStreamToInsertBlob (), возвращающую false, поэтому я предполагаю, что это так, поскольку я не устанавливаю это свойство явно.
Джастин
После установки этого свойства (true или false) я получаю исключение времени выполнения: ОШИБКА: столбец «подпись» имеет тип bytea, а выражение - тип oid ». Я должен упомянуть, что использую hibernate 3.5.5.Final + PG Драйверы 8.2.
Джастин,
10

Вот что говорит Oreilly Enterprise JavaBeans, 3.0.

В JDBC есть специальные типы для этих очень больших объектов. Тип java.sql.Blob представляет двоичные данные , а java.sql.Clob представляет символьные данные.

Вот исходный код PostgreSQLDialect

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

Так что ты можешь сделать

Переопределите PostgreSQLDialect следующим образом

public class CustomPostgreSQLDialect extends PostgreSQLDialect {

    public CustomPostgreSQLDialect() {
        super();

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

Теперь просто определите свой собственный диалект

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

И используйте свою портативную аннотацию JPA @Lob

@Lob
public byte[] getValueBuffer() {

ОБНОВИТЬ

Здесь было добыто здесь

У меня запущено приложение спящем режиме 3.3.2, и приложения работают нормально , все поля blob используют oid (byte [] в java)

...

При переходе в спящий режим 3.5 все поля больших двоичных объектов больше не работают , и в журнале сервера отображается: ERROR org.hibernate.util.JDBCExceptionReporter - ОШИБКА: столбец имеет тип oid, но выражение имеет тип bytea

что можно объяснить здесь

Как правило, это не ошибка PG JDBC , а изменение стандартной реализации Hibernate в версии 3.5 . В моей ситуации установка совместимого свойства при подключении не помогла .

...

Гораздо больше того, что я видел в 3.5 - beta 2, и я не знаю, было ли это исправлено, - это Hibernate - без аннотации @Type - автоматически создаст столбец типа oid, но попытается прочитать это как bytea

Интересно, потому что когда он отображает Types.BOLB как bytea (см. CustomPostgreSQLDialect), он получает

Не удалось выполнить пакетное обновление JDBC

при вставке или обновлении

Артур Рональд
источник
Это решение выглядит великолепно, я пробую его сейчас.
Джастин
Это генерирует правильный DDL, но не работает во время выполнения: я получаю исключение java.sql.BatchUpdateException при попытке доступа к объекту со свойством blob.
Джастин
@Justin Попробуйте аналогичный сценарий, используя Oracle вместо PostgreSQL, и посмотрите, что у вас получится. Исключение BatchUpdateException связано с ошибками, возникающими во время операции пакетного обновления.
Артур Рональд
На самом деле я действительно хочу не отображать BLOB в «bytea», а вместо этого отображать комбинацию аннотаций (byte [] + @Lob) в Types.VARBINARY!
Джастин
@Justin См. Download-llnw.oracle.com/javase/1.5.0/docs/api/java/sql/…
Артур Рональд
7

Я использую Hibernate 4.2.7.SP1 с Postgres 9.3, и у меня следующие работы:

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

поскольку у Oracle нет проблем с этим, а для Postgres я использую собственный диалект:

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

Преимущество этого решения, которое я считаю, состоит в том, что я могу не трогать банки спящего режима.

Дополнительные сведения о проблемах совместимости Postgres / Oracle с Hibernate см. В моем сообщении в блоге .

Петр Буткович
источник
2
Работал для меня с использованием Hibernate 4.3.6 и Postgresql 9.3 с расширением Postgresql9Dialect. Спасибо!
Andrés Oviedo
Работает с Hibernate 5.3.7.Final и Postgres95Dialect. Спасибо
Бернхард Керн
6

Наконец-то у меня это работает. Он расширяет решение от А. Гарсиа, однако, поскольку проблема заключается в типе гибернации Тип MaterializedBlob, просто сопоставление Blob> bytea недостаточно, нам нужна замена для MaterializedBlobType, который работает с поддержкой сломанных BLOB-объектов в гибернации. Эта реализация работает только с bytea, но, возможно, парень из проблемы JIRA, которому нужен OID, мог бы внести свой вклад в реализацию OID.

К сожалению, замена этих типов во время выполнения - это боль, поскольку они должны быть частью диалекта. Если бы только это расширение JIRA попало в 3.6, это было бы возможно.

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

Большая часть этого, вероятно, может быть статической (действительно ли getBinder () нужен новый экземпляр?), Но я действительно не понимаю внутреннего спящего режима, поэтому это в основном копирование + вставка + изменение.

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 за ваше исследование. Поздравляю. Просто совет: предпочитайте редактировать свой вопрос / ответ до 8 раз. В противном случае ваш вопрос / ответ станет вики сообщества, и вы не получите репутации, а голос «UP» больше не будет подсчитываться
Артур Рональд,
Живи и учись, я полагаю, у меня было так много правок, что я постоянно забывала делать то или иное с моей тестовой средой.
Джастин
То же самое здесь, +1 для исследования и в растворе для вашей ситуации.
Паскаль Тивент,
есть ли шанс на решение с версией Hibernate 4.2.x? Внутреннее устройство Hibernate немного изменилось (я прокомментировал проблему: hibernate.atlassian.net/browse/HHH-5584 ).
Петр Буткович
2

Я исправил свою проблему, добавив аннотацию @Lob, которая создаст byte [] в oracle как blob, но эта аннотация создаст поле как oid, которое не работает должным образом. Чтобы сделать byte [] созданным как bytea, я сделал клиентский диалект для postgres, как показано ниже

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

Также необходимо переопределить параметр для диалекта

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

больше подсказок можно найти по ней: https://dzone.com/articles/postgres-and-oracle

Эль Гандор Яссер
источник
0

Я получил это, переопределив аннотацию с файлом XML для Postgres. Аннотация сохраняется для Oracle. На мой взгляд, в этом случае было бы лучше переопределить отображение этой проблемы с отображением xml. Мы можем переопределить один / несколько объектов с помощью сопоставления xml. Таким образом, мы будем использовать аннотацию для нашей базы данных, которая в основном поддерживается, и файл xml для каждой другой базы данных.

Примечание: нам просто нужно переопределить один единственный класс, так что это не имеет большого значения. Подробнее читайте в моем примере Пример переопределения аннотации с помощью XML

Винь Во
источник
0

В Postgres @Lob ломается для byte [], поскольку он пытается сохранить его как oid, и для String также возникает такая же проблема. Ниже код ломается на postgres, который отлично работает с oracle.

@Lob
private String stringField;

и

@Lob
private byte[]   someByteStream;

Чтобы исправить выше, на postgres ниже написано 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);
  }
}

Теперь настройте собственный диалект в спящем режиме

hibernate.dialect=X.Y.Z.PostgreSQLDialectCustom   

XYZ - это имя пакета.

Теперь он работает нормально. ПРИМЕЧАНИЕ. Моя версия Hibernate - 5.2.8. Окончательная версия Postgres - 9.6.3.

Гаджендра Кумар
источник
0

Спасибо Джастину, Паскаль за то, что направил меня в правильном направлении. Я также столкнулся с той же проблемой с Hibernate 3.5.3. Ваши исследования и указатели на нужные классы помогли мне определить проблему и исправить.

Для тех, кто все еще придерживается Hibernate 3.5 и использует комбинацию oid + byte [] + @LoB, вот что я сделал, чтобы исправить эту проблему.

  1. Я создал собственный BlobType, расширяющий MaterializedBlobType и переопределяющий методы set и get с доступом в стиле 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. Зарегистрируйте CustomBlobType в Hibernate. Вот что я сделал для этого.

      hibernateConfiguration= new AnnotationConfiguration();
      Mappings mappings = hibernateConfiguration.createMappings();
      mappings.addTypeDef("materialized_blob", "x.y.z.BlobType", null);
Нибин Джейкоб Паникер
источник