在 Hibernate 中将 Java byte[] 映射到 MySQL binary(64)

Posted

技术标签:

【中文标题】在 Hibernate 中将 Java byte[] 映射到 MySQL binary(64)【英文标题】:Mapping Java byte[] to MySQL binary(64) in Hibernate 【发布时间】:2011-08-23 20:06:42 【问题描述】:

我在将字节数组映射到 Hibernate 中的 mysql 数据库时遇到了一些问题,我想知道我是否遗漏了一些明显的东西。我的班级大致是这样的:

public class Foo 
    private byte[] bar;

    // Getter and setter for 'bar'

在 MySQL 5.5 中表是这样定义的:

CREATE TABLE foo (
bar BINARY(64) NOT NULL)

Hibernate 3.6.2 的映射看起来与此类似:

<hibernate-mapping>
    <class name="example.Foo" table="foo">
        <property name="bar" column="bar" type="binary" />
    </class>
</hibernate-mapping>

我只使用 hbm2ddl 进行验证,当我部署应用程序时它给了我这个错误:

Wrong column type in foo for column bar. Found: binary, expected: tinyblob

如果在映射中使用 type="binary" 不会导致 Hibernate 期望列的类型是二进制(而不是 tinyblob),我不知道会发生什么。我花了一些时间谷歌搜索,但找不到确切的错误。类似错误的解决方案是...

    上指定“长度”。这会改变 Hibernate 所期望的类型,但它始终是各种 blob,而不是它找到的“二进制”类型。 不要在属性元素上声明“类型”,而是嵌套一个列元素并为其赋予一个 sql-type 属性。那是可行的,但这也会使绑定特定于 MySQL,所以如果可能的话,我想避免它。

此设置有什么突出的地方会导致这种不匹配吗?如果我指定 type="binary" 而不是 "blob",为什么 Hibernate 需要一个 blob 而不是二进制文件?

【问题讨论】:

二进制类型让我对 Java/Hibernate 感到无尽的沮丧。通常我解决这个问题的方法是在 Java 端对二进制数据进行 Base64 编码,然后将其作为TEXT 字段存储在数据库中。然后你的实体中的属性可以是String 类型,一切都会更加顺利。 是的,字节数组在 Hibernate 中似乎确实很麻烦。映射明确告诉 Hibernate 期望二进制作为类型,但它正在寻找不同类型的 blob,这不是一个错误吗?确实非常令人沮丧! 这不是明确告诉 Hibernate 使用二进制文件。 property 元素的 type 属性不用于指定 SQL 数据类型。我在下面的回答中对此进行了解释。我无法评论 Hibernate 在设置为使用 BINARY 数据类型(正确)时的行为方式,我没有尝试过。 由于 MySQL 的二进制类型似乎被 Hibnerate “孤立”了,并且变通办法似乎是映射它们的唯一方法,我将其发布到 Hibernate 论坛。 forum.hibernate.org/viewtopic.php?f=1&t=1011076 我有点希望这被认为是一个错误并在将来修复。 【参考方案1】:

我认为问题出在type="binary"

该类型是休眠的泛型类型。它不直接映射到特定于数据库引擎的类型。根据您使用的驱动程序,它们被转换为不同的 SQL 类型。显然,MySQL 驱动程序将休眠类型“二进制”映射到一个 tinyblob。

完整的休眠类型列表可在here获得。

您有 2 个选项。您可以更改 CREATE TABLE 脚本以使用 tinyblob 数据类型存储该列。然后你的休眠验证不会失败,你的应用程序就会工作。这将是建议的解决方案。

仅当您必须在 DB 中使用 BINARY 数据类型时才应使用第二个选项。您可以做的是在休眠映射中指定一个 sql 类型,以便强制休眠使用您想要的类型。映射如下所示:

<property name="bar">
  <column name="bar" sql-type="binary" />
</property>

这样做的主要缺点是您失去了 DB 引擎的独立性,这就是大多数人首先使用休眠的原因。此代码仅适用于具有 BINARY 数据类型的数据库引擎。

【讨论】:

你是对的 - type="binary" 绝对是问题所在。我只是(错误地)假设由于既有 blob Hibernate 类型和二进制 Hibernate 类型,“blob”Hibernate 类型将对应于各种 MySQL blob 类型,而二进制将对应于二进制。不幸的是,似乎没有任何 Hibernate 类型对应于 MySQL 二进制文件,这似乎是一个相当大的疏忽。【参考方案2】:

我们最终解决类似问题的方法是编写我们自己的自定义 UserType。

UserTypes 相对容易实现。只需创建一个实现 org.hibernate.usertype.UserType 的类并实现 @override 方法。

在您的休眠定义中,使用用户类型非常简单:

<property name="data" type="com.yourpackage.hibernate.CustomBinaryStreamUserType" column="binary_data" />

简单地说,它会执行这个类来读取和写入数据库中的数据。具体来说,使用了 nullSafeGet 和 nullSafeSet 方法。

在我们的例子中,我们使用它在将二进制数据写入数据库之前对其进行 gzip 压缩,并在读取时将其解压缩。这隐藏了使用此数据从应用程序压缩数据这一事实。

【讨论】:

A UserType 很容易编写并且肯定会工作,但它似乎并不比问题中的选项 2 更与数据库无关 - 用嵌套的 声明 sql 类型属性。不同之处在于,使用“选项 2”是一种单行解决方案,不会向项目引入更多代码(错误的机会)。虽然我会说,在您的 gzipping 数据示例中,UserType 似乎是最好的地方。我认为这很聪明。【参考方案3】:

我认为在休眠中映射二进制列有一个简单的解决方案。

“BINARY”列可以很容易地映射到休眠实体类中的“java.util.UUID”。

例如列定义看起来像

`tokenValue` BINARY(16) NOT NULL

Hibernate Entitiy 将有以下代码来支持 BINARY 列

private UUID tokenValue;

@Column(columnDefinition = "BINARY(16)", length = 16)
public UUID getTokenValue() 
    return this.tokenValue;


public void setTokenValue(UUID sessionTokenValue) 
    this.tokenValue = tokenValue;

【讨论】:

指定列的 SQL 定义是一个已经讨论过的选项(包括在问题中。)

以上是关于在 Hibernate 中将 Java byte[] 映射到 MySQL binary(64)的主要内容,如果未能解决你的问题,请参考以下文章

hibernate如何保存blob数据

Java 中将位打包到 byte[] 并读回的最有效方法是啥?

在java中将字符串数组元素转换为字节数组[重复]

如何在java中将密钥归零?

java中将一个数组按照每份固定大小拆分

在 Java 中将 ByteBuffer 转换为字符串