Monday, June 9, 2008

ORM Models : Hibernate with Annotations and Cayenne

Before I used Hibernate (*1), I played around with Cayenne (*2). I liked its Visual Modeler. It allows you to create models, and map them to database fields. It also generates Java Code and sql scripts. All packaged up. I liked it. Untill I worked with Hibernate.

I must admit, I tend to not like things that get too much hype, and sometimes the hype is much deserved. So, in such cases I kind of 'miss the boat'. Hibernate falls in that category.. But, in the end, having a better grasp at competing technologies never hurts.. Let me explain, and I will limit the discussion to few items, which I feel are a 'make or break', at least for me, and please take it for what its worth.

Lets take Cayenne first. Lets take a look at its generated model. Or should I say 'models', as it creates two files.

public class Blog extends _Blog {
...

}


pretty small.. Wait till you see the _Blog


public class _Blog extends org.apache.cayenne.CayenneDataObject {

public static final String BLOG_DESCRIPTION_PROPERTY = "blogDescription";

public static final String BLOG_TITLE_PROPERTY = "blogTitle";

public static final String DATE_CREATED_PROPERTY = "dateCreated";

public static final String DATE_MODIFIED_PROPERTY = "dateModified";


public static final String BLOG_ID_PK_COLUMN = "BLOG_ID";


public void setBlogDescription(String blogDescription) {

writeProperty("blogDescription", blogDescription);

}

public String getBlogDescription() {

return (String)readProperty("blogDescription");

}


public void setBlogTitle(String blogTitle) {

writeProperty("blogTitle", blogTitle);

}

public String getBlogTitle() {

return (String)readProperty("blogTitle");

}


public void setDateCreated(java.util.Date dateCreated) {

writeProperty("dateCreated", dateCreated);

}


public java.util.Date getDateCreated() {

return (java.util.Date)readProperty("dateCreated");

}


public void setDateModified(java.util.Date dateModified) {

writeProperty("dateModified", dateModified);

}


public java.util.Date getDateModified() {

return (java.util.Date)readProperty("dateModified");

}


}


So, what is the issue..

Firstly, Cayenne creates a model, which is for you to extend and customize, which extends another class (_Blog.java).

The _ in _Blog.java means, 'do not touch'. Cayenne reserves the full right to over-write it. This is a problem, in at least one of these ways:
  1. If I have to modify the model, I have to use the 'Modeler' and then 're-generate' the code.
  2. I am tied to Cayenne for the model class, and the proof? _Blog.java itself extends org.apache.cayenne.CayenneDataObject
Secondly, Blog.java is not a POJO.. It extends _Blog.java and that itself extends CayenneDataObject (Does it have to be a POJO? No, but Does it help if it is a POJO? Of Course..)

Thirdly, Can you find getBlogId() or setBlogId(...) in the _Blog.java ? Neither do I. Cayenne documentation says, "Normally it is not advisable to map primary and foreign key columns (PK and FK) as Java class properties (ObjAttributes)." (*3) But they do not explain the wisdom in it. So, you have to write an elaborate custom method to get the id from the model itself (see the referenced link). But to set an id, that is still a no-no. Of course, you may agree with the methodology of not allowing access to Id from model, but I tend to like ot have it.

So, to keep it short, I'll just limit my discussion to these three points. And no, I'm not skipping Hibernate. Here I'll just relate Hibernate with these three points.

Lets see a Hibernate Model class.

@Entity

@Table(name="CLIENT")
public class Client implements Serializable {


@Id
@Column(name="id", nullable=false)
@GeneratedValue(strategy=GenerationType.AUTO)
private Long id;

@Column(name="name", nullable=false, length=255, unique=true)
private String name;


@Column(name="account_manager",nullable=true, length=255)
private String accountManager;


@Column(name="account_owner",nullable=true, length=255)
private String accountOwner;


public Client() {


}


public Long getId() {
return id;
}


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


public String getName() {
return name;
}


public void setName(String name) {
this.name = name;
}


public String getAccountManager() {
return accountManager;
}

public void setAccountManager(String accountManager) {
this.accountManager = accountManager;
}


public String getAccountOwner() {
return accountOwner;
}


public void setAccountOwner(String accountOwner) {
this.accountOwner = accountOwner;
}


}


This example of the Model class uses Annotations, though all of this can be accomplished using an XML file. But I prefer Annotations, and using Annotations has its benefits, which I won't mention here, as I'm discussing Model classes.

So, First issue : I generate the Class. Even if there exists tools that generate the Model classes, there is nothing stopping me from writing or modifying it.

Secondly, the Client.java extends Serializable, which qualifies it for a POJO. I know, I know, this class has javax.persistence.Entity, javax.persistence.Id and similar annotations, but, there is nothing that prevents me from instantiating a Client Class, and setting all the properties and have it then persist..

Thirdly, getId() and setId(...). Ahh. Dosen't that make you breathe easier.

In any case, I would use Hibernate just for these reasons, even though there are other equally compelling reasons to use it. Like, javax.persistence/EJB3 supported annnotations, the Hibernate model above could be used with JPA without changing a single line of code. (Of course, a Hibernate DAO will differ from JPA DAO), extensive Maven support for Hibernate and other 'nice to haves'.

(*1) http://www.hibernate.org/
(*2) http://cayenne.apache.org/
(*3) http://cayenne.apache.org/doc20/accessing-pk-and-fk-values.html