我目前正在尝试将遗留数据库架构(我不能轻易改变)映射到 JPA 1.0 (提供程序是Hibernate 3.3)。该架构是在磁盘空间非常宝贵的时候设计的,因此有几种情况下会将多个不同的值打包到二进制字节数组中。例如,给出以下三个枚举:
enum ItemA { A, B, C }
enum ItemB { E, F }
enum ItemC { G, H, I, J }
这些将被压缩为单个位串列,其中:
ItemA
(A = 0b01
,B = 0b10
,C = 0b11
),ItemB
(0 = E
,1 = F
);和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
字段,然后在插入/更新时自动将它们打包到bitstring中?如果它们只读,然后我只是将它们隐藏在视图中并将它们映射为只读。
可能的解决方案讨论:
我考虑过使用@PrePersist
/ @PreUpdate
生命周期回调,但是Hibernate文档说不要触及这些回调中的EntityManager
,这使得访问数据库变得有些困难。我可以添加一个新的持久性单元并使用REQUIRES_NEW
事务来访问生命周期回调中的存储过程吗?这看起来有点哈哈。
Hibernate特定的@SqlUpdate
/ @SqlInsert
注释可以覆盖SQL作为解决方案吗?这似乎是可能的,但我害怕需要依赖Hibernate期望绑定变量出现的(有点随意的)顺序 - 这是否随着时间的推移而稳定?
编辑:另一种可能性是使用视图进行读取并使用INSTEAD OF
触发器填充位串。我想尽可能避免这种情况,因为它隐藏在JPA之外,可能会混淆其他开发者。
任何帮助表示赞赏!
答案 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,但这通常不是问题。