【问题标题】:JPA (Hibernate) pack multiple enum values into a single byte array using stored functionJPA(Hibernate)使用存储函数将多个枚举值打包到一个单字节数组中
【发布时间】:2013-04-29 20:28:45
【问题描述】:

我目前正在尝试将遗留数据库架构(我无法轻易更改)映射到 JPA 1.0(提供程序是 Hibernate 3.3)。该模式是在磁盘空间非常宝贵的时代设计的,因此有几种情况会将多个不同的值打包到二进制字节数组中。例如,给定以下三个枚举:

enum ItemA { A, B, C }
enum ItemB { E, F }
enum ItemC { G, H, I, J }

这些将被压缩成一个单个位串列,其中:

  • 前 2 位编码 ItemAA = 0b01B = 0b10C = 0b11),
  • 下一位是ItemB (0 = E, 1 = F);和
  • 接下来的 2 位是 ItemCG = 0b00H = 0b01I = 0b10J = 0b11)。

因此,为了映射它,我需要屏蔽每个 enum 的位范围,然后将其映射到相应的值。

更糟糕的是,每个enumenum 中的每个值使用的实际位位置和掩码取决于数据库中存储的其他一些配置表。唯一受支持的打包和解包这些值的方法是通过一组存储函数 (PL/SQL)

function pack_data(item_a in char(1), item_b in char(1), item_c in char(1)) returns raw...
function unpack_data_item_a(bit_string in raw) returns char(1) ..
function unpack_data_item_b(bit_string in raw) returns char(1) ..
etc

我当前的 JPA 映射如下所示:

@Entity
@Table(name = "some_table")
public class MyEntity {
    @Id
    @Column(name = "entity_id")
    private Long id;

    @Column(name = "bit_string")
    private byte[] bitstring;

    @Transient
    private ItemA itemA;
    @Transient
    private ItemB itemB;
    @Transient
    private ItemC itemC;
    ...
}

问题是:如何在加载时自动填充(解包)这些 enum 字段,然后在插入/更新时再次自动将它们打包到位串中?如果它们被读取- 只有这样我才会将它们隐藏在视图中并将它们映射为只读。

可能的解决方案讨论:

我考虑过使用 @PrePersist/@PreUpdate 生命周期回调,但 Hibernate 文档说不要在这些回调中触摸 EntityManager,这使得访问数据库有些困难。我可以添加一个新的持久性单元并使用REQUIRES_NEW 事务来访问生命周期回调中的存储过程,也许?这似乎有点 hacky。

Hibernate 特定的 @SqlUpdate/@SqlInsert 注释是否可以覆盖 SQL 作为解决方案?这似乎是可能的,但我害怕依赖 Hibernate 期望绑定变量出现的(有点随意的)顺序 - 这随着时间的推移是否稳定?

编辑: 另一种可能性是使用视图进行读取并使用INSTEAD OF 触发器来填充位串。如果可能的话,我想避免这种情况,因为它对 JPA 是隐藏的,并且可能会混淆其他开发人员。

任何帮助表示赞赏!

【问题讨论】:

  • 这个问题似乎在我不知情或未经我同意的情况下被编辑过。这是 SO 的常见行为吗?我对结果不感兴趣。

标签: hibernate jpa enums oracle11g


【解决方案1】:

当您的数据库列没有 1:1 映射到您的实体逻辑时,这通常意味着您应该使用自定义类型(休眠用户类型)来映射数据。然后,您可以在此用户类型中执行所有逻辑,并且您的实体可以保持干净。

您实现了一个 org.hibernate.usertype.UserType - 产生了您的 SuperEnumUserType。 在您的实体中,您使用 @Type 注释枚举属性:

@Type(type = "my.cool.project.SuperEnumUserType")
private ItemA itemA;

查看示例:http://javadata.blogspot.de/2011/07/hibernate-and-user-types.html

【讨论】:

  • 谢谢。你能给我举个例子吗? Hibernate 文档中的示例似乎假设我可以将字段作为参数绑定到 PreparedStatement。我应该调用 getConnection() 来调用我存储的函数,然后将结果绑定到语句中吗?
  • 我添加了更多细节,并添加了一个教程链接,该教程展示了如何将单个列映射到多个属性。
  • 感谢您提供详细信息。我花了一些时间研究自定义 Hibernate 类型方法,但它最终导致代码过多而收益太少。我添加了另一个答案,详细说明了最终对我有用的方法。
【解决方案2】:

在调查了 IsNull 建议的自定义 Hibernate 类型选项后,我认为对于这种特殊情况,它的代码太多了 - 以及原始 JDBC 代码(在自定义类型内)。相反,我选择使 MyEntity 类有效地不可变,并添加一个 MyEntityBuilder(构建器模式)来创建它。构建器收集各种枚举字段,然后在最后的 build() 步骤中计算打包位字符串:

public class MyEntityBuilder {
    private final EntityManager em;
    private ItemA a;
    ...

    public MyEntityBuilder(final EntityManager em) {
        this.em = em;
    }
    public MyEntityBuilder setA(ItemA a) {
        this.a = a;
        return this;
    }
    // other setters...
    public MyEntity build() {
        final byte[] packedData = (byte[])
              em.createNamedQuery(MyEntity.PACK_DATA)
                .setParameter("item_a", a)
                .setParameter("item_b", b)
                .setParameter("item_c", c)
                .getSingleResult();
        return new MyEntity(packedData, a, b, c);
    }
}

其中 MyEntity.PACK_DATA 是一个命名的本机查询,它调用存储的函数并返回打包的二进制数据。这负责写入数据,因为构建器确保打包的 bit_string 在构造时是正确的。为了阅读,我使用原始包装器视图并将枚举字段标记为 insertable=false、updateable=false(尽管我可能会在此处移至 Hibernate @Formula 注释)。

与自定义 Hibernate 类型方法相比,构建器模式的代码更短(因为我们可以使用 JPA 本身来执行查询而不是编写 JDBC 代码),并且完全避免了任何特定于 Hibernate 的代码。调用者确实需要知道要使用 Builder 并需要访问 EntityManager,但这通常不是问题。

【讨论】:

    猜你喜欢
    • 2016-11-10
    • 2022-01-15
    • 1970-01-01
    • 2013-03-02
    • 1970-01-01
    • 2018-04-11
    • 1970-01-01
    • 1970-01-01
    • 2013-07-10
    相关资源
    最近更新 更多