适合 byte[] 的 Hibernate 注解

140

我的应用程序使用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 会破坏(且不会修复),因此我们不得不升级到3.5版本。目前我还没有找到明确的解决方案,但我注意到如果我只是删除@Lob注解,它会使用postgresql类型bytea(在postgresql上可以工作,但在其他数据库上可能有问题)。

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中得到解决了?

更新: 在阅读了这篇博客之后,我终于弄清楚了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类型处理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[])?”这确实可以工作,直到您尝试存储null 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版本的弃用注释,Hibernate中的类型系统目前仍处于“正在进行中”的状态。实际上,3.5.5代码中有很多已过时的内容,因此在子类化PostgreSQLDialect时很难知道该看哪些内容。

根据我的了解,postgresql上的Types.BLOB/'oid'应该映射到某个自定义类型,该类型使用OID样式的JDBC访问(即PostgresqlBlobType对象而不是MaterializedBlobType对象)。我从未真正成功地使用过postgresql上的Blobs,但我确实知道bytea可以像一个Blobs一样简单地工作/产生预期的效果。

我目前正在研究BatchUpdateException——可能是驱动程序不支持批处理。


2004年的一句重要引述:

“总结我的废话,我想说,在Hibernate进行更改之前,我们应该等待JDBC驱动程序正确处理LOBS。”

参考资料:


这个问题似乎在3.6中已经被修复了,不确定3.5.6是否也是如此;MaterializedBlobType类从3.5.5 > 3.6进行了彻底的重写。OID类型现在可以正常工作,因为它们改变了实现方式。 - Justin
很好!不知道Jira问题是否正在跟踪此次重写(也许这次重写是更深层次变化的结果)。如果可能的话,把这些改变迁移到3.5版中会很不错。如果不行,那就是个坏消息。 - Pascal Thivent
事实上,第一次测试时出现了误报(我知道应该等待!)——问题仍未解决,错误只是移动到了BlobTypeDescriptor。 - Justin
谢谢。@Type(type="org.hibernate.type.BinaryType") 对于存储PDF文件的表对我很有用。我使用 Intelligent Converters 的 Oracle-To-PostgreSQL 迁移了一个数据库,它自动将BLOB转换为BYTEA并插入,但BlobType对我不起作用。 - jmoran
没有为我们这些需要通用映射以用于多个RDBMS的人而言的乐趣... 我不能将一个类型映射到SQLServer,再将另一个类型映射到PostgreSQL。 - Gwaptiva
10个回答

76
什么是注释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注释的使用是可选的。如果没有为此类字段或属性指定Basic注释,则将应用Basic注释的默认值。”
Hibernate默认情况下将其映射到SQL VARBINARY(或SQL LONGVARBINARY,具体取决于Column大小?),PostgreSQL使用bytea处理。但是,如果要将byte[]存储在大对象中,应使用@Lob。Hibernate将其映射到SQL BLOB,PostgreSQL使用oid处理。这在Hibernate的某些最近版本中是否已修复?

嗯,问题在于我不知道具体是什么问题。但我至少可以说自从3.5.0-Beta-2(引入更改的地方)在3.5.x分支中没有任何变化。

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

hibernate.jdbc.use_streams_for_binary=false

如果你想要在使用 Oracle 数据库时使用 oid,也就是 byte[]@Lob 结合的方法(因为使用 VARBINARY 并不是你所需要的)。尝试过这个方法了吗?
另一种选择是,HHH-4876 建议使用已废弃的 PrimitiveByteArrayBlobType 来获取旧版本(Hibernate 3.5 以前)的行为。

参考资料

  • JPA 2.0 规范
    • 第 2.8 节 "映射非关联字段或属性的默认值"
    • 第 11.1.6 节 "基本注释"
    • 第 11.1.24 节 "LOB 注释"

资源


设置了这个属性(无论是true还是false),我会得到一个运行时异常:ERROR: column "signature" is of type bytea but expression is of type oid。值得一提的是,我正在使用hibernate 3.5.5.Final + PG 8.2驱动程序。 - Justin
这对我来说也很奇怪——我仔细检查了数据库(没有任何bytea),我需要找到那条消息的来源。我尝试过流,无论是false还是true(@Lob + byte[])。当保存一个带有blob的对象时,所有的都会出现相同的错误。唯一有效的方法似乎是在使用bytea时使用@Type(type="org.hibernate.type.BinaryType")。 - Justin
@Justin,“使用blob保存对象时,所有方法都会产生相同的错误”,这非常令人失望,因为这是Hibernate开发人员提供的建议:S。使用bytea时,唯一有效的方法似乎是@Type(type="org.hibernate.type.BinaryType")。这就是在没有@Lob的情况下所得到的结果。但这在Oracle中不起作用,对吧?我认为您需要自定义Dialect以获得可移植的解决方案。 - Pascal Thivent
要么我的设置出了问题,也许我需要升级 hibernate 或 postgres 的版本。我尝试过8.2和8.4的驱动程序——到目前为止什么都没用 :( - Justin
@Justin 我会仔细阅读这个博客文章,包括今晚的所有内容。但据我所知,该博客谈论的是Hibernate 3.5.0-Beta-2+以及HHH-4405HHH-3892引入的更改,所以我倾向于认为你是正确的。老实说,我仍然不理解这些更改的所有原因,并且在某种程度上,它们似乎破坏了某些东西。 - Pascal Thivent
显示剩余7条评论

11

以下是 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() {

更新:

下面内容是从stackoverflow获取的(链接)

我的应用程序在hibernate 3.3.2上运行良好,所有使用oid(java中的byte[])的blob字段工作正常。

...

迁移到Hibernate 3.5后,所有blob字段都无法工作了,并且服务器日志显示:ERROR org.hibernate.util.JDBCExceptionReporter - ERROR: column is of type oid but expression is of type bytea

这可以通过这里的解释来说明(链接)

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

...

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

有趣的是,当他将Types.BOLB映射为bytea(请参见CustomPostgreSQLDialect)时,插入或更新操作就会出现以下错误:

无法执行JDBC批量更新


这个解决方案看起来很棒,我现在正在尝试它。 - Justin
这会生成正确的DDL,但在运行时失败:当我尝试存储一个具有blob属性的对象时,我收到了java.sql.BatchUpdateException。 - Justin
@Justin 很有趣,虽然我不使用Postgres,但PostgreSQLDialect将标准Types.VARBINARY映射为bytea。因此,我认为Postgres不支持本地SQL类型Types.VARBINARY。请参见http://download-llnw.oracle.com/javase/1.5.0/docs/api/java/sql/Types.html#VARBINARY 它已经很清楚了:它识别通用的SQL类型VARBINARY - Arthur Ronald
感谢您的研究,我目前正在尝试所有驱动程序的组合以及一些变化的 hibernate。我甚至检查了 Environment.useStreamsForBinary() 的值,以确保我设置了全局属性。 - Justin
我已经在为其他功能子类化PostgreSQL方言,显然这个解决方案对我很好,而且不会影响其他数据库。 - fleed
显示剩余4条评论

9

我正在使用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 jar文件不变。
有关Hibernate与Postgres / Oracle兼容性的更多问题,请参阅我的博客文章

2
使用Hibernate 4.3.6和Postgresql 9.3,扩展Postgresql9Dialect,对我很有效。谢谢! - Andrés Oviedo
适用于 Hibernate 5.3.7.Final 和 Postgres95Dialect。谢谢 - Bernhard Kern

7
我终于让它工作了。 它扩展了A. Garcia的解决方案,但由于问题出在hibernate类型MaterializedBlob类型上,仅将Blob> bytea映射就不足够了,我们需要一个替代MaterializedBlobType的实现,它可以使用hibernates破碎的blob支持。 此实现仅适用于bytea,但也许JIRA问题中想要OID的人可以贡献一个OID实现。
遗憾的是,在运行时替换这些类型很麻烦,因为它们应该是Dialect的一部分。如果只有这个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() 真的需要一个新实例吗?),但我不太了解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);
 }
}

恭喜你的研究,加一分。只是一个建议:最好自己编辑问题/答案多达8次。否则,你的问题/答案将变成社区维基,并且你将不会获得声望和UP投票将不再被计算。 - Arthur Ronald
学习是一个不断的过程,我做了很多修改,因为我总是忘记在测试环境中做某些事情。 - Justin
同样的情况,研究了一下并为你的问题提供了一个解决方案。+1 - Pascal Thivent
有没有可能在4.2.x版本的Hibernate中找到解决方案?Hibernate内部结构有些变化(我已经评论了相关问题:https://hibernate.atlassian.net/browse/HHH-5584)。 - Peter Butkovic

6

我通过添加@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

谢谢。对于我们这些需要在一个应用程序中支持多个方言的人来说非常好。这样我就可以为Materialized_blob映射Oracle、DB2、SQLServer、MySQL和H2,并让PostgreSQL表现良好。使用Postgres 14.5,Hibernate 5.6.7。 - Gwaptiva

2

Hibernate 6+, javaee 9+

  @Lob
  @JdbcTypeCode(Types.BINARY)
  public byte[] getValueBuffer() {
    return m_valueBuffer;
  }

1
你可以在实体中使用代码。
@Lob
@Type(type = "org.hibernate.type.BinaryType")
@Column(name = "stringField")
private byte[] stringField;

1

在Postgres中,@Lob会导致byte[]保存为oid时出现问题,String也存在同样的问题。以下代码在oracle上运行正常,在postgres上出现了问题。

@Lob
private String stringField;

@Lob
private byte[]   someByteStream;

为了解决上述问题,我们编写了以下自定义的 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


0

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

注意:我们只需要覆盖一个类,所以这不是什么大问题。 从我的示例中了解更多信息 使用XML覆盖注释的示例


0
感谢Justin和Pascal指引我正确的方向。我也遇到了Hibernate 3.5.3的同样问题。你们的研究和指向正确类的指引帮助我找出了问题并进行了修复。
为了让那些仍然卡在Hibernate 3.5上,使用oid + byte[] + @LoB组合的人受益,以下是我解决问题的方法。
我创建了一个自定义的 BlobType,扩展了 MaterializedBlobType 并重写了 set 和 get 方法以进行 oid 样式访问。要将 CustomBlobType 注册到 Hibernate 中,可以添加以下配置:
hibernateConfiguration= new AnnotationConfiguration();
Mappings mappings = hibernateConfiguration.createMappings();
mappings.addTypeDef("materialized_blob", "x.y.z.CustomBlobType", null);

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接