Byte []的适当 Hibernate 注释

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

当我们发现 hibernate 3.5 断裂(不会修复)这个注释组合在 postgreql 中(没有变通方法)时,我们不得不升级到3.5。到目前为止,我还没有找到一个明确的修复方法,但是我注意到,如果删除@Lob,它将使用 postgreql 类型 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 给出答案之后,我尝试了这个组合,它实际上对 postgreql 有效,但对 Oracle 无效。

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

我真正需要做的是控制@org。Hibernate 注解。键入组合(@Lob + byte []得到映射)到(on postgreql)。


下面是来自3.5.5的片段。最终版本来自 aterializedBlobType (sql 类型 Blob)。根据 Steve 的博客,postgreql 希望您使用 Streams 来表示 bytea (不要问我为什么) ,并使用 postgreql 的自定义 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 []) ?这个 是的工作,直到尝试存储一个空字节[]。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中的大部分代码都已经被弃用了,因此很难知道在对 PostgreSQLDirecect 进行子类化时应该查看哪些代码)。

Postgreql 上的 Typees.BLOB/‘ OID’应该映射到一些使用 OID 样式 JDBC 访问的自定义类型(即 PostgreqlBlobType 对象和 NOT Materials alizedBlobType)。实际上,我从来没有成功地将 Blobs 与 postresql 结合使用,但是我知道 bytea 只是简单地按照 one/I 预期工作。

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


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

参考文献:

123512 次浏览

我终于把它修好了。然而,由于问题在于 Hibernate 类型的 MatrializedBlob 类型仅仅映射 Blob > bytea 是不够的,我们需要一个可以使用 Hibernate 破碎 Blob 支持的 MatrializedBlobType 的替代品。这个实现只能与 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);
}
}

O’reilly 的 EJB 是这样的

JDBC 对于这些非常大的对象有特殊的类型。

这是 PostgreSQLDiect 的源代码

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

所以你能做什么

重写 PostgreSQLDiect,如下所示

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 字段不再工作,服务器日志显示: ERROR org.hibernate.util.JDBCExceptionReporter-ERROR: column 的类型为 oid,而 expression 的类型为 bytea

这可以在这里解释

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

...

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

有趣的是,当他将 Typees.BOLB 映射为 bytea (请参阅 CustomPostgreSQL 方言)时,他得到

无法执行 JDBC 批处理更新

当插入或更新时

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

这取决于你想要什么。 JPA 可以持久化一个没有注释的 byte[]。从 JPA 2.0规范:

11.1.6基本注释

Basic注释是最简单的 到数据库列的映射类型。 可以应用 Basic注释 到持久性属性或实例 下列任一变量 Type: Java 原语、 type、 wrappers 最原始的类型, 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节所述,使用 是可选的 用于持久字段和属性 如果基本 注释没有为这样的 字段或属性,则默认值为 的基本注释。

Hibernate 会“默认”将 it 映射到 SQLVARBINARY(或者根据 Column的大小映射到 SQLLONGVARBINARY)PostgreSQL 使用 bytea处理的。

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

11.1.24 Lob 注释

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

Hibernate 将它映射到 PostgreSQL 使用 oid处理的 SQLBLOB .

在最近的冬眠版本中修复了这个问题吗?

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

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

hibernate.jdbc.use_streams_for_binary=false

如果你想在 @Lob中使用 oid,也就是 byte[](这是我的理解,因为 VARBINARY不是你想在 Oracle 中使用的)。你试过这个吗?

作为一种替代方法,HHH-4876建议使用已经废弃的 PrimitiveByteArrayBlobType来获得旧的行为(在 Hibernate 3.5之前)。

参考文献

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

资源

我通过使用 Postgres 的 XML 文件覆盖注释使它工作起来。为 Oracle 保留注释。在我看来,在这种情况下,我们最好用 xml 映射覆盖这个问题的映射-某个实体。我们可以使用 xml 映射覆盖单个/多个实体。因此,我们将对主要支持的数据库使用注释,对彼此的数据库使用 xml 文件。

注意: 我们只需要覆盖一个类,所以这没什么大不了的。 从我的例子中了解更多 示例使用 XML 覆盖批注

我正在使用 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 兼容的 Postgres/Oracle 问题,请参阅我的 博客文章

在 Postgres@Lob 中,byte []中断,因为它试图将其保存为 oid,而 String 中也出现了同样的问题。下面的代码是破解 postgres,这是工作良好的甲骨文。

@Lob
private String stringField;

还有

@Lob
private byte[]   someByteStream;

为了解决上面的问题,在 postgres 下面写了一些自定义的 hibernate 方言

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 是包名。

现在好了。 注意-我的 Hibernate 版本-5.2.8. 最终版 Postgres 版本-9.6.3

我通过添加@Lob 的注释来解决我的问题,这个注释将在 oracle 中创建 byte []作为 blob,但是这个注释将创建的字段作为 oid,这个字段不能正常工作。为了使 bytea 创建的 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);
}
}

还需要覆盖方言的参数

方言 = com.ntg.common.DBCompatibilityHelper. PostgreSQLDiectCustom

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

谢谢贾斯汀,谢谢帕斯卡给我指明了正确的方向。我在使用 Hibernate 3.5.3时也面临同样的问题。你的研究和对正确课程的指导帮助我确定了问题并做了修正。

对于那些仍然停留在 Hibernate 3.5和使用 oid + byte [] +@LoB 组合的人来说,以下是我为解决这个问题所做的工作。

  1. 我创建了一个自定义 BlobType,它扩展了 Materials ializedBlobType,并使用 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);
      
  1. 您可以在实体中使用
@Lob
@Type(type = "org.hibernate.type.BinaryType")
@Column(name = "stringField")
private byte[] stringField;

Hibernate 6 + ,javaee 9 +

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