Vincent Partington has more than 10 years of enterprise Java experience. He has written and presented on topics as diverse as performance, security, RIA, and persistence technologies. Vincent Partington is the Chief Technical Officer of XebiaLabs where he is responsible for their flagship deployment automation product Deployit. Vincent is a DZone MVB and is not an employee of DZone and has posted 25 posts at DZone. You can read more from them at their website. View Full User Profile

JPA Implementation Patterns: Using UUIDs as Primary Keys

09.01.2009
| 22207 views |
  • submit to reddit

The default way in JPA for primary keys is to use the @GeneratedValue annotation with the strategy attribute set to one of AUTO, IDENTITY, SEQUENCE, or TABLE. You pick the most appropriate strategy for your situation and that's it.
But you can also choose to generate the primary key yourself.

Using UUIDs for this is ideal and has some great benefits. In our current project we've used this strategy by creating an abstract base class our entities to inherit from which takes care of dealing with primary keys.

@MappedSuperclass
public abstract class AbstractBaseEntity implements Serializable {
	private static final long serialVersionUID = 1L;
 
	@Id
	private String id;
 
	public AbstractBaseEntity() {
		this.id = UUID.randomUUID().toString();
	}
 
	@Override
	public int hashCode() {
		return id.hashCode();
	}
 
	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (!(obj instanceof AbstractBaseEntity)) {
			return false;
		}
		AbstractBaseEntity other = (AbstractBaseEntity) obj;
		return getId().equals(other.getId());
	}
}

Using UUIDs versus sequences in general has been widely discussed on the web, so I won't go into too much detail here. But here are some pro's and con's:

Pros

  • Write this base class once and every entity gets an Id for free. If you implement equals and hashcode as above you also throw that one in as a bonus.
  • UUID are Universal Unique(what's in a name). This means that you get great flexibility if you need to copy/merge records from location a to b without having to re-generate keys. (or use complex sequence strategies).
  • UUIDs are not guessable. This means it can be safer to expose them to the outside world in let's say urls. If this would be good practice is another thing.

Cons

  • Performance can be an issue. See http://johannburkard.de/blog/programming/java/Java-UUID-generators-compared.html, Some databases don't perform well with UUIDs (at least when they are stored as strings) for indexes.
  • Ordering, Sorting. Speaks for itself.
  • JPA specific: you cannot test if a record has already been persisted by checking if the Id field has been set. One might argue if you need such checks anyway.

Conclusion

UUIDs are easy to use, but wouldn't it be nice if the JPA spec would open up to include UUID as a strategy? Some JPA implementations, like Hibernate, already have support for this:

@Id @GeneratedValue(generator="system-uuid")
@GenericGenerator(name="system-uuid",
strategy = "uuid")

 

From http://blog.xebia.com/
Published at DZone with permission of Vincent Partington, author and DZone MVB.

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)

Comments

Erik Post replied on Tue, 2009/09/01 - 7:48am

I think this is information is very useful, if a little low on details. For instance, one of the central problems when using JPA, Hibernate and what have you is that typically, entities which have not yet been persisted have null id's. If you look at, say, the JPA entities that NetBeans generates for you, you will find that this id is used to define the entity's equals() and hashCode() methods.

The equals() method is in turn used by Java's Collections, e.g. to determine whether your entity is in a collection or not, or to delete entities from a collection. Putting entities with null id's into a collection can lead to serious bugs, due to equals() reporting that two entities are equivalent, when in fact they're not. Nastiness ensues; for exampe, you may be deleting the wrong entity from a collection. In addition, changing an object's hashCode() during the lifetime of an object violates Java's contract for hashCode().

Using UUID's instead allows you to avoid these problems. This is one of the many snags you encounter when using certain ORM's; they're supposed to make things easier, but you need to be well aware of what goes on under the hood.

Denis Lapanik replied on Tue, 2009/09/01 - 2:15pm

you do NOT NEED lines 21, 22. instanceof checks for null :-)

Alexander Ashitkin replied on Tue, 2009/09/01 - 2:34pm

@Id @GeneratedValue(generator="system-uuid")
@GenericGenerator(name="system-uuid", strategy = "uuid")
 Does id value will be generated once the entity have been persisted and newly created instances have null id? 

Rainer P. replied on Tue, 2009/09/01 - 3:54pm

Nice article!

One question: Why do you think that only uuids can have a common base class? IMHO you can also have base classes with an long id - what's the difference?

To the performance problem: The most evil thing about uuid alues is the space they need. I had a project with uuids persisted in a varchar(36). Consider not only the private keys but also the foreign keys: In fact, more then 60 % of the database space was used by this keys and the huge indexes built on them!

But in general there are really some good points in using uuids, especially in all of the (many) use cases when you want to move data from one database to other.

I don't see the sorting / ordering problem, sorry. You surely should never rely on sequence-generated ids for sorting; it is just wrong. Or do I have missed the problem here?

@eriksensei and the equal problem with not-yet-persisted entities: Just throw an exception in such cases and you will have no more problem - it is just a (self-defined) error in such cases then to compare "not yet existing" entities - a point which can be tested and avoided easily.
Or follow the tip from G. King to use natural keys instead.
And never forget the compareTo method! TreeMaps e.g. will thank you ;-)
IMHO every entity must have a _good_ compareTo method implementation which really only returns 0 when it _is_ the same entity.
Same helps with the sorting / ordering mentioned in the article.

Erik Post replied on Wed, 2009/09/02 - 8:46am in response to: Rainer P.

@Rainer P: You're right about the exception of course, but it is horribly inconvenient not being able to construct object graphs prior to persisting. The natural key thing very often won't fly, at least in a lot of the entities I've encountered. Another approach would be to add a separate (non-database) UUID key just for hashCode()'s sake. It's all about choosing the lesser of several evils in my opinion.

Andy Jefferson replied on Sun, 2009/09/06 - 1:17pm

> UUIDs are easy to use, but wouldn't it be nice if the JPA spec would open up to include UUID as a strategy?

JDO already includes such things in its spec and has for 3+ years.

--Andy (DataNucleus)

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.