The JPA hashCode() / equals() dilemma
There have been some discussions here about JPA entities and which hashCode()
/ equals()
implementation should be used for JPA entity classes. Most (if not all) of them depend on Hibernate, but I'd like to discuss them JPA-implementation-neutrally (I am using EclipseLink, by the way).
All possible implementations are having their own advantages and disadvantages regarding:
hashCode()
/ equals()
contract conformity (immutability) for List
/ Set
operations As far I can see, there are three options :
Object.equals()
and Object.hashCode()
hashCode()
/ equals()
work hashCode()
/ equals()
are broken hashCode()
/ equals()
are broken My questions are:
UPDATE 1:
By " hashCode()
/ equals()
are broken", I mean that successive hashCode()
invocations may return differing values, which is (when correctly implemented) not broken in the sense of the Object
API documentation, but which causes problems when trying to retrieve a changed entity from a Map
, Set
or other hash-based Collection
. Consequently, JPA implementations (at least EclipseLink) will not work correctly in some cases.
UPDATE 2:
Thank you for your answers -- most of them have remarkable quality.
Unfortunately, I am still unsure which approach will be the best for a real-life application, or how to determine the best approach for my application. So, I'll keep the question open and hope for some more discussions and/or opinions.
Read this very nice article on the subject: Don't Let Hibernate Steal Your Identity.
The conclusion of the article goes like this:
Object identity is deceptively hard to implement correctly when objects are persisted to a database. However, the problems stem entirely from allowing objects to exist without an id before they are saved. We can solve these problems by taking the responsibility of assigning object IDs away from object-relational mapping frameworks such as Hibernate. Instead, object IDs can be assigned as soon as the object is instantiated. This makes object identity simple and error-free, and reduces the amount of code needed in the domain model.
I always override equals/hashcode and implement it based on the business id. Seems the most reasonable solution for me. See the following link.
To sum all this stuff up, here is a listing of what will work or won't work with the different ways to handle equals/hashCode:
EDIT :
To explain why this works for me:
We usually have two IDs in our entities:
equals()
and hashCode()
in particular) Take a look:
@Entity
public class User {
@Id
private int id; // Persistence ID
private UUID uuid; // Business ID
// assuming all fields are subject to change
// If we forbid users change their email or screenName we can use these
// fields for business ID instead, but generally that's not the case
private String screenName;
private String email;
// I don't put UUID generation in constructor for performance reasons.
// I call setUuid() when I create a new entity
public User() {
}
// This method is only called when a brand new entity is added to
// persistence context - I add it as a safety net only but it might work
// for you. In some cases (say, when I add this entity to some set before
// calling em.persist()) setting a UUID might be too late. If I get a log
// output it means that I forgot to call setUuid() somewhere.
@PrePersist
public void ensureUuid() {
if (getUuid() == null) {
log.warn(format("User's UUID wasn't set on time. "
+ "uuid: %s, name: %s, email: %s",
getUuid(), getScreenName(), getEmail()));
setUuid(UUID.randomUUID());
}
}
// equals() and hashCode() rely on non-changing data only. Thus we
// guarantee that no matter how field values are changed we won't
// lose our entity in hash-based Sets.
@Override
public int hashCode() {
return getUuid().hashCode();
}
// Note that I don't use direct field access inside my entity classes and
// call getters instead. That's because Persistence provider (PP) might
// want to load entity data lazily. And I don't use
// this.getClass() == other.getClass()
// for the same reason. In order to support laziness PP might need to wrap
// my entity object in some kind of proxy, i.e. subclassing it.
@Override
public boolean equals(final Object obj) {
if (this == obj)
return true;
if (!(obj instanceof User))
return false;
return getUuid().equals(((User) obj).getUuid());
}
// Getters and setters follow
}
EDIT: to clarify my point regarding calls to setUuid()
method. Here's a typical scenario:
User user = new User();
// user.setUuid(UUID.randomUUID()); // I should have called it here
user.setName("Master Yoda");
user.setEmail("yoda@jedicouncil.org");
jediSet.add(user); // here's bug - we forgot to set UUID and
//we won't find Yoda in Jedi set
em.persist(user); // ensureUuid() was called and printed the log for me.
jediCouncilSet.add(user); // Ok, we got a UUID now
When I run my tests and see the log output I fix the problem:
User user = new User();
user.setUuid(UUID.randomUUID());
Alternatively, one can provide a separate constructor:
@Entity
public class User {
@Id
private int id; // Persistence ID
private UUID uuid; // Business ID
... // fields
// Constructor for Persistence provider to use
public User() {
}
// Constructor I use when creating new entities
public User(UUID uuid) {
setUuid(uuid);
}
... // rest of the entity.
}
So my example would look like this:
User user = new User(UUID.randomUUID());
...
jediSet.add(user); // no bug this time
em.persist(user); // and no log output
I use a default constructor and a setter, but you may find two-constructors approach more suitable for you.
链接地址: http://www.djcxy.com/p/36980.html