Given below a one-to-many relationship from country to state (the table name is taken state_table in the database, since state may be a reserved word in some RDBMS).

The inverse side of the association is given below (I assume the owning side is not required to present).

public class Country implements Serializable {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Basic(optional = false)
    @Column(name = "country_id")
    private Long countryId;

    private static final long serialVersionUID = 1L;

    // Other fields + getters + setters + constructors.

    @Override
    public int hashCode() {
        int hash = 7;
        hash = 47 * hash + Objects.hashCode(this.countryId);
        return hash;
    }

    @Override
    public boolean equals(Object obj) {

        System.out.println("equals() in entity = " + (obj == null));
        System.out.println("this.countryId = " + this.countryId + " : other.countryId = " + ((Country) obj).countryId);

        if (obj == null) {
            return false;
        }

        if (getClass() != obj.getClass()) {
            return false;
        }

        final Country other = (Country) obj;        

        if (!Objects.equals(this.countryId, other.countryId)) {
            return false;
        }

        return true;
    }
}

Objects in the equals() and hashcode() methods is a utility class introduced in Java 7 holding only static utility methods.

The following segment of code uses the equals() method in the above entity class as an example.

StateTable stateTable = entityManager.find(StateTable.class, 1L);
Country country = entityManager.find(Country.class, 1L);

stateTable.getCountryId().hashCode();

System.out.println("countryId = " + stateTable.getCountry().getCountryId());
System.out.println("equals = " + country.equals(stateTable.getCountry()));

The first stdout statement shows the correct value 1 corresponding to the entity's primary key (countryId of type Long).

The second stdout statement, however, returns false incredibly even though countryId (primary key) in both the objects is supposed to be the same upon fetching - it is broken.

Those two stdout statements in the equals() method in the entity class outputs the following.

equals() in entity = false
this.countryId = 1 : other.countryId = null

Although the supplied country object is not null as confirmed by the first stdout statement in equals(), ((Country) obj).countryId returns null surprisingly and consequently, the equals() method always returns false irrespective of what object is supplied as a parameter, since Object equals null is never true. This should not happen anyway.


The @ManyToOne association is lazily initialized (fetch = FetchType.LAZY). If it is turn to fetch = FetchType.EGAR, then everything goes just fine. The equals() method returns a correct outcome based on the supplied object.

The code is executed under a transaction in a Spring service marked by the following annotations.

@Service
@Transactional(readOnly = true, propagation = Propagation.REQUIRED)

Thus, there should not be an issue of initializing lazy associations. It is explicitly stated in the JPA WikiBook.

For collection relationships sending size() is normally the best way to ensure a lazy relationship is instantiated. For OneToOne and ManyToOne relationships, normally just accessing the relationship is enough (i.e. employee.getAddress()), although for some JPA providers that use proxies you may need to send the object a message (i.e. employee.getAddress().hashCode()).

The last statement implies a provider specific behaviour,

Although for some JPA providers that use proxies you may need to send the object a message (i.e. employee.getAddress().hashCode()).

I however, finished trying in both the ways but to no avail.

  1. Assigned value of stateTable.getCountry() to another variable before passing it to equals()). Just as,

    Country anotherCountry = stateTable.getCountry();
    country.equals(anotherCountry); // Always returns false same as mentioned above.
    
  2. Used hashcode() associated with this association using stateTable.getCountry().hashCode() as specified earlier to make sure that the lazy association is initialized within a transaction context before invoking equals() and passing through the country object.

While I tried using Hibernate 4.3.6 final (Tomcat 8.0.9.0), this behaviour is not identical on EclipseLink 2.6.0 (GlassFish 4.1) which behaves as one is likely to expect intuitively.

What goes wrong with Hibernate? What is the solution?

2

There are 2 answers

2
Stefan Steinegger On BEST ANSWER

You have to understand how proxies work. A proxy derives from the actual class, but doesn't hold its data. When the proxy is loaded from the database, another instance of the class (Country in this case) is created, which is a normal instance. It holds the data. The proxy forwards all calls to this instance.

Your code has two potential problems.

  • Fields shouldn't be accessed by another object, because it could be a proxy. The proxies fields are not initialized. Call the getters and you get the values.
  • .class returns the proxy type and not the actual type. It is not necessarily Country. Use if ( !(other instanceof Cat) ) return false as proposed in the docs.

Note that because the proxy forwards all calls to the real object, in your entity is this never a reference to a proxy. But every argument you get may be a proxy.

0
Jens Schauder On

Don't access the fields directly. Instead use the getters inside the equals method.