将数据库column1,column2,columnN映射到元素集合

时间:2009-07-15 22:37:05

标签: java hibernate jpa usertype compositeusertype

在旧数据库表中,我们有编号的列,如C1,C2,C3,C100或M1,M2,M3,M100。
此列表示BLOB数据。

无法更改此数据库的任何内容。

通过使用JPA Embeddable,我们将所有列映射到单个字段。然后在嵌入期间,我们使用100覆盖注释覆盖名称。

最近我们切换到了Hibernate,我发现了 UserCollectionType CompositeUserType 之类的内容。但我没有找到任何与我相近的用例。

是否可以通过使用Hibernate实现某些用户类型,以便能够将一组列映射到集合而无需额外查询

修改
您可能已经注意到列的名称可能因表而异。我想创建一个类似“LegacyArray”的类型,每次使用此类型时都不需要指定所有的@Columns。 但相反,我会使用

  @Type(type = "LegacyArrayUserType",
        parameters =
   {
      @Parameter(name = "prefix", value = "A"),
      @Parameter(name = "size", value = "128")
   })
   List<Integer> legacyA;

  @Type(type = "LegacyArrayUserType",
        parameters =
   {
      @Parameter(name = "prefix", value = "B"),
      @Parameter(name = "size", value = "64")
   })
   List<Integer> legacyB;

6 个答案:

答案 0 :(得分:3)

我可以想到几种方法可以做到这一点。

<强> 1。为模拟规范化表结构的集合信息创建视图,并将其作为集合映射到Hibernate:

假设您的现有表名为primaryentity,我将创建一个类似于以下内容的视图:

-- untested SQL...
create view childentity as
(select primaryentity_id, c1 from primaryentity union
select primaryentity_id, c2 from primaryentity union
select primaryentity_id, c3 from primaryentity union
--...
select primaryentity_id, c100 from primaryentity)

现在从Hibernate的角度来看,childentity只是一个带有primarykey外键的规范化表。对此进行映射应该非常简单,并在此处介绍:

这种方法的好处:

  • 从Hibernate的角度来看,这些表是规范化的,这是一个相当简单的映射
  • 不对现有表格进行更新

缺点:

  • 数据是只读的,我不认为您的视图可以以可更新的方式定义(我可能是错的)
  • 需要更改数据库,您可能需要创建大量视图

或者,如果您的DBA甚至不允许您向数据库添加视图,或者您需要执行更新:


<强> 2。使用Hibernate的dynamic model mapping facility将您的C1,C2,C3属性映射到Map,并使用DAO图层的一些代码在Map和Collection属性之间进行适当的对话:

我自己从未这样做,但我相信Hibernate确实允许您将表映射到HashMaps。我不确定Hibernate如何动态地允许你这样做(也就是说,你可以简单地指定表名,让Hibernate自动映射所有列吗?),但这是我能想到的另一种方式。< / p>

如果采用这种方法,请务必使用data access object模式,并确保从客户端代码中隐藏内部实现(使用HashMaps)。另外,在写入数据库之前,请务必检查集合的大小是否超过可用列的数量。

这种方法的好处:

  • 根本没有对数据库进行任何更改
  • 数据可更新
  • O / R Mapping相对简单

缺点:

  • DAO图层中有很多管道来映射相应的类型
  • 使用可能在将来发生变化的实验性Hibernate功能

答案 1 :(得分:1)

就个人而言,我认为设计听起来像是关系数据库中断了first normal form。如果您需要C101或M101,会发生什么?再次更改您的架构?我认为这是非常具有侵入性的。

如果你将Hibernate添加到混音中,那就更糟了。添加C101或M101意味着必须改变您的Java对象,Hibernate映射,一切。

如果你与C和M表有1:m的关系,你可以通过添加额外的行来处理我刚引用的案例。您的Java对象包含Collection&lt; C&gt;或集合&lt; M&gt;。你的Hibernate映射是一对多的,不会改变。

也许你没有看到任何Hibernate示例符合你的情况,因为它是一个不推荐的设计。

如果必须,也许你应该看一下Hibernate Component Mapping

更新:适当注意到这是遗产的事实。我提出第一个正常形式的观点同样适用于将来可能会发现这个问题的其他人,就像发布问题的人一样。我不想以这样一种方式回答这个问题,即它默默地将这种设计称为“好”。

指出Hibernate组件映射是相关的,因为在您搜索时知道您要查找的内容的名称可能是关键。 Hibernate允许对象模型比它映射的关系模型更精细。您可以自由地为非规范化模式建模(例如,Name和Address对象作为较大Person对象的一部分)。这就是他们给出这种技术的名称。它也可能有助于找到其他例子。

答案 2 :(得分:1)

很抱歉,如果我在这里误解你的问题,我对Hibernate不太了解。但是,在从数据库中选择以获得类似于你想要的东西时,你难道不能连接吗?

像:

SELECT whatever
     , C1||C2||C3||C4||...||C100 AS CDATA
     , M1||M2||M3||M4||...||M100 AS MDATA
FROM ...
WHERE ...

(当然,连接运算符在RDBMS之间有所不同。)

答案 3 :(得分:1)

[编辑]我建议使用CompositeUserTypeHere is an example。在“Java Persistence With Hibernate”一书的第228f页中也有一个很好的例子。

这允许您在Java中将许多列作为单个对象处理。

映射如下所示:

@org.hibernate.annotations.Columns(columns = {
    @Column(name="C1"),
    @Column(name="C2"),
    @Column(name="C3"),
    ...
})
private List<Integer> c;

Hibernate将在正常查询期间立即加载所有列。

在您的情况下,您必须将列表中的int值复制到nullSafeSet中的固定数量的列中。伪代码:

for (int i=1; i<numColumns; i++)
    if (i < list.size())
        resultSet.setInt(index+i, list.get(i));
    else
        resultSet.setNull(index+i, Hibernate.INTEGER.sqlType());

nullSafeGet中,您必须创建一个列表,并在列为NULL时停止添加元素。为了更加安全,我建议您创建自己的列表实现,不允许超出列数(继承自ArrayList并覆盖ensureCapacity())。

[EDIT2]如果您不想键入所有@Column注释,请为它们使用代码生成器。这可以像提供名称和数字的脚本一样简单,并将@Column(...)打印到System.out。脚本运行后,只需将数据剪切并粘贴到源中即可。

唯一的其他解决方案是访问内部Hibernate API以在运行时构建该信息,但该API是内部的,因此很多东西都是私有的。您可以使用Java反射和setAccessible(true),但该代码可能无法在下次更新Hibernate时继续使用。

答案 4 :(得分:0)

您可以使用UserType将给定数量的列映射到您希望的任何类型。如果(例如)集合的大小总是被已知数量的项目限制,那么这可以是一个集合。

自从我使用Hibernate以来已经有一段时间了(> 3年)所以我很生疏,但我记得这很容易做到;您的BespokeUserType类会从ResultSet传递给水合您的对象。

答案 5 :(得分:0)

我也从未使用过Hibernate。

我建议用解释语言(例如Python)编写一个小程序,在其中可以执行字符串,就像它是一个命令一样。您可以构建一个语句,使您可以手动完成您想做的繁琐工作。