在运行时扩展 JPA 实体数据

发布于 2024-07-21 13:22:12 字数 644 浏览 2 评论 0原文

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

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

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

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

编辑:我将示例从标记为“类型”的字段(可能是“合作伙伴”或“客户”)更改为当前版本,因为它令人困惑。

I need to allow client users to extend the data contained by a JPA entity at runtime. In other words I need to add a virtual column to the entity table at runtime. This virtual column will only be applicable to certain data rows and there could possibly be quite a few of these virtual columns. As such I don't want to create an actual additional column in the database, but rather I want to make use of additional entities that represent these virtual columns.

As an example, consider the following situation. I have a Company entity which has a field labelled Owner, which contains a reference to the Owner of the Company. At runtime a client user decides that all Companies that belong to a specific Owner should have the extra field labelled ContactDetails.

My preliminary design uses two additional entities to accomplish this. The first basically represents the virtual column and contains information such as the field name and type of value expected. The other represents the actual data and connects an entity row to a virtual column. For example, the first entity might contain the data "ContactDetails" while the second entity contains say "555-5555."

Is this the right way to go about doing this? Is there a better alternative? Also, what would be the easiest way to automatically load this data when the original entity is loaded? I want my DAO call to return the entity together with its extensions.

EDIT: I changed the example from a field labelled Type which could be a Partner or a Customer to the present version as it was confusing.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

酒几许 2024-07-28 13:22:12

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

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

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

编辑:

要启用搜索,您需要类似 lucene 的东西,它是免费的出色引擎对任意数据的文本搜索。 还有 hibernate-search 它使用注释等直接将 lucene 与 hibernate 集成 - 我还没有'没用过它,但我听说过它的好消息。

对于获取/写入/访问数据,您基本上是在处理 XML,因此任何 XML 技术都应该适用。 最好的方法实际上取决于实际内容以及如何使用它。 我建议研究 XPath 进行数据访问,也许还可以考虑定义自己的休眠usertype 以便所有访问都封装到一个类中,而不仅仅是普通的字符串。

Perhaps a simpler alternative could be to add a CLOB column to each Company and store the extensions as an XML. There is a different set of tradeoffs here compared to your solution but as long as the extra data doesn't need to be SQL accessible (no indexes, fkeys and so on) it will probably be simple than what you do now.

It also means that if you have some fancy logic regarding the extra data you would need to implement it differently. For example if you need a list of all possible extension types you would have to maintain it separately. Or if you need searching capabilities (find customer by phone number) you will require lucene or similar solution.

I can elaborate more if you are interested.

EDIT:

To enable searching you would want something like lucene which is a great engine for doing free text search on arbitrary data. There is also hibernate-search which integrates lucene directly with hibernate using annotations and such - I haven't used it but I heard good things about it.

For fetching/writing/accessing data you are basically dealing with XML so any XML technique should apply. The best approach really depends on the actual content and how it is going to be used. I would suggest looking into XPath for data access, and maybe look into defining your own hibernate usertype so that all the access is encapsulated into a class and not just plain String.

对你的占有欲 2024-07-28 13:22:12

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

我当前的实现如下(过滤掉不相关的部分):

@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也被加载并且我可以访问与这些ExtensionEntries相对应的扩展。 换句话说,例如,如果我想在网页上显示此附加信息,我可以按如下方式访问所有必需的信息:

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

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

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

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); 
}

这就是我目前的情况,我不知道如何继续解决这些问题。 我认为我的整个设计可能有缺陷,但我不确定如何尝试和解决它。

欢迎任何想法和建议!

I've run into more problems than I hoped I would and as such I decided to dumb down the requirements for my first iteration. I'm currently trying to allow such Extensions only on the entire Company entity, in other words, I'm dropping the whole Owner requirement. So the problem could be rephrased as "How can I add virtual columns (entries in another entity that act like an additional column) to an entity at runtime?"

My current implementation is as follow (irrelevant parts filtered out):

@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() { .. }
}

The implementation as is allows me to load a Company entity and Hibernate will ensure that all its ExtensionEntries are also loaded and that I can access the Extensions corresponding to those ExtensionEntries. In other words, if I wanted to, for example, display this additional information on a web page, I could access all of the required information as follow:

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

There are a number of problems with this, however. Firstly, when using FetchType.EAGER with an @OneToMany, Hibernate uses an outer join and as such will return duplicate Companies (one for each ExtensionEntry). This can be solved by using Criteria.DISTINCT_ROOT_ENTITY, but that in turn will cause errors in my pagination and as such is an unacceptable answer. The alternative is to change the FetchType to LAZY, but that means that I will always "manually" have to load ExtensionEntries. As far as I understand, if, for example, I loaded a List of 100 Companies, I'd have to loop over and query each of those, generating a 100 SQL statements which isn't acceptable performance-wise.

The other problem which I have is that ideally I'd like to load all the Extensions whenever a Company is loaded. With that I mean that I'd like that @Transient getter named getExtensions() to return all the Extensions for any Company. The problem here is that there is no foreign key relation between Company and Extension, as Extension isn't applicable to any single Company instance, but rather to all of them. Currently I can get past that with code like I present below, but this will not work when accessing referenced entities (if for example I have an entity Employee which has a reference to Company, the Company which I retrieve through employee.getCompany() won't have the Extensions loaded):

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); 
}

So that's were I'm at currently, and I have no idea how to proceed in order to get past these problems. I'm thinking that my entire design might be flawed, but I'm unsure of how else to try and approach it.

Any and all ideas and suggestions are welcome!

十年九夏 2024-07-28 13:22:12

Company、Partner 和 Customer 的示例实际上是多态性的良好应用,多态性通过 JPA 的继承来支持:您将有以下 3 种策略可供选择:单表、每类表和连接。 您的描述听起来更像是联合策略,但不一定。

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

The example with Company, Partner, and Customer is actually good application for polymorphism which is supported by means of inheritance with JPA: you will have one the following 3 strategies to choose from: single table, table per class, and joined. Your description sounds more like joined strategy but not necessarily.

You may also consider just one-to-one( or zero) relationship instead. Then you will need to have such relationship for each value of your virtual column since its values represent different entities. Hence, you'll have a relationship with Partner entity and another relationship with Customer entity and either, both or none can be null.

清泪尽 2024-07-28 13:22:12

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

Use pattern decorator and hide your entity inside decoratorClass bye

傾旎 2024-07-28 13:22:12

恕我直言,使用 EAV 模式是一个糟糕的选择,因为性能问题和报告问题(许多连接)。 挖掘解决方案我在这里找到了其他东西: http://www.infoq.com /文章/hibernate-自定义字段

Using EAV pattern is IMHO bad choice, because of performance problems and problems with reporting (many joins). Digging for solution I've found something else here: http://www.infoq.com/articles/hibernate-custom-fields

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文