Question

I have a problem with lazy loading in hibernate when dealing with inheritance. I have one entity that references a second entity that is subclassed. I want the reference to load lazily, but this causes errors in my .equals() methods.

In the code below, if you call equals() on an instance of A, the check fails in the C.equals() function when checking if the Object o is an instance of C. It fails because the other object is actually a Hibernate proxy created by javassist, which extends B, not C.

I understand that Hibernate cannot create a proxy of type C without going to the database and thus breaking the lazy loading. Is there a way to make the getB() function in class A return the concrete B instance instead of the proxy (lazily)? I've tried using the Hibernate specific @LazyToOne(LazyToOneOption.NO_PROXY) annotation on the getB() method to no avail.

@Entity @Table(name="a")
public class A {
    private B b;

    @ManyToOne(fetch=FetchType.LAZY)
    @JoinColumn(name="b")
    public B getB() {
        return this.b;
    }

    public boolean equals(final Object o) {
        if (o == null) {
            return false;
        }

        if (!(o instanceof A)) {
            return false;
        }
        final A other = (A) o;
        return this.getB().equals(o.getB());
    }
}

@Entity @Table(name="b")
@Inheritance(strategy=InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(
    name="type",
    discriminatorType=DiscriminatorType.STRING
)
public abstract class B {
   private long id;

   public boolean equals(final Object obj) {
       if (this == obj) {
           return true;
       }
       if (obj == null) {
           return false;
       }
       if (!(obj instanceof B)) {
           return false;
       }
       final B b = (B) o;
       return this.getId().equals(b.getId());
    }
}

@Entity @DiscriminatorValue("c")
public class C extends B {
   private String s;

   public boolean equals(Object obj) {
       if (this == obj) {
           return true;
       }
       if (!super.equals(obj)) {
           return false;
       }
       if (obj == null) {
           return false;
       }
       if (!super.equals(obj)) {
           return false;
       }
       if (!(obj instanceof C)) {
           return false;
       }
       final C other = (C) o;
       if (this.getS() == null) {
           if (other.getS() != null) {
               return false;
           }
       } else if (!this.getS().equals(other.getS())) {
           return false;
       }
       return true;
    }
}

@Entity @DiscriminatorValue("d")
public class D extends B {
    // Other implementation of B
}
Was it helpful?

Solution

It turns out that I was on the right tracking trying to use the @LazyToOne(LazyToOneOption.NO_PROXY) annotation. It wasn't working for me out of the box because I hadn't yet run the Hibernate bytecode enhancer tool on it. Instructions for this can be found here:

19.1.7. Using lazy property fetching

OTHER TIPS

You might want to try changing the fetch property on getB() to FetchType.EAGER. Hope this helps.

Regardless of whether the objects are entities and/or lazy-loaded, it's practically impossible to respect the contract of equals and to have a specialization of equals in a subclass which uses instanceof. Indeed, you would then be in a situation where you would have b1.equals(c1) == true, but c1.equals(b1) == false.

So, I think the superclass (B) should define equals, and make it final, because all the subclasses should use the base class equals method.

That said, your equals method in B isn't right:

if (!super.equals(obj)) {
   return false;
}

This means that the Object implementation of equals must return true to have two B instances equal. Which means that two B instances are only equals if they are the same object.

if (!(obj instanceof C)) {
    return false;
}

Why does the B class check that the other instance is an instance of C. It should check if the other instance is an instance of B.

Since in the end, two Bs are equal if they have the same ID, and since the IDs must be unique for all the inheritance tree, you're safe if you make this equals method final.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top