equals() and hashcode()

Problem Definition

The issue with the equals() and hashcode() I'm going to discuss here is about the java contract for implementing equals (and hashcode) and how it relates to Entity Beans.

The issue being that the equals implementation on java.lang.Object matches 'is the same instance'. There maybe occasions when developers want to implement equals() on Entity Beans to reflect that a Entity Bean is 'logically' the same rather than simply the same instance.

It should also be noted that equals and hashcode are used in the collections api both implicitly (hashing for HashMap HashSet etc) and more explicitly (contains method).

For me the best overall discussion on this issue is the Hibernate forum Topic: equals() / hashCode(): Is there *any* non-broken approach? I would recommend reading this entire discussion to get a feel for the various options and arguments that go along with them.

 

4 main options

There are 4 main options for handling equals() and hashcode() with Entity Beans in Ebean and each have their own pros and cons.

 

Work around the problem

Firstly it should be noted that you can work around the issue of equals() and hashcode() by using other code to perform similar functions.

Instead of using equals() you could get explictly use the values of the "Id" property and perform equals() on the those. It is expected that Id properties will all implement equals and this is explicitly required for EmbeddedId objects.

Instead of using a contains() method on a list or set you could use a Map keySet contains(). So in this case you would need to have a Map when the keys (or values) where the Id properties and you perform contains on the keySet checking to see if an Id already exists in a Map.

The approaches above do not cover all the uses of equals and hashcode but the general idea is that sometimes working around the issue by explicitly using the Id property maybe a viable option.

Use UUID Generation on object instantiation

Using a UUID and generating it on object instantiation is a mechanisim that will generally work. Rather than provide an explanation I'll link to a full article.

Don't Let Hibernate Steal Your Identity by James Brundege

The downside for many is that the use of UUIDs as identity values is generally more cumbersome than simple integers and strings (which is generally the case for Database Sequences/AutoIncrement/Identity.

The Gerry Power technique on a base class

This technique was documented in a hibernate forum by Gerry Power. Its on page 8 of a 9 page discussion on the issue of equals and hashcode.

To me this technique is going to be just about as good as it gets for Developers who do not want to use UUID and I have taken the liberty of reproducing the code Gerry posted verbatem.


public class BaseModel implements Serializable {
   
    private volatile Object object;
    private Long id;
   
    public Long getId() {
        return id;
    }

    private void setId(Long id) {
        this.id = id;
    }
               
    public boolean equals(Object obj) {
        final boolean returner;
        if (obj instanceof BaseModel) {
            return getObject().equals(((BaseModel)obj).getObject());
        } else {
            returner = false;
        }
        return returner;
    }
   
    public int hashCode() {
        return getObject().hashCode();
    }
   
    private Object getObject() {
        if (object != null || object == null && id == null) {
            if (object == null) { //Avoid the performance impact of synchronized if we can
                synchronized(this) {
                    if (object == null)
                        object = new Object();
                }
            }
            return object;
        }
        return id;
    }
} 

The downside I see for this technique is that perhaps not all your Ids will be of the same type (Long in the code above) and for some they may not like specifying a base class for their Entity Beans for whatever reason.

Gerry Power technique by byte code generation

For me this technique was pretty darn good and I felt that some people may want Ebean to automatically implement this technique on Entity Beans via code generation.

The upside is that without doing any work you can use equals() to compare Reference/Proxy objects with Entity Beans that are fetched back from queries.

The downside is that you can not use the byte code generated form to compare a 'Vanilla' entity bean with a 'byte code generated' entity bean. That is, Ebean can implement override the generated code but of course your vanilla beans are always left as is.


        Topic topicRef = (Topic)Ebean.getReference(Topic.class, 21);

        Topic topic0 = (Topic)Ebean.find(Topic.class, 21);
        Topic topic1 = (Topic)Ebean.find(Topic.class, 21);

        // these are all true
        topicRef.equals(topic0);
        topicRef.equals(topic1);
        topic0.equals(topic1);


        Topic vanillaTopic = new Topic();
        vanillaTopic.setId(21);

        // this is a vanilla bean - no byte code generation 
        // and this will be false (which may confuse people?)
        vanillaTopic.equals(topic0);


In Ebean the property ebean.equalsmode controls whether the equals and hashcode methods are generated. The valid values are "always","never","ifabsent" and the default is "always".

Additionally there is the EqualsMode Annotation that can be used to control the behaviour for a specific bean if required.

Introduction User Guide (pdf) Install/Configure Public JavaDoc Whitepapers
General Database Specific Byte Code Deployment Annotations Features
Top Bugs Top Enhancements
woResponse