Posted under JPA
Permalink
Tags Eclipselink, Gotcha, JPA, Tip
Having read a number of technical blogs on this there appeared to be no clear winner either way, until I hit a requirement to use a mapped superclass for an entity. Then the following issues became very important:-
- Annotating fields gives you less flexibility – methods can be overriden by a subclass, whereas fields cannot.
- My particular example where I hit this was when designing an implementation to use for hierarchical/tree structured data, to be used in a number of places in an application. I wanted to make all the tree usage polymorphic from the same base class implementation, as then various other areas of the code could be reused for any tree. As entities were passed all the way up to the (JSF) UI, I was using certain design patterns for handling hierarchies at the UI level and making them able to handle any tree was desirable.
- Some of the functionality for handling trees – in my case path enumerated trees as described here in this excellent article on slideshare.net – could be placed in a base class. Examples of this were methods to compress node Ids in the path enumerated string using Base64, and building tree path strings for use at the UI etc.
- A key gotcha was that I wanted different impementations of trees to be annotated differently, for example to use different sequence Id generation. If the tree Id was in the mapped superclass and I was using field annotation, I was stuck. This issue is discussed in these articles here (see the comments at the end) and here.
My solution was to take the decision to always annotate (getter) methods from now on. This allowed the following implementation:-
- My mapped superclass contained getters annotated @Transient where I wanted to change their behaviour in a subclass. For example, my treeId primary key was in the superclass but marked @Transient so that it would not be mapped there. Then, in each subclass, I added an override getter/setter pair for the field, and annotated the getter with the desired ID generation. This allowed every subclass to have its own sequence generation (in my case), but to still allow polymorphic usage in other code.
- When doing this, a classic gotcha was to forget to mark the base class getters @Transient. This caused them to be mapped in both the mapped superclass and the subclass, resulting in multiple writable mappings which Eclipselink complained about loudly! It feels counter-intuitive to set them as transient, but the important point is that they will be mapped by the subclass.
- When doing this, I had to explicitly annotate the mapped superclass for property annotation using @Access(AccessType.PROPERTY). This is because normally the default type of access (field or property) is derived automatically from where the primary key is annotated. In this case, there was no such annotation on the mapped superclass so it needed telling – again, strange and loud Eclipselink errors resulted without this as my @Transient annotations on getters in the superclass were being ignored, as it defaulted to field annotation!
- Another issue was that I wanted a name field in the base superclass, but wanted to override this in the subclasses. In fact one tree implementation had node classes with an associated keyword class, where the name was stored on the keyword class and delegated from the getter on the node class, which in turn overrode the getter in the mapped superclass. Again, I marked the name getters as @Transient in both the tree mapped superclass and in the node class, and just mapped the name in the keyword class. This allowed other code to obtain the name polymorphically using a base superclass reference, but the actual name came from 2 levels further down. JPA was kept happy as the name was mapped wherever I needed it to be.
The following sample code fragment from my mapped superclass illustrated some of these points:-
package uk.co.salientsoft.test.domain;
import javax.persistence.Access;
import javax.persistence.AccessType;
import javax.persistence.Column;
import javax.persistence.MappedSuperclass;
import javax.persistence.Transient;
import uk.co.salientsoft.jpa.ColumnMeta;
/**
* Entity implementation class for Entity: TreeNode
* This mapped superclass performs the common tree/treepath handling for all tree entities
*
*/
@MappedSuperclass
@Access(AccessType.PROPERTY)
public abstract class TreeNode {
private long TreeNodeId;
private String treePath;
private int nodeLevel;
@Transient public long getTreeNodeId() {return this.TreeNodeId;}
@Transient public void setTreeNodeId(long TreeNodeId) {this.TreeNodeId = TreeNodeId;}
@Transient public abstract String getName();
@Transient public abstract void setName(String name);
…
Leave a Reply
You must be logged in to post a comment.