简体   繁体   English

在运行时扩展JPA实体数据

[英]Extending JPA entity data at runtime

I need to allow client users to extend the data contained by a JPA entity at runtime. 我需要允许客户端用户在运行时扩展JPA实体包含的数据。 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 . 在运行时,客户端用户决定属于特定所有者的所有公司都应具有标记为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." 例如,第一个实体可能包含数据“ContactDetails”,而第二个实体包含“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 . 我希望我的DAO调用将实体与其扩展一起返回。

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. 编辑:我将示例从标记为Type的字段更改为当前版本的合作伙伴客户 ,因为它令人困惑。

Perhaps a simpler alternative could be to add a CLOB column to each Company and store the extensions as an XML. 也许更简单的替代方法是将CLOB列添加到每个公司并将扩展存储为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. 与您的解决方案相比,这里有一组不同的权衡,但只要额外的数据不需要SQL可访问(没有索引,fkeys等),它可能比您现在所做的更简单。

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. 或者,如果您需要搜索功能(通过电话号码查找客户),您将需要lucene或类似的解决方案。

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. 要启用搜索,您需要像lucene这样的东西,它是对任意数据进行自由文本搜索的绝佳引擎。 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. 还有hibernate-search ,它使用注释等直接将lucene与hibernate集成在一起 - 我没有使用它,但我听到了很多关于它的东西。

For fetching/writing/accessing data you are basically dealing with XML so any XML technique should apply. 对于获取/写入/访问数据,您基本上处理XML,因此应该应用任何XML技术。 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. 我建议调查XPath以获取数据访问权限,并考虑定义自己的hibernate usertype,以便将所有访问权限封装到一个类中,而不仅仅是普通的String。

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

Using EAV pattern is IMHO bad choice, because of performance problems and problems with reporting (many joins). 使用EAV模式是恕我直言的糟糕选择,因为性能问题和报告问题(许多连接)。 Digging for solution I've found something else here: http://www.infoq.com/articles/hibernate-custom-fields 挖掘解决方案我在这里找到了其他东西: http//www.infoq.com/articles/hibernate-custom-fields

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. 公司,合作伙伴和客户的示例实际上是通过JPA继承支持的多态性的良好应用程序:您将有以下3种策略可供选择:单个表,每个类的表和连接。 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. 因此,您将与合作伙伴实体建立关系,并与客户实体建立另一种关系,两者之间或两者都不能为空。

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 . 因为是实现允许我加载一个公司实体和Hibernate将确保其所有ExtensionEntries也被加载,我也可以访问对应于那些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). 首先,当使用带有@OneToMany的FetchType.EAGER时,Hibernate使用外连接,因此将返回重复的公司(每个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. 这可以通过使用Criteria.DISTINCT_ROOT_ENTITY来解决,但这反过来会导致我的分页错误,因此是一个不可接受的答案。 The alternative is to change the FetchType to LAZY, but that means that I will always "manually" have to load ExtensionEntries . 另一种方法是将FetchType更改为LAZY,但这意味着我将始终“手动”加载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. 据我了解,例如,如果我加载了100个公司的列表,我将不得不循环并查询每个公司,生成100个SQL语句,这在性能方面是不可接受的。

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 . 我的意思是,我希望@Transient getter命名为getExtensions()来返回任何公司的所有扩展 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): 目前,我能过去与代码像我下面的介绍,但访问引用的实体时,这是不行的(例如,如果我有有公司参照实体员工公司 ,我通过employee.getCompany检索()获没有加载扩展程序:

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! 欢迎任何和所有的想法和建议!

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM