1

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

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

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

  • 前 2 位编码ItemA( A = 0b01, B = 0b10, C = 0b11),
  • 下一位是ItemB( 0 = E, 1 = F); 和
  • 接下来的 2 位是ItemC( G = 0b00, H = 0b01, I = 0b10, 和J = 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在加载时自动填充(解包)这些字段,然后在插入/更新时再次自动将它们打包到位串中?如果它们是只读的,那么我只需将它们隐藏在视图中并将它们映射为只读。

可能的解决方案讨论:

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

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

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

任何帮助表示赞赏!

4

2 回答 2

0

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

于 2013-05-04T13:02:04.133 回答
0

当您的数据库列没有 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

于 2013-04-29T21:51:23.103 回答