Are there any gotchas with this JPA “cached hashCode” pattern?

五迷三道 提交于 2019-12-14 03:52:33

问题


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 Collections in @Entitys, 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 @Entitys 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);
}

回答1:


This business key isn't unique, so it seems like a poor choice for equals().

Business key 1002 has tag 500 and tag -502 added to it, business key 995 has tag 3 and tag 2 added to it? Are those objects really meant to be equal?

The relatively low risk of collisions for 32 bit numbers may be tolerable for whatever purpose your particular entity serves, but to refer to something as a 'Pattern' one would like for it to actually be correct, not just arbitrarily unlikely to fail for a given data size.




回答2:


I've written, rewritten, and deleted two or three long answers explaining why i think this is a bad idea, but ultimately, all any of them were basic explanations of what object identity is, and i'm sure you understand that.

My response to this pattern is that it just doesn't solve any problem which needs solving.

The solution to the alleged "hashCode/equals problem" is incredibly simple. Give your entities an identifier when you create them. Persist it. Base equals and hashCode on it. Do not rely on an identifier created by the database or persistence layer at insertion. This is by now a positively ancient principle.

As an aside, for entities, it is never right to base either equals or hashCode on mutable fields. That is not how equality works for entities. Entities are not just a bag of state, they are an identity to which some state happens to be attached. Equality and hashcode must be based on the identity, not the state.



来源:https://stackoverflow.com/questions/11730379/are-there-any-gotchas-with-this-jpa-cached-hashcode-pattern

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!