Friday, May 23, 2008

JPA Identity Interger/Long or String?

In my previous article JAXB and the nasty XmlID, I discussed how if you wanted to use @XmlID and @XmlIDREF as pointer references to XML serialized objects that the values had to be Strings. In some cases they might be fine, especially if your primary key is a compound key that you are serializing. However, the more common case for simple persisted objects is you have used some numeric identity value as the primary key. A short search will show almost all demos/tutorials and examples of JPA use either a Integer or Long wrapper. This of course is not a restriction of JPA. You can use anything you want as the primary key, but if you are going to leverage some of the @GenerateValue options, unless you are willing to define your own Alpha scheme, you will get a numeric value. Which leads me to my point: If I want to XML Serialize an object using JAXB (which is much easier than doing it by hand with a DOM Document) that contains foreign key references, surely I can do this in some way without having to convert my identities to Strings?

The simple answer is yes, but not by applying the JAXB annotations on the persisted property. Instead, you would need to create a proxy method that can convert your PK (Primary Key) into a string representation, and this method is tagged with the @XmlID annotation. In order to be used by JAXB you need at least a String property. Lets look at a simple code example.
   import javax.xml.bind.annotation.XmlID;
   import javax.xml.bind.annotation.XmlRootElement;
   import javax.xml.bind.annotation.XmlTransient;
   import javax.xml.bind.annotation.XmlAttribute;
   import javax.persistence.Entity;
   import javax.persistence.Id;
   import javax.persistence.GeneratedValue;
   import javax.persistence.Transient;

   @XmlRootElement
   @Entity
   public class PersistedObject {
       @Id
       @GeneratedValue(strategy=TABLE, generator="CUST_GEN")
       @XmlTransient // we are not going to write out the id
       private Long id = null;

       @XmlTransient
       @Transient   // this is not an entity managed attribute
       private String identityString = null;

       @XmlTransient
       public Long getId( ) { return id; }

       public void setId( Long id ) { this.id = id; }

       @XmlID
       @XmlAttribute(name="id")
       public String getIdentityString( ) {
          return ( id != null ) ? id.toString() : "0";
       }
   }


In this manner, we can denote our JPA identity with the data-type which makes sense (either a Long or Integer) yet allow for easy XML Serialization through the use of the @XmlID field on the getIdentityString() method. This is certainly not ideal, and I would've preferred to put the annotation on a method only, however JAXB requires the XmlID tag on a property.

Unfortunately for me, I only thought of this after converted my persistent beans, services and unit tests over to String Ids. Fortunately SCM tools (subversion in this case) come to the rescue and I can easily back out my changes.

No comments: