字节 [] 的正确休眠注释

Posted

技术标签:

【中文标题】字节 [] 的正确休眠注释【英文标题】:proper hibernate annotation for byte[] 【发布时间】:2011-04-10 07:04:06 【问题描述】:

我有一个使用 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;
  

当我们在 postgresql 中发现 hibernate 3.5 breaks (and won't fix) 这个注解组合时,我们不得不升级到 3.5(没有解决方法)。到目前为止,我还没有找到明确的解决方法,但我确实注意到,如果我只是删除 @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 的 sn-p。根据 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[])?这确实工作,直到你尝试存储一个空字节[]。 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 年的精彩引述: “总结一下我的胡言乱语,我想说我们应该等待 JDBC 驱动程序在更改 Hibernate 之前正确执行 LOB。”

参考资料:

https://forum.hibernate.org/viewtopic.php?p=2393203 https://forum.hibernate.org/viewtopic.php?p=2435174 http://hibernate.atlassian.net/browse/HHH-4617 http://postgresql.1045698.n5.nabble.com/Migration-to-Hibernate-3-5-final-td2175339.html https://jira.springframework.org/browse/SPR-2318 https://forums.hibernate.org/viewtopic.php?p=2203382&sid=b526a17d9cf60a80f13d40cf8082aafd http://virgo47.wordpress.com/2008/06/13/jpa-postgresql-and-bytea-vs-oid-type/

【问题讨论】:

这个好像3.6已经修复了,不确定3.5.6; MaterializedBlobType 类从 3.5.5 > 3.6 完全重写。 OID 类型现在可以使用,因为它们更改了实现。 不错!我想知道 Jira 问题在跟踪这个重写(如果有的话)(也许重写是更深层次变化的结果)。如果可能的话,最好将 3.5 中的更改向后移植。如果不可能,那就是坏消息。 实际上我的测试第一次给了我一个误报(我知道我应该等待!)——它仍然没有修复,这个错误刚刚移动到 BlobTypeDescriptor。 谢谢。 @Type(type="org.hibernate.type.BinaryType") 为我工作了一个存储 PDF 文件的表。我使用 Intelligent Converters 中的 Oracle-To-PostgreSQL 将数据库从 Oracle 迁移到 Postgres,它自动从 BLOB 转换并插入到 BYTEA,但 BlobType 对我不起作用。 【参考方案1】:

注释 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 注释是可选的 用于持久字段和属性 这些类型的。如果基本 没有为此类指定注释 字段或属性,默认值 的基本注释将适用。

Hibernate 会将它“默认”映射到 SQL VARBINARY(或 SQL LONGVARBINARY,取决于 Column 的大小?),PostgreSQL 使用 bytea 处理。

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

11.1.24 Lob 注释

Lob 注释指定一个 持久性属性或字段应该是 作为一个大对象持续存在 数据库支持的大对象类型。 便携式应用程序应使用 Lob 映射到一个注解时 数据库Lob 类型。 Lob 注释 可以与 基本注释或带有 ElementCollection 注解时 元素集合值基本 类型。 Lob 可以是二进制文件或 字符类型。 Lob 类型是 从类型推断 持久性字段或属性,并且, 除了字符串和字符类型, 默认为 Blob。

Hibernate 会将其映射到 PostgreSQL 使用 oid 处理的 SQL BLOB .

这个问题在一些最新版本的休眠中修复了吗?

嗯,问题是我不知道问题到底是什么。但我至少可以说,自 3.5.0-Beta-2 以来(在 3.5.x 分支中引入了更改),一切都没有改变。

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

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 注释”

资源

http://opensource.atlassian.com/projects/hibernate/browse/HHH-4876 http://opensource.atlassian.com/projects/hibernate/browse/HHH-4617 http://relation.to/Bloggers/PostgreSQLAndBLOBs

【讨论】:

天啊,我意识到自从我开始回答以来,这个问题已经发生了很大变化。稍后将阅读所有更改,并在必要时消化更改后更新我的答案。 很高兴看到规范,所以休眠将 (@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 所以我假设我是 - 因为我没有明确设置此属性。 设置此属性(为真或假)后,我得到一个运行时异常:错误:列“签名”是 bytea 类型,但表达式是 oid 类型”。我应该提到我正在使用休眠 3.5.5.Final + PG 8.2 驱动程序。【参考方案2】:

这就是 O'reilly Enterprise JavaBeans 3.0 所说的

JDBC 为这些非常大的对象提供了特殊类型。 java.sql.Blob类型表示二进制数据,java.sql.Clob表示字符数据。

这里是PostgreSQL方言源代码

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 中的字节 [])

...

迁移到 hibernate 3.5 所有 blob 字段不再起作用,服务器日志显示:ERROR org.hibernate.util.JDBCExceptionReporter - ERROR: column is of type oid but expression is of type bytea

可以解释 here

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

...

更多的是我在 3.5 - beta 2 中看到的,我不知道这是否已修复是 Hibernate - 没有 @Type 注释 - 将自动创建 oid 类型的列,但会尝试阅读此内容作为字节茶

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

无法执行 JDBC 批量更新

插入或更新时

【讨论】:

这个解决方案看起来很棒,我正在尝试。 这会生成正确的 DDL,但在运行时失败:尝试使用具有 blob 属性的对象时出现 java.sql.BatchUpdateException。 @Justin 通过使用 Oracle 而不是 PostgreSQL 尝试类似的场景,看看你会得到什么。 BatchUpdateException 与批量更新操作期间发生的错误有关。 其实我真正想要的不是将 BLOB 映射到“bytea”,而是将 (byte[] + @Lob) 注释组合映射到 Types.VARBINARY! @Justin See download-llnw.oracle.com/javase/1.5.0/docs/api/java/sql/…【参考方案3】:

我正在使用带有 Postgres 9.3 的 Hibernate 4.2.7.SP1,以下对我有用:

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

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

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

【讨论】:

使用 Hibernate 4.3.6 和 Postgresql 9.3 以及扩展 Postgresql9Dialect 为我工作。谢谢! 适用于 Hibernate 5.3.7.Final 和 Postgres95Dialect。谢谢【参考方案4】:

我终于搞定了。它扩展了 A. Garcia 的解决方案,但是,由于问题在于 hibernate 类型 MaterializedBlob 类型,仅映射 Blob > bytea 是不够的,我们需要替换 MaterializedBlobType ,它可以与休眠中断的 blob 支持一起使用。此实现仅适用于 bytea,但可能需要 OID 的 JIRA 问题中的人可以贡献一个 OID 实现。

遗憾的是,在运行时替换这些类型很痛苦,因为它们应该是方言的一部分。 如果只有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);
 

【讨论】:

+1 供您研究。恭喜。只是一个建议:最好将自己的问题/答案编辑多达 8 次。否则,您的问题/答案将成为社区 wiki,您将不会获得声誉,并且不会再计算 UP 投票 生活和学习我想,我有很多编辑,因为我总是忘记在我的测试环境中做一件事或另一件事。 这里也一样,+1 用于研究,a 解决方案适合您的情况。 4.2.x Hibernate 版本是否有机会解决? Hibernate 内部结构发生了一些变化(我评论了问题引用:hibernate.atlassian.net/browse/HHH-5584)。【参考方案5】:

我通过添加 @Lob 的注释来解决我的问题,这将在 oracle 中将 byte[] 创建为 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

【讨论】:

【参考方案6】:

在 Postgres 上,@Lob 正在破坏 byte[],因为它试图将其保存为 oid,而对于 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.dialect=X.Y.Z.PostgreSQLDialectCustom   

X.Y.Z 是包名。

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

【讨论】:

【参考方案7】:

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

注意:我们只需要重写一个类,所以没什么大不了的。 从我的示例中了解更多信息 Example to override annotation with XML

【讨论】:

【参考方案8】:

感谢贾斯汀和帕斯卡为我指引正确的方向。我也面临与 Hibernate 3.5.3 相同的问题。您的研究和对正确课程的指导帮助我发现了问题并进行了修复。

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

    我创建了一个自定义 BlobType 扩展 MaterializedBlobType 并使用 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));
      
    
    

      向 Hibernate 注册 CustomBlobType。以下是我为实现这一目标所做的工作。

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

【讨论】:

【参考方案9】:
    可以在实体中使用
@Lob
@Type(type = "org.hibernate.type.BinaryType")
@Column(name = "stringField")
private byte[] stringField;

【讨论】:

以上是关于字节 [] 的正确休眠注释的主要内容,如果未能解决你的问题,请参考以下文章

如何使用注释创建休眠复合键

没有会话的休眠 FetchType.LAZY

TEXT 的休眠列注释

休眠注释:实体没有默认构造函数

JPA(休眠)映射OneToMany不正确?

休眠一对多映射注释问题