byte []的正确hibernate注释

时间:2010-09-09 14:10:42

标签: java database hibernate postgresql blob

我有一个使用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,当我们在postgresql中发现hibernate 3.5 breaks (and won't fix)这个注释组合时(没有解决方法)。到目前为止我还没有找到明确的解决方法,但我注意到如果我只是删除了@Lob,它使用了postgresql类型的bytea(有效,但仅适用于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 []属性的可移植方式是什么?
  • 这是在最新版本的hibernate中修复的吗?

更新 阅读this blog之后,我终于弄明白JIRA问题的原始解决方法是什么:显然你应该删除@Lob并注释该属性为:

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

但是,这对我来说不起作用 - 我仍然得到OID而不是bytea;然而,它对JIRA问题的作者起了作用,他似乎想要oid。

在A. Garcia的回答之后,我尝试了这个组合,它实际上可以在postgresql上运行,但不在oracle上运行。

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

我真正需要做的是控制哪个@ org.hibernate.annotations.Type组合(@Lob + byte []被映射)到(在postgresql上)。


以下是来自MaterializedBlobType(sql类型Blob)的3.5.5.Final的片段。根据Steve的博客,postgresql希望你使用Streams for bytea(不要问我为什么)和postgresql的自定义Blob类型为oids。另请注意,在JDBC上使用setBytes()也适用于bytea(来自过去的经验)。所以这解释了为什么use-streams没有影响它们都假设'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 [])?此 工作, UNTIL 您尝试存储空字节[]。 postgreSQL驱动程序认为是OID类型表达式,列类型是bytea - 这是因为hibernate(正确地)调用JDBC.setNull()而不是PG驱动程序期望的JDBC.setBytes(null)。

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

hibernate中的类型系统目前是“正在进行中的工作”(根据3.5.5弃用评论)。实际上很多3.5.5代码都被弃用,很难知道在对PostgreSQLDialect进行子类化时要注意什么。

AFAKT,postgresql上的Types.BLOB /'oid'应该映射到一些使用OID样式JDBC访问的自定义类型(即PostgresqlBlobType对象和NOT MaterializedBlobType)。我从来没有真正成功地将blob与postgresql一起使用,但我确实知道bytea只是像我期望的那样工作。

我目前正在查看BatchUpdateException - 驱动程序可能不支持批处理。


2004年的精彩报价:  “总结我的ramblings,我会说他们应该等到JDBC驱动程序在更改Hibernate之前正确地执行LOB。”

参考文献:

8 个答案:

答案 0 :(得分:60)

  

注释byte []属性的可移植方式是什么?

这取决于你想要什么。 JPA可以保留未注释的byte[]。从JPA 2.0规范:

  

11.1.6基本注释

     

Basic注释是最简单的   映射到数据库列的类型。   可以应用Basic注释   持久化属性或实例   以下任何一项的变量   types:Java原语,类型,包装器   原始类型,   java.lang.String,   java.math.BigInteger,   java.math.BigDecimal,   java.util.Date,   java.util.Calendarjava.sql.Date,   java.sql.Timejava.sql.Timestamp,    byte[]Byte[] char[]Character[],枚举以及其他任何内容   实现Serializable的类型。   如第2.8节所述,使用   Basic注释的选项是可选的   用于持久字段和属性   这些类型。如果是基本的   没有为这样的注释指定注释   字段或属性,默认值   基本注释将适用。

Hibernate将“默认”映射到SQL VARBINARY(或SQL LONGVARBINARY,具体取决于Column大小?),PostgreSQL处理{{1} }。

但是如果您希望bytea存储在大对象中,则应使用byte[]。来自规范:

  

11.1.24 Lob Annotation

     

@Lob注释指定a   持久性属性或字段应该是   坚持作为一个大对象   数据库支持的大对象类型。   便携式应用程序应该使用   映射到a时的Lob注释   数据库Lob类型。 Lob注释   可以与。一起使用   基本注释或与   当Lob注释时   元素集合值是基本的   类型。 ElementCollection可以是二进制或   字符类型。 Lob类型是   从类型推断出来   持久的领域或财产,   除了字符串和字符类型,   默认为Blob。

Hibernate会将它映射到PostgreSQL用Lob处理的SQL BLOB

  

这是在最新版本的hibernate中修复的吗?

嗯,问题是我不知道究竟是什么问题。但我至少可以说自3.5.0分支机构3.5.0-Beta-2(这是已经引入更改的地方)以来没有任何变化。

但我对HHH-4876HHH-4617PostgreSQL and BLOBs(在oid的javadoc中提到)等问题的理解是,您应该设置以下属性

PostgreSQLDialect

如果你想使用hibernate.jdbc.use_streams_for_binary=false oid byte[](这是我的理解,因为@Lob不是你想要的Oracle)。你试过这个吗?

作为替代方案,HHH-4876建议使用已弃用的VARBINARY来获取旧行为(在Hibernate 3.5之前)。

参考

  • JPA 2.0规范
    • 第2.8节“映射非关系字段或属性的默认值”
    • 第11.1.6节“基本注释”
    • 第11.1.24节“Lob注释”

资源

答案 1 :(得分:9)

这就是O'reilly 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() {

<强>更新

此处已提取here

  

我有一个在 hibernate 3.3.2中运行的应用程序,应用程序运行正常,所有blob字段都使用oid(java中的byte [])

...

  

迁移到 hibernate 3.5所有blob字段不再起作用,服务器日志显示:ERROR org.hibernate.util.JDBCExceptionReporter - ERROR:列的类型为oid,但表达式的类型为bytea < / p>

可以解释 here

  

这一般不是PG JDBC中的错误但是改变了3.5版本中Hibernate的默认实现。在我的情况下在连接上设置兼容属性   没有帮助

...

  

我在3.5 - beta 2中看到的更多,我不知道这是否已修复是Hibernate - 没有@Type注释 - 会自动创建oid类型的列,但会尝试读取这个as bytea

有趣的是因为当他将Types.BOLB映射为bytea时(参见CustomPostgreSQLDialect)他得到了

  

无法执行JDBC批量更新

插入或更新时

答案 2 :(得分:6)

我终于有了这个工作。然而,它扩展了A. Garcia的解决方案,因为问题在于hibernate类型MaterializedBlob类型只是映射Blob&gt; bytea是不够的,我们需要替换MaterializedBlobType,它适用于hibernates破碎的blob支持。此实现仅适用于bytea,但是JIRA问题中想要OID的人可能会提供OID实现。

遗憾的是,在运行时替换这些类型很痛苦,因为它们应该成为Dialect的一部分。 如果只有this JIRA enhanement进入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()确实需要一个新的实例吗?),但我并不真正理解hibernate内部所以这主要是复制+粘贴+修改。

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

答案 3 :(得分:4)

我正在使用Hibernate 4.2.7.SP1和Postgres 9.3以及以下作品:

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

因为甲骨文没有遇到麻烦,对于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);
  }
}

我认为这个解决方案的优点是,我可以保持hibernate jar不受影响。

有关Hibernate的更多Postgres / Oracle兼容性问题,请参阅我的blog post

答案 4 :(得分:1)

我通过添加@Lob的注释来修复我的问题,它将在oracle中创建byte []作为blob,但是这个注释会将字段创建为oid而不能正常工作,要使byte []创建为bytea i made客户方言对于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);
  }
 }

还需要覆盖Dialect的参数

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

可以找到更多提示:https://dzone.com/articles/postgres-and-oracle

答案 5 :(得分:0)

我通过使用Postgres的XML文件覆盖注释来实现它。为Oracle保留注释。在我看来,在这种情况下,我们最好用xml映射覆盖这个麻烦的映射。我们可以使用xml映射覆盖单个/多个实体。因此,我们将为主要支持的数据库使用注释,并为每个其他数据库使用xml文件。

注意:我们只需要覆盖一个单独的类,所以这不是什么大问题。 从我的例子中了解更多 Example to override annotation with XML

答案 6 :(得分:0)

On Postgres @Lob在尝试将其保存为oid时会破坏byte [],而对于String也会出现同样的问题。下面的代码在postgres上打破了,这在oracle上工作正常。

@Lob
private String stringField;

@Lob
private byte[]   someByteStream;

为了修复上面的postgres,下面写了自定义的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中配置自定义方言

hibernate.dialect=X.Y.Z.PostgreSQLDialectCustom   

X.Y.Z是包名。

现在工作正常。 注意 - 我的Hibernate版本 - 5.2.8.Final       Postgres版本 - 9.6.3

答案 7 :(得分:0)

感谢Justin,Pascal指导我走向正确的方向。我也遇到了与Hibernate 3.5.3相同的问题。您的研究和对正确课程的指示帮助我确定了问题并做了修复。

为了那些仍然坚持使用Hibernate 3.5并使用oid + byte [] + @LoB组合的人的好处,以下是我为解决这个问题所做的工作。

  1. 我创建了一个扩展MaterializedBlobType的自定义BlobType,并使用oid样式访问覆盖了set和get方法。

    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. 使用Hibernate注册CustomBlobType。以下是我为实现这一目标所做的工作。

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