Tutorial Chapter 2 – Easier Development and Maintenance
Tired of wiring in an
id,
version, and
timestamp field into all of your Hibernate objects? There’s an easy way to solve this pain once and for all of your classes. Avoid code-repetition: today’s article focuses on using Hibernate Annotations to map common fields into one mapped superclass.
If you have not done so already, and need to get a bare bones hibernate application up and running,
this guide should get you up and running in a few minutes.
Basic Concepts:
You’re using Hibernate, or at least getting your feet wet at this point, so let’s assume that you’ve started to notice a pattern. You need in all of your objects:
- an id column
- a version column
- perhaps a timestamp column
Lets say that you are also aware of the
hashcode() and equals() issues that come with using objects in collections, so you want to define some basic functionality to handle that situation as well.
It would be pretty nice if we could do all this in one place, keeping updates quick and painless. Well, using the EJB-3/Hibernate
@MappedSuperclass annotation, we can. Mapped superclass tells Hibernate that you want all mappings defined in the base class to apply and be included in all classes which extend from it.
Instructions:
Let’s take a look at an example base-class that will meet our above needs.
- Copy the following code examples into your HIbernate Annotations enabled project
PersistentObject.java
import java.io.Serializable;
import java.util.Date;
import javax.persistence.Column;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.MappedSuperclass;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import javax.persistence.Version;
@MappedSuperclass
public abstract class PersistentObject implements Serializable
{
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "id", updatable = false, nullable = false)
private Long id = null;
@Version
@Column(name = "version")
private int version = 0;
@Temporal(TemporalType.TIMESTAMP)
@Column(name = "last_update")
private Date lastUpdate;
protected void copy(final PersistentObject source)
{
this.id = source.id;
this.version = source.version;
this.lastUpdate = source.lastUpdate;
}
@Override
public boolean equals(final Object obj)
{
if (this == obj)
{
return true;
}
if (obj == null)
{
return false;
}
if (!(obj instanceof PersistentObject))
{
return false;
}
final PersistentObject other = (PersistentObject) obj;
if (this.id != null && other.id != null)
{
if (this.id != other.id)
{
return false;
}
}
return true;
}
protected static boolean getBooleanValue(final Boolean value)
{
return Boolean.valueOf(String.valueOf(value));
}
public Long getId()
{
return this.id;
}
@SuppressWarnings("unused")
private void setId(final Long id)
{
this.id = id;
}
public int getVersion()
{
return this.version;
}
@SuppressWarnings("unused")
private void setVersion(final int version)
{
this.version = version;
}
public Date getLastUpdate()
{
return this.lastUpdate;
}
public void setLastUpdate(final Date lastUpdate)
{
this.lastUpdate = lastUpdate;
}
} |
import java.io.Serializable;
import java.util.Date;
import javax.persistence.Column;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.MappedSuperclass;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import javax.persistence.Version;
@MappedSuperclass
public abstract class PersistentObject implements Serializable
{
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "id", updatable = false, nullable = false)
private Long id = null;
@Version
@Column(name = "version")
private int version = 0;
@Temporal(TemporalType.TIMESTAMP)
@Column(name = "last_update")
private Date lastUpdate;
protected void copy(final PersistentObject source)
{
this.id = source.id;
this.version = source.version;
this.lastUpdate = source.lastUpdate;
}
@Override
public boolean equals(final Object obj)
{
if (this == obj)
{
return true;
}
if (obj == null)
{
return false;
}
if (!(obj instanceof PersistentObject))
{
return false;
}
final PersistentObject other = (PersistentObject) obj;
if (this.id != null && other.id != null)
{
if (this.id != other.id)
{
return false;
}
}
return true;
}
protected static boolean getBooleanValue(final Boolean value)
{
return Boolean.valueOf(String.valueOf(value));
}
public Long getId()
{
return this.id;
}
@SuppressWarnings("unused")
private void setId(final Long id)
{
this.id = id;
}
public int getVersion()
{
return this.version;
}
@SuppressWarnings("unused")
private void setVersion(final int version)
{
this.version = version;
}
public Date getLastUpdate()
{
return this.lastUpdate;
}
public void setLastUpdate(final Date lastUpdate)
{
this.lastUpdate = lastUpdate;
}
}
—-
Extending the Base Class:
Using the MockObject class from Chapter 1, now extend PersistentObject. We can remove the fields that are now mapped in PersistentObject and add new fields to fit our business needs. Notice that MockObject does not define fields or methods for
id,
version,
equals(),
hashcode(), or
timestamp; this behavior is now contained within our mapped superclass.
From now on, all you need to do to incorporate this behavior into new classes is to extend PersistentObject.
MockObject.java
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Table;
import javax.persistence.Transient;
@Entity
@Table(name = "mock_objects")
public class MockObject extends PersistentObject
{
@Transient
private static final long serialVersionUID = -3621010469526215357L;
@Column
private String textField;
@Column
private long numberField;
public String getTextField()
{
return textField;
}
public void setTextField(String textField)
{
this.textField = textField;
}
public long getNumberField()
{
return numberField;
}
public void setNumberField(long numberField)
{
this.numberField = numberField;
}
} |
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Table;
import javax.persistence.Transient;
@Entity
@Table(name = "mock_objects")
public class MockObject extends PersistentObject
{
@Transient
private static final long serialVersionUID = -3621010469526215357L;
@Column
private String textField;
@Column
private long numberField;
public String getTextField()
{
return textField;
}
public void setTextField(String textField)
{
this.textField = textField;
}
public long getNumberField()
{
return numberField;
}
public void setNumberField(long numberField)
{
this.numberField = numberField;
}
}
—-
To prove it, we’ll re-run our demo application from the quick-start guide. Notice that the results are the same. I’ve added a little logic to show that our new columns work as well.
HibernateDemo.java
Our driver class does the following things:
- Get a handle to Hibernate Session
- Create and persist two new MockObjects
- Assign values into the textField of each object
- Print out the generated IDs and set fields
For referenced HibernateUtil,java, please see Chapter 1.
import org.hibernate.Transaction;
import org.hibernate.classic.Session;
public class HibernateDemo
{
public static void main(String[] args)
{
Session session = HibernateUtil.getSessionFactory().getCurrentSession();
Transaction transaction = session.beginTransaction();
MockObject object0 = new MockObject();
object0.setTextField("I am object 0");
MockObject object1 = new MockObject();
object1.setTextField("I am object 1");
session.save(object0);
session.save(object1);
transaction.commit();
System.out.println("Object 0");
System.out.println(object0.getTextField());
System.out.println("Generated ID is: " + object0.getId());
System.out.println("Generated Version is: " + object0.getVersion());
System.out.println("Object 1");
System.out.println(object1.getTextField());
System.out.println("Generated ID is: " + object1.getId());
System.out.println("Generated Version is: " + object1.getVersion());
}
} |
import org.hibernate.Transaction;
import org.hibernate.classic.Session;
public class HibernateDemo
{
public static void main(String[] args)
{
Session session = HibernateUtil.getSessionFactory().getCurrentSession();
Transaction transaction = session.beginTransaction();
MockObject object0 = new MockObject();
object0.setTextField("I am object 0");
MockObject object1 = new MockObject();
object1.setTextField("I am object 1");
session.save(object0);
session.save(object1);
transaction.commit();
System.out.println("Object 0");
System.out.println(object0.getTextField());
System.out.println("Generated ID is: " + object0.getId());
System.out.println("Generated Version is: " + object0.getVersion());
System.out.println("Object 1");
System.out.println(object1.getTextField());
System.out.println("Generated ID is: " + object1.getId());
System.out.println("Generated Version is: " + object1.getVersion());
}
}
Which results in the following output:
Object 0
I am object 0
Generated ID is: 1
Generated Version is: 0
Object 1
I am object 1
Generated ID is: 2
Generated Version is: 0
—-
Considering the Forces:
It may be worth mentioning that using a base class for all of your objects can be a blessing and a curse. If you begin to reference objects using the more generic PersistentObject type, it is possible that you could find yourself constrained to the behavior of this one class. When this happens, consider defining PersistentObject as an
Interface and then implement that interface with any number of
@MappedSuperclass objects.
There are benefits to referencing objects by their generic type, as well. One example, which we’ll take a look at this when we dive into the
Dao pattern, makes it very easy to perform a wide array of operations on your Hibernate objects.
Congratulations. You should now have a mapped superclass to contain your common functionality.
References:
- Hibernate’s Standard Tutorial
- Hibernate Annotations Reference
This article is part of a series:
Guide to Hibernate Annotations
The hashcode and equals are broken.
You have to use a business key for that.
More info at http://www.hibernate.org/109.html
I am already using generic top level domain class with id, version etc, but with hbms, so I have to copy/paste hbm mappings for them. Is there any way to do the same thing in hbm, something like @MappedSuperclass?
I would favor composition over inheritance for this problem.
Also, for auditing, pl/sql is sometimes a better way of handling it if there are >1 data access path to the tables. pl/sql can still be used to get the user id with connection pooling.
Also, I don’t think your hash/equals are going to work very well in particular circumstances. If you disconnect and reconnect objects, or you have multiple new objects in a collection (set) without id’s.
I also tried using super class for annotations based hibernate POJOs. First problem i faced is that, if your child class uses different kind of primary key or that class is join class. How you can override your annotations defined in base class? I guess not
cherouvim >> I’m wondering… I don’t believe they are broken for the purposes of this example, but they will definitely need to be overridden for any class with a business key that is not the ‘id’ primary key. What do you think?
ashish >> I’ll try to get back to you on that, but I am pretty sure it is possible to override superclass annotations.
The problem with using id for hashcode/equals means that as soon as the pojo gets an id, then it’s a different pojo.
Also, if you use the version for this, the above will keep happening whenever you change anything on the pojo. You’ll not be able to merge() it as well.
Have a look at: http://www.hibernate.org/hib_docs/reference/en/html/transactions.html#transactions-basics-identity
11.1.3. Considering object identity
“Never use the database identifier to implement equality, use a business key, a combination of unique, usually immutable, attributes. The database identifier will change if a transient object is made persistent. If the transient instance (usually together with detached instances) is held in a Set, changing the hashcode breaks the contract of the Set. Attributes for business keys don’t have to be as stable as database primary keys, you only have to guarantee stability as long as the objects are in the same Set.”
Hey, how does the equals method look now?
Hi,
Suppose I have a status field in the superclass. And I have annotated it properly because I need the field to be persistent.
Now, in one of the the subclasses I feel that I would rather have that property as a Transient field – so I override the getters/setters and annotate it with @Transient.
The rest of the scenario is just as you have described.
Will this work ? I haven’t been able to do so. Am I missing something here ? Shouldn’t hibernate honor the annotation on the subclass-getter-method ?
I’m not sure you can completely remove a mapped field because that would seem to go against inheritance rules (See Liskov Substitution Principle” but I do think there’s something that may help. Try the @ AnnotationOverride property. This might help you convert that field into something at least more useful… It will still be there though…
Search for @ AttributeOverride
I also use a superclass to map commons fields between all my persistent objects. It seemed to work well until I faced the problem to change inheritance mapping strategy (table per concrete class, table per class hierarchy, table per subclass…) for a part of the hierarchy. The problem is that it seems to be impossible to mix inheritance strategies inside the same hierarchy. Since all objects are members of the same hierarchy it is impossible to change the strategy without changing all strategies.
Does anybody encountered this kind of problem?
I’m trying to find an answer to this problem. I want to do what you are doing in the code by having a Version and Timestamp of any updates to the object, however, we want to use xml mappings. This seems to be limited by the DTD which forces the xml mappings to have either a Version OR a Timestamp but not both.
On a side note (seems like as good a place as any to ask this question). I also want a Timestamp for the creation date. The column this is bound is described like the following:
But for this to work. I need to assign the current date upon execution of Insert on this object. Any suggestions? Thanks.
I know recently Lincoln used Spring’s interceptor to do something similiar with created on date. I’ll make sure he speaks to that since I don’t have many of the details.
But do you have to have the date set at insert compared to setting it using Java. i.e. bean.setCreatedOn(new Date());?
great and simple article.
thanks
In my project I have a similar set up as in the example above, I am using a Super class for id.
@Override
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = “id”, updatable = false, nullable = false)
public Long getId() {
return this.id;
}
All my domains extend from that class. Since some of the domains have there own sequences, I was expecting that
GenerationType.AUTO will pick up from the respective sequence for that domain.
I get this error, I know I dont have any sequence called as “hibernate_sequence”
Caused by: org.postgresql.util.PSQLException: ERROR: relation “hibernate_sequence” does not exist
Does anyone have any suggestion?
Thanks in advance
@PK, you need to specify the sequence name. use something like:
@Id
@SequenceGenerator(name=”YOUR_SEQ_GEN”,sequenceName=”YOUR_SEQ”, initialValue=10000,allocationSize=1)
@GeneratedValue(strategy=GenerationType.SEQUENCE,generator=”YOUR_SEQ_GEN”)
private Long id;
Could anyone show how to do the mapping in .hbm.xml definitions instead of annotations. Our outsourced developer insists on using configuration over convention.
If you look at the hibernate documentation, there is a lot of information there that describes how to do this.
http://docs.jboss.org/hibernate/orm/3.3/reference/en/html/mapping.html
Why is PersistentObject class abstract? I don’t see any abstract methods. Is it for intent? i.e. you don’t want anyone to initialize it?
Yes, that’s exactly it.
[…] ocpsoft.org/java/hibernate-use… […]
I have tried something similar to the above, except my equivalent to PersistentObject is in a separate JAR| called xxxxx-common.jar and I still get the error about MyClass (super class) has no identifier.
So is there a way of getting this to work, when your abstract class is in a JAR rather than just classes folder?
Sounds like a bug. I would submit an issue on the issue tracker of your EE server (or with Hibernate or EclipseLink if you are using them standalone.)
I would like to reverse engineer my schema for some tables to use mapped superclasses.
I did it many years ago,but I can’t figure out how to specify now.
Two days of googling and reading jboss and hibernate documentation and asking the question on stackoverflow and I am stuck.
Certainly I am not the only one reverse engineering this.