skwisgaar
skwisgaar

Reputation: 977

Hibernate proxy as a key in HashMap

Today I spent some time debugging an issue with hibernate, simplified example would look like:

Map<Cat, Owner> catsMap = new HashMap();
List<Owner> owners = ownerRepo.getOwners();
for (Owner owner : owners) {
     // cat is Lazy, according to its nature :)
     catsMap.put(owner.getCat(), owner);
}

Cat cat = catRepo.findOne("meow");
Owner meowOwner = catsMap.get(cat);

at this moment meowOwner is null because it is not found in catsMap keyset. It took sometime to figure out why because in debug window I see that the Cat with name 'meow' exists in keyset of catsMap, moreover, if I write an expression

catsMap.keySet().iterator().next().equals(cat)

it returns true, hashcodes are the same, same values, though

catsMap.get(cat)

still returns null in the same expressions window. At last I called

catsMap.keySet().iterator().next().getClass()

and finally found out that it is long.path.to.package.Cat_$$_jvstaea_41, so it is a proxy and equals fails on the step when it checks class equality. The solution is, of course, obvious, but the question is why do I have

catsMap.keySet().iterator().next().equals(cat)

returning true? I tried also reversed case

cat.equals(catsMap.keySet().iterator().next())

and this one returns false, which is breaking the equals() convention of transitivity.

PS: in all the examples I assume that currently there is only one cat and one owner in DB

Upvotes: 2

Views: 527

Answers (1)

Guillaume F.
Guillaume F.

Reputation: 6473

Cat cat = catRepo.findOne("meow") should return the same instance, unless your Map is outside of the initial transaction. If you want to store an Entity outside of a transaction, make sure to unproxy it before storing it.

public T unproxy(T proxied)
{
    T entity = proxied;
    if (entity instanceof HibernateProxy) {
        Hibernate.initialize(entity);
        entity = (T) ((HibernateProxy) entity)
                  .getHibernateLazyInitializer()
                  .getImplementation();
    }
    return entity;
}

You have to make sure you use the right syntax for your equals and hashCode overrides. This is an example of implementation:

@Override
public boolean equals(Object obj) {
    if (obj == this)
        return true;
    if (!(obj instanceof MyEntityClass))
        return false;

    MyEntityClass other = (MyEntityClass) obj;
    return Objects.equals(getId(), other.getId());
}

@Override
public int hashCode() {
    return Objects.hash(getId());
}

Note: Do not use the fields directly this.id, prefer the getter to allow for Hibernate's proxy to resolve the entity when necessary. Also, prefer instanceof to getClass() != obj.getClass(), the first will handle implementations and extends correctly, not the second.

Upvotes: 1

Related Questions