为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 破坏了(并且不会修复) postgresql中的这个注释组合时(没有解决方法),我们不得不升级到3.5 。到目前为止,我还没有找到明确的修复程序,但是我确实注意到,如果我只是删除@Lob,它将使用postteasql类型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 []属性的可移植方式是什么?
  • 在最新的休眠版本中已解决此问题吗?

更新: 阅读此博客后,我终于弄清楚了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希望您将流用于bytea(不要问我为什么),并将postgresql的自定义Blob类型用于oid。还请注意,在JDBC上使用setBytes()也可用于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 [])?这确实工作,UNTIL你尝试存储空字节[]。postgreSQL驱动程序认为是OID类型表达式,列类型是bytea,这是因为休眠(正确地)调用JDBC.setNull()而不是PG驱动程序期望的JDBC.setBytes(null)。

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

休眠中的类型系统当前是一个“进行中的工作”(根据3.5.5弃用注释)。实际上,不赞成使用3.5.5的大部分代码,在对PostgreSQLDialect进行子类化时,很难知道要看什么。

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

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


2004年的一句名言:“总结一下,我想说他们应该在更改Hibernate之前等待JDBC驱动程序正确执行LOB。”

参考文献:


这似乎已在3.6中修复,但不确定是否在3.5.6中。MaterializedBlobType类从3.5.5> 3.6完全重写。由于OID类型更改了实现,因此现在可以使用。
贾斯汀2010年

真好!我不知道什么Jira问题正在跟踪此重写(如果有的话)(也许重写是更深层的变化的结果)。如果可能的话,最好移植3.5中的更改。坏消息,如果不可能的话。
Pascal Thivent

实际上,我的测试第一次给我带来了误报(知道我应该等待!)-它仍然没有得到解决,该错误已移至BlobTypeDescriptor。
贾斯汀2010年

谢谢。@Type(type =“ org.hibernate.type.BinaryType”)为我工作,用于存储PDF文件的表。我从Intelligent Converters使用Oracle-To-PostgreSQL将数据库从Oracle迁移到Postgres,它自动转换并从BLOB插入BYTEA,但BlobType对我不起作用。
jmoran

Answers:


68

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

这取决于您想要什么。JPA可以保留未注释的内容byte[]。根据JPA 2.0规范:

11.1.6基本注释

Basic注释是映射到数据库列的最简单的类型。所述Basic注释可以应用于以下任何类型的持久性或实例变量:Java原始,类型,原始类型的包装, java.lang.Stringjava.math.BigIntegerjava.math.BigDecimaljava.util.Datejava.util.Calendarjava.sql.Datejava.sql.Timejava.sql.Timestampbyte[]Byte[]char[]Character[],枚举,以及任何其他类型的器具Serializable。如2.8节所述,Basic对于这些类型的持久字段和属性,注释的使用是可选的。如果未为此类字段或属性指定基本注释,则将应用基本注释的默认值。

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

但是,如果您希望将byte[]其存储在大对象中,则应使用@Lob。从规格:

11.1.24吊球注释

Lob注释指定持久性属性或字段应保持为一个大型对象到数据库支持的大对象类型。可移植应用程序Lob在映射到数据库Lob类型时应使用 注释。所述Lob注释可以与基本注释或与一起使用 ElementCollection的注释当元素集合值是基本类型。A Lob可以是二进制或字符类型。Lob从持久字段或属性的类型推断出该类型,并且除了字符串和字符类型外,默认为Blob。

并且Hibernate会将其映射到BLOBPostgreSQL使用PostgreSQL处理的SQL oid

在最新的休眠版本中已解决此问题吗?

好吧,问题是我不知道到底是什么问题。但是我至少可以说,自3.5.x分支中的3.5.0-Beta-2(已引入更改)以来,没有任何更改。

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

hibernate.jdbc.use_streams_for_binary=false

如果您想使用oidie byte[],那么@Lob(这是我的理解,因为VARBINARY您不希望使用Oracle)。你有尝试过吗?

作为替代方案,HHH-4876建议使用不推荐使用的方法PrimitiveByteArrayBlobType来获得旧的行为(Hibernate 3.5之前的版本)。

参考资料

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

资源资源


OMG,自从我开始回答以来,我意识到这个问题已经发生了很大变化。稍后将阅读所有更改,并在必要时消化更改后更新我的回答。
Pascal Thivent

很好看规范,因此休眠是完全正确的,可以将(@Lob + byte [])映射到大型对象支持的类型。在Postgresql中有2个(字节或oid)。但是,虽然休眠3.5映射为oid(默认情况下),但它使用JDBC getBytes()进行读取,而PGSQL驱动程序将返回6字节的oid而不是数据。还请注意,自提出问题以来,博客作者(在他的博客上)做出了最有帮助的答复。
贾斯汀2010年

@Justin 但是,虽然休眠3.5映射为oid(默认情况下),但它使用JDBC getBytes()读取,该PGSQL驱动程序返回6字节的oid而不是数据 -使用时hibernate.jdbc.use_streams_for_binary=false也会发生这种情况吗?(现在要检查史蒂夫所说的话)。
Pascal Thivent

我将尝试在属性文件中指定它,但是PostgreSQLDialect的useInputStreamToInsertBlob()返回false,因此我以为是,因为我没有明确设置此属性。
贾斯汀2010年

将此属性设置为true或false之后,我得到一个运行时异常:错误:列“签名”的类型为bytea,但表达式的类型为oid”。我应该提到我正在使用休眠3.5.5.Final + PG 8.2驱动程序
贾斯汀

10

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

更新

这里已经提取这里

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

...

迁移到休眠3.5以后,所有blob字段都不再起作用,并且服务器日志显示:错误org.hibernate.util.JDBCExceptionReporter-错误:列的类型为oid,但表达式的类型为bytea

可以在 这里解释

通常,这不是PG JDBC中的错误而是3.5版本中Hibernate的默认实现的更改。在我的情况下,在连接上设置兼容属性没有帮助

...

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

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

无法执行JDBC批处理更新

插入或更新时


这个解决方案看起来光荣,我现在正在尝试。
贾斯汀2010年

这会生成正确的DDL,但在运行时会失败:尝试使用具有blob属性的对象时,出现java.sql.BatchUpdateException。
贾斯汀2010年

@Justin通过使用Oracle而不是PostgreSQL尝试类似的情况,然后看看会得到什么。BatchUpdateException与批处理更新操作期间发生的错误有关。
亚瑟·罗纳德

实际上,我真正想要的不是将BLOB映射到“ bytea”,而是将(byte [] + @Lob)批注组合映射到Types.VARBINARY!
贾斯汀2010年


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

我认为这种解决方案的优点是可以保持休眠状态。

有关Hibernate与Postgres / Oracle的更多兼容性问题,请参阅我的博客文章


2
使用Hibernate 4.3.6和Postgresql 9.3(扩展了Postgresql9Dialect)为我工作。谢谢!
安德烈斯奥维耶多

与Hibernate 5.3.7.Final和Postgres95Dialect一起使用。Thx
Bernhard Kern,

6

我终于完成了这项工作。它扩展了A. Garcia的解决方案,但是,由于问题出在休眠类型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次。否则,您的问题/答案将成为社区Wiki,并且您将不会获得声誉,也将不再计算UP投票
Arthur Ronald

我想生活和学习,我做了很多编辑,因为我一直忘记在测试环境中做一件事或其他事情。
贾斯汀

同样在这里,+ 1用于研究以及针对您情况解决方案。
Pascal Thivent

4.2.x Hibernate版本是否有解决方案的机会?Hibernate内部结构已经发生了一些变化(我评论了这个问题:hibernate.atlassian.net/browse/HHH-5584)。
Peter Butkovic

2

我通过添加@Lob注释来解决我的问题,该注释将在oracle中将blob创建为byte [],但是此注释将创建为oid不能正常工作的字段,将字节[]生成为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

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

注意:我们只需要重写一个类,所以没什么大不了的。从我的示例示例中阅读更多内容, 以使用XML覆盖注释


0

在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   

XYZ是程序包名称。

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


0

感谢贾斯汀,帕斯卡(Pascal)指导我朝着正确的方向前进。我还面临着与Hibernate 3.5.3相同的问题。您的研究和对正确班级的指导帮助我确定了问题并进行了修复。

为了使仍然使用Hibernate 3.5并使用oid + byte [] + @LoB组合的用户受益,以下是我为解决此问题所做的工作。

  1. 我创建了一个自定义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));
      }
    }
    1. 向Hibernate注册CustomBlobType。以下是我为实现这一目标所做的工作。

      hibernateConfiguration= new AnnotationConfiguration();
      Mappings mappings = hibernateConfiguration.createMappings();
      mappings.addTypeDef("materialized_blob", "x.y.z.BlobType", null);
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.