views:

984

answers:

5

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.

A: 

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.

grigory
The example was, in hindsight, a bad one. I'm not concerned with the inheritance part of my design, but rather I'm interested in finding a way to allow users to create "virtual columns" at runtime. I'm not sure I understand how creating a Customer and a Partner entity will help for this, as I'd still need to provide a way for the user to create "virtual columns", only this time on the Partner and Customer entities.
Zecrates
A: 

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!

Zecrates
A: 

Use pattern decorator and hide your entity inside decoratorClass bye

Hi, I'm not entirely sure what you mean, or rather, I know what the Decorator pattern is, but I'm not sure how it will help with my problems. Could you please elaborate?
Zecrates
+2  A: 

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.

Gregory Mostizky
Thanks for the answer, I am definitely interested in hearing more. I didn't even consider such an option. The extra data doesn't need to be SQL accessible, but should be searchable. I'm unsure how I'd be able to "automatically" load this data into the entities [so that I can call company.getExtensions().get("someField");], taking into account that entity is sometimes loaded through Hibernate's fetching?
Zecrates
A: 

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

l0co