在运行时扩展JPA实体数据

时间:2009-05-19 07:08:09

标签: java hibernate jpa runtime

我需要允许客户端用户在运行时扩展JPA实体包含的数据。换句话说,我需要在运行时向实体表添加虚拟列。此虚拟列仅适用于某些数据行,并且可能存在相当多的虚拟列。因此,我不想在数据库中创建实际的附加列,而是希望使用代表这些虚拟列的其他实体。

例如,请考虑以下情况。我有一个 Company 实体,其中包含一个标有所有者的字段,其中包含对公司所有者的引用。在运行时,客户端用户决定属于特定所有者的所有 Companies 应该具有标记为 ContactDetails 的额外字段。

我的初步设计使用了两个额外的实体来完成此任务。第一个基本上代表虚拟列,并包含诸如字段名称和预期值类型之类的信息。另一个表示实际数据,并将实体行连接到虚拟列。例如,第一个实体可能包含数据“ContactDetails”,而第二个实体包含“555-5555”。

这是正确的方法吗?还有更好的选择吗?此外,在加载原始实体时,自动加载此数据的最简单方法是什么?我希望我的DAO调用将实体与扩展一起返回。

编辑:我将示例从标记为 Type 的字段更改为当前版本的合作伙伴客户混乱。

5 个答案:

答案 0 :(得分:3)

也许更简单的替代方法是将CLOB列添加到每个公司并将扩展存储为XML。与您的解决方案相比,这里有一组不同的权衡,但只要额外的数据不需要SQL可访问(没有索引,fkeys等),它可能比您现在所做的更简单。

这也意味着如果您对额外数据有一些奇特的逻辑,则需要以不同方式实现它。例如,如果您需要所有可能的扩展类型的列表,则必须单独维护它。或者,如果您需要搜索功能(通过电话号码查找客户),您将需要lucene或类似的解决方案。

如果你有兴趣,我可以详细说明。

编辑:

要启用搜索功能,您需要lucene这样的内容,这是对任意数据进行自由文本搜索的绝佳引擎。还有hibernate-search使用注释等直接将lucene与hibernate集成 - 我没有使用过它,但我听说过它很好。

对于获取/写入/访问数据,您基本上处理XML,因此应该应用任何XML技术。最好的方法实际上取决于实际内容以及如何使用它。我建议查看XPath以获取数据访问权限,并考虑定义自己的hibernate usertype,以便将所有访问权限封装到一个类中,而不仅仅是普通的String。

答案 1 :(得分:0)

公司,合作伙伴和客户的示例实际上是通过JPA继承支持的多态性的良好应用程序:您将有以下3种策略可供选择:单个表,每个类的表和连接。您的描述听起来更像是加入策略,但不一定。

您也可以考虑一对一(或零)关系。然后,您需要为虚拟列的每个值建立这种关系,因为它的值代表不同的实体。因此,您将与合作伙伴实体建立关系,并与客户实体建立另一种关系,两者都可以为空。

答案 2 :(得分:0)

我遇到了比我希望的更多的问题,因此我决定愚弄我的第一次迭代的要求。我目前正在尝试仅在整个 Company 实体上允许此类 Extensions ,换句话说,我正在放弃整个所有者要求。因此,问题可以改为“如何在运行时将虚拟列(另一个实体中的条目作为附加列)添加到实体中?”

我目前的实施如下(过滤掉了无关的部分):

@Entity
class Company {
  // The set of Extension definitions, for example "Location"
  @Transient
  public Set<Extension> getExtensions { .. }

  // The actual entry, for example "Atlanta"
  @OneToMany(fetch = FetchType.EAGER)
  @JoinColumn(name = "companyId")
  public Set<ExtensionEntry> getExtensionEntries { .. }
}

@Entity
class Extension {
  public String getLabel() { .. }
  public ValueType getValueType() { .. } // String, Boolean, Date, etc.
}

@Entity
class ExtensionEntry {
  @ManyToOne(fetch = FetchType.EAGER)
  @JoinColumn(name = "extensionId")
  public Extension getExtension() { .. }

  @ManyToOne(fetch = FetchType.LAZY)
  @JoinColumn(name = "companyId", insertable = false, updatable = false)
  public Company getCompany() { .. }

  public String getValueAsString() { .. }
}

实现允许我加载 Company 实体,Hibernate将确保其所有 ExtensionEntries 也被加载,并且我可以访问 Extensions < / em>对应于 ExtensionEntries 。换句话说,如果我想在网页上显示这些附加信息,我可以访问以下所有必需信息:

Company company = findCompany();
for (ExtensionEntry extensionEntry : company.getExtensionEntries()) {
  String label = extensionEntry.getExtension().getLabel();
  String value = extensionEntry.getValueAsString();
}

然而,这有很多问题。首先,当使用带有@OneToMany的FetchType.EAGER时,Hibernate使用外连接,因此将返回重复的公司(每个ExtensionEntry一个)。这可以通过使用Criteria.DISTINCT_ROOT_ENTITY来解决,但这反过来会导致我的分页错误,因此是一个不可接受的答案。另一种方法是将FetchType更改为LAZY,但这意味着我将始终“手动”加载 ExtensionEntries 。据我所知,例如,如果我加载了100个 Companies 的列表,我将不得不循环并查询每个,生成100个SQL语句,这是不可接受的性能-wise。

我遇到的另一个问题是,无论何时加载 Company ,我都希望加载所有 Extensions 。有了这个,我的意思是我希望@Transient getter命名为getExtensions()来返回任何 Company 的所有 Extensions 。这里的问题是 Company Extension 之间没有外键关系,因为 Extension 不适用于任何单个公司实例,而是所有这些实例。目前我可以使用下面的代码来解决这个问题,但是在访问引用的实体时这不起作用(例如,我有一个实体 Employee ,它引用了 Company ,我通过employee.getCompany()检索的 Company 将不会加载 Extensions

List<Company> companies = findAllCompanies();
List<Extension> extensions = findAllExtensions();
for (Company company : companies) {
  // Extensions are the same for all Companies, but I need them client side
  company.setExtensions(extensions); 
}

所以那是我现在的,我不知道如何继续以解决这些问题。我认为我的整个设计可能存在缺陷,但我不确定如何尝试和接近它。

欢迎任何想法和建议!

答案 3 :(得分:0)

使用模式装饰器并在decoratorClass中隐藏您的实体再见

答案 4 :(得分:0)

使用EAV模式是恕我直言的不好选择,因为性能问题和报告问题(许多连接)。挖掘解决方案我在这里找到了其他的东西:http://www.infoq.com/articles/hibernate-custom-fields