ONJava.com -- The Independent Source for Enterprise Java
oreilly.comSafari Books Online.Conferences.

advertisement

AddThis Social Bookmark Button

Don't Let Hibernate Steal Your Identity
Pages: 1, 2, 3

We've gained several benefits by moving to a pure object, id. Our implementation of equals() and hashCode() is much simpler and easier to read. These methods are no longer error-prone and are guaranteed to work with Collections both before and after the objects are saved. Hibernate is also a bit faster, since it no longer needs to read a sequence value from the database before saving a new object. Furthermore, the new definition of equals() and hashCode() is universal for all objects that contain an object id. That means we can move those methods to an abstract parent class. We no longer need to re-implement equals() and hashCode() for every domain object, and we no longer need to think through which combination of fields is both unique and immutable for each class. Instead, we simply extend the abstract parent class. Of course, we don't want to force our domain objects to extend from a parent class, so we'll also define an interface to keep things flexible.



public interface PersistentObject {
    public String getId();
    public void setId(String id);

    public Integer getVersion();
    public void setVersion(Integer version);
}

public abstract class AbstractPersistentObject
        implements PersistentObject {

    private String id = IdGenerator.createId();
    private Integer version;

    public String getId() {
        return id;
    }
    public void setId(String id) {
        this.id = id;
    }

    public Integer getVersion() {
        return version;
    }
    public void setVersion(Integer version) {
        this.version = version;
    }

    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null ||
            !(o instanceof PersistentObject)) {

            return false;
        }

        PersistentObject other
            = (PersistentObject)o;

        // if the id is missing, return false
        if (id == null) return false;

        // equivalence by id
        return id.equals(other.getId());
    }

    public int hashCode() {
        if (id != null) {
            return id.hashCode();
        } else {
            return super.hashCode();
        }
    }

    public String toString() {
        return this.getClass().getName()
            + "[id=" + id + "]";
    }
}

We now have a simple and effective way to create domain objects. They extend AbstractPersistentObject, which automatically gives them an id when they're first created and properly implements equals() and hashCode(). They also get a reasonable default implementation of toString() that they can optionally override. If this is a test object or an example object for a query-by-example the id can be changed or set to null. Otherwise it should not be altered. If for some reason we need to create a domain object that extends some other class, it can implement the PersistentObject interface rather than extend the abstract class.

Our Person class is now much simpler:

public class Person
    extends AbstractPersistentObject {

    // Person-specific fields and behavior here

}

The Hibernate mapping document has not changed since the last example. We don't bother to tell Hibernate about the abstract parent class, and instead just make sure that every PersistentObject mapping file includes an id (with an "assigned" generator) and a version tag with unsaved-value="null". Astute readers may have noticed that an id gets assigned every time a persistent object is instantiated, which means that even when Hibernate is reading an existing object from the database it will get a new id when Hibernate creates an in-memory instance of the saved object. That's OK; Hibernate will then call setId() on the object to replace the newly assigned id with the saved id. The extra id generation is not a problem as long as our id generation algorithm is cheap (i.e., it does not need to contact the database).

So far so good, but we've left out one critical detail: how to implement IdGenerator.createId(). We can define some criteria for our ideal key-generation algorithm:

  • Can be cheaply generated without contacting the database.
  • Is guaranteed unique, even across different VMs and different machines.
  • If necessary can be generated by, or at least be compatible with, other programs, programming languages, or databases.

What we need is a universally unique identifier (UUID). UUIDs are 16-byte (128-bit) numbers that adhere to a standard format. The String version of a UUID looks like this:

2cdb8cee-9134-453f-9d7a-14c0ae8184c6

The characters are simple hexadecimal representations of the bytes, while the dashes delimit different portions of the number. This format is simple and easy to work with, but at 36 characters it is a bit long. The dashes are always located in the same place and can be removed to reduce the size to 32 characters. For a more compact representation you can create an object that holds the number as either a byte[16] array, or as two eight-byte longs. If you're using Java 1.5 or later, you can use the UUID class directly, though this is not the most compact in-memory format. For more information, see the Wikipedia UUID entry and the JavaDoc entry for the UUID class.

There are several implementations of UUID-producing algorithms. Since the final UUID is in a standard format, it doesn't matter which one we use in our IdGenerator class. We can even change the implementation at any time or mix and match different implementations, since each id is guaranteed unique regardless of the algorithm. If you're using Java 1.5 or later, the most convenient implementation is the java.util.UUID class:

public class IdGenerator {
    public static String createId() {
        UUID uuid = java.util.UUID.randomUUID();
        return uuid.toString();
    }
}

For those not using Java 1.5+, there are at least two external libraries that implement UUIDs and are compatible with earlier versions of Java: the Apache Commons ID project and the Java UUID Generator (JUG) project. Both are available under the Apache License (JUG is also available under the LGPL).

Here's an example implementation of IdGenerator using the JUG library:

import org.safehaus.uuid.UUIDGenerator;

public class IdGenerator {

    public static final UUIDGenerator uuidGen
        = UUIDGenerator.getInstance();

    public static String createId() {
        UUID uuid
            = uuidGen.generateRandomBasedUUID();
        return uuid.toString();
    }
}

What about the UUID generator algorithm that is built into Hibernate? Is this a suitable way to get UUIDs for object identity? Not if you want object identity to be independent of object persistence. While Hibernate does have the option of generating UUIDs for you, we're back to the same old problem: your objects will not receive an ID at the time of creation, and will have to wait until they're saved.

The major drawback of using UUIDs as database primary keys is their size in the database (rather than in memory), where indexes and foreign keys compound the size increase. Here you have to make trade-offs. With a String representation, the database primary keys are 32 or 36 bytes. The number can also be stored directly as bytes, which cuts the size by half but is harder to understand when directly querying the database. Whether these approaches are feasible for your project depends on your requirements.

If using UUIDs as primary keys is not acceptable for your database, you may want to consider using a database sequence, but always assign IDs at the time new objects are created rather than letting Hibernate manage your IDs. In this case, your business objects that create new domain objects can call a service that uses a data access object (DAO) to retrieve the id from a database sequence. If you can use a Long datatype for your object ids, a single database sequence (and service method) will suffice for all your domain objects.

Conclusion

Object identity is deceptively hard to implement correctly when objects are persisted to a database. However, the problems stem entirely from allowing objects to exist without an id before they are saved. We can solve these problems by taking the responsibility of assigning object IDs away from object-relational mapping frameworks such as Hibernate. Instead, object IDs can be assigned as soon as the object is instantiated. This makes object identity simple and error-free, and reduces the amount of code needed in the domain model.

Resources

James Brundege currently works as an independent contractor and consultant through his company Synaptocode Software LLC.


Return to ONJava.com.