I was on #hibernate
IRC and somebody shared the following (partial) pattern with me
@Entity
public MyEntity() {
... primary key, object properties, getters/setters go here ...
@Column(nullable=false)
private int hashCode;
public MyEntity() {
hashCode += id;
}
private final Set<String> tags = Sets.newHashSet();
public void addTag(String tag) {
if(tags.add(tag)) {
hashCode += tag.hashCode();
}
}
public void removeTag(String tag) {
if(tags.remove(tag) {
hashCode -= tag.hashCode();
}
}
public void hashCode() {
return hashCode;
}
... http://www.artima.com/lejava/articles/equality.html style equals ...
}
One could call this a "cached hashCode which is updated piecewise". (This is definitely NOT a "business key" pattern as some commenters seem to believe. The "business key" pattern requires a column with a uniqueness constraint on it, like a username, which is used for equality tests).
When used in JPA/Hibernate, it means that the @Entity
can has similar advantages to "eq/hC with buisness [sic] key" from the JBoss Equals and HashCode article, yet behave the way a developer would expect any normal Javabean object to behave (i.e. without having to think about the object like a database row): before being persisted to the DB; after a Transaction
in EAGER
fetching modes; and at any time with LAZY
fetch inside a Transaction
or in EXTENDED
mode.
However, ensuring that the hashCode
is always updated correctly could be a real challenge.
Does anybody here have any experience with this pattern and could you share your discoveries (both positive and negative) about it? I'm extremely interested in Gotchas but I am not at all interested in comments which make claims that something is "bad" without solid arguments about why it is bad.
Note that I am aware of The JPA hashCode() / equals() dilemma, but I don't believe this pattern was actually covered in that discussion.
This pattern was originally suggested as a way to avoid a problem when loading nested Collection
s in @Entity
s, such as encountered in Hibernate LazyInitializationException on find() with EAGER @ElementCollection.
UPDATE: some commenters are getting very passionate about the existing approaches. For the avoidance of doubt, I am just interested in the merits of this new pattern. For your reference, and to also ask you to stop saying how you believe equals/hashCode should be implemented, note that I have used the following pattern for my @Entity
s for several years now:
@Id
private UUID id = UUID.randomUUID();
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (!(obj instanceof MY_CLASS) || id == null)
return false;
MY_CLASS other = (MY_CLASS) obj;
return id.equals(other.id);
}
@Override
public int hashCode() {
Preconditions.checkNotNull(id, "id must be set before @Entity.hashCode can be called");
return id.hashCode();
}
and I only recently tried something new to see if I really needed to have a separate method like this
public boolean hasSameProperties(Note other) {
Preconditions.checkNotNull(other);
if (this == other)
return true;
return Objects.equal(source, other.source)
&& Objects.equal(title, other.title)
&& Objects.equal(tags, other.tags)
&& Objects.equal(contents, other.contents);
}