JPA(Hibernate)使用存储函数将多个枚举值打包到单个字节数组中

时间:2013-04-29 20:28:46

标签: hibernate jpa enums oracle11g

我目前正在尝试将遗留数据库架构(我不能轻易改变)映射到 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),
  • 下一位是ItemB0 = E1 = F);和
  • 接下来的2位是ItemCG = 0b00H = 0b01I = 0b10J = 0b11)。

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

更糟糕的是,每个enum使用的实际位位置和掩码以及enum中的每个值都会根据存储在数据库中的其他一些配置表而有所不同。打包和解包这些值的唯一支持方式是通过一组存储函数(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字段,然后在插入/更新时自动将它们打包到bitstring中?如果它们只读,然后我只是将它们隐藏在视图中并将它们映射为只读。

可能的解决方案讨论:

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

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

编辑:另一种可能性是使用视图进行读取并使用INSTEAD OF触发器填充位串。我想尽可能避免这种情况,因为它隐藏在JPA之外,可能会混淆其他开发者。

任何帮助表示赞赏!

2 个答案:

答案 0 :(得分:0)

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

您实现了org.hibernate.usertype.UserType - 导致您的 SuperEnumUserType 。 在您的实体中,使用@Type:

注释enum属性
@Type(type = "my.cool.project.SuperEnumUserType")
private ItemA itemA;

请点击此处查看示例:http://javadata.blogspot.de/2011/07/hibernate-and-user-types.html

答案 1 :(得分:0)

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

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,但这通常不是问题。