Thursday, January 12, 2012

Extending your JPA POJOs

Extensibility is an important characteristic in many architectures.  It is a measure of how easy (or difficult) it is to add or change functionality without impacting existing core system functionality.

Let's take a simple example.  Suppose your company has a core product to track all the users in a sports club.  Within your product architecture, you have a domain model represented by JPA POJOs.  The domain model contains many POJOs including - of course - a User POJO. 
package com.alex.staveley.persistence
/**
 * User entity.  Represents Users in the Sports Club. 
 *
 * Note: The SQL to generate a table for this in MySQL is:
 *
 * CREATE TABLE USER (ID INT NOT NULL auto_increment, NAME varchar(255) NOT NULL, 
 *  PRIMARY KEY (ID)) ENGINE=InnoDB;
 */ 
@Entity
public class User {
    /* Surrogate Key - automatically generated by DB. */ 
    @GeneratedValue(strategy=GenerationType.IDENTITY) 
    @Id
    private int id;
 
    private String name;

    public int getId() {
        return id;
    }
 
    public void setName(String name) {
        this.name=name;
    }
 
    public String getName() {
        return name;
    }
}
Now, some customers like your product but they need some customisations done before they buy it.  For example, one customer wants the attribute birthplace added to the User and wants this persisted.  The logical place for this attribute is - of course - in the User POJO, but no other customer wants this attribute.  So what do you do? Do you make a specific User class just for this customer and then swap it in just for them?  What happens when you change your Product User class then?  What happens if another customer wants another customisation?  Or changes their mind?  Are you sensing things are going to get messy?

Thankfully, one implementation of JPA: Eclipselink helps out here.  The 2.3 release (available since June 2011, latest release being a 2.3.2 maintenance released just recently, 9th December, 2011) includes some very features which work a treat for this type of scenario.  Let's elaborate.  By simply adding the @VirtualAccessmethods Eclipselink annotation to a POJO we signal to Eclipselink that the POJO may have some extra (also known as virtual) attributes.  You don't have to specify any of these extra attributes in code, otherwise they wouldn't be very virtual!  You just have to specify a generic getter and setter to cater for their getting and setting.  You also have to have somewhere to store them in memory, something like a good old hashmap - which of course should be transient because we don't persist the hashmap itself.  Note: They don't have to be stored in a HashMap, it's just a popular choice!

Let's take a look at our revamped User which is now extensible!
@Entity
@VirtualAccessMethods
public class User {
    /* Surrogate Key - automatically generated by DB. */ 
    @GeneratedValue(strategy=GenerationType.IDENTITY) 
    @Id
    private int id;
 
    private String name;
 
    @Transient
    private Map extensions = new HashMap();

    public int getId() {
        return id;
    }
 
    public void setName(String name) {
        this.name=name;
    }
 
    public String getName() {
        return name;
    }
 
    public  T get(String name) {
        return (T) extensions.get(name);
    }
 
    public Object set(String name, Object value) {
        return extensions.put(name, value);
    } 
}
So, is that it?  Well there's a little bit more magic.  You have to tell eclipselink about your additional attributes.  More specifically: what their names and datatypes are. You do this by updating your eclipselink-orm.xml which resides in the same META-INF folder that the persistent.xml is in. 
Now this configuration simply states, the User entity has an additional attribute which in java is "thebirthplace" and it is virtual.  This means it is not explictly defined in the POJO but if we were to debug things, we'd see the attribute having the name 'thebirthplace' in memory.  This configuration also states that the corresponding database column for the attribute is birthplace.  And eclipselink can get and set this attribute using the generic get /set methods.

You wanna test it? Well add the column to your database table.  In MySql this would be:  
        alter table user add column birthplace varchar(64)

Then run this simple test:

So now, we can have one User POJO in our product code which is extensible.  Each customer can have their own attributes added to the User - as they wish.  And of course, each customer is separated from all other customers very easily by just ensuring each customer's extensions resides in a specific eclipslink-orm.xml.  Remember, you are free to name these files as you want and if you don't use the default names you just update the persistence.xml file to state what names you are using.

This approach to extending the User, means that when we want to update the User POJO in our product, we only have to update one (and only one) User POJO.  But, when specific attributes have to be added for specific customer(s), we don't touch the User POJO code.  We simple make the changes to the XML and do not have to recompile anything from the core product. And of course, at any time it is easy to see what the customisations for any customer are by just simply looking at the appropriate eclipselink-orm.file.

Ye Ha. Happy Extending! 

References:
  1. http://wiki.eclipse.org/EclipseLink/UserGuide/JPA/Advanced_JPA_Development/Extensible_Entities
  2. http://www.eclipse.org/eclipselink/

No comments:

Post a Comment