HashCode throws a nullpointer exception - java

I have a puzzle for you.
I am making a herb store web app and this is my database :
The store can have many products
A product can contain many herbs
These are my JPA classes :
public class StoreJPA {
...
#OneToMany(mappedBy="storeJpa", cascade = CascadeType.ALL, orphanRemoval=true, fetch=FetchType.EAGER)
private Set<ProductJPA> specialOffers = new HashSet<ProductJPA>();
...
}
public class ProductJPA {
#ManyToOne
#JoinColumn(name="store_id")
private StoreJPA storeJpa;
#OneToMany(mappedBy="productJpa", cascade = CascadeType.ALL, orphanRemoval=true, fetch=FetchType.EAGER)
private Set<ContainsJPA> contains = new HashSet<ContainsJPA>();
...
private Set<HerbJPA> getHerbs(){
return contains.stream().map(h -> h.getHerbJpa()).collect(Collectors.toSet());
}
#Override
public int hashCode(){
long h = 1125899906842597L; // prime
for(ProductHasHerbJPA phh : contains){
h = 31*h + phh.getHerbJpa().getId();
}
return (int)(31*h + storeJpa.getId());
}
#Override
public boolean equals(Object o){
if(o!=null && o instanceof ProductJPA){
if(o==this)
return true;
return ((ProductJPA)o).getStoreJpa().getId()==storeJpa.getId() &&
((ProductJPA)o).getHerbs().equals(getHerbs()) // compare herbs they contain
}
return false;
}
...
}
public class ContainsJPA {
#Id
private Long id;
#ManyToOne
#JoinColumn(name="product_id")
private ProductJPA productJpa;
#ManyToOne
#JoinColumn(name="herb_id")
private HerbJPA herbJpa;
...
#Override
public int hashCode(){
long h = 1125899906842597L + productJpa.getId(); // <-- nullpointer exception
return (int)(31*h + herbJpa.getId());
}
#Override
public boolean equals(Object o){
if( o != null && o instanceof HerbLocaleJPA) {
if(o==this) {
return true;
}
return ((ProductHasHerbJPA)o).getHerbJpa().getId()==herbJpa.getId() &&
((ProductHasHerbJPA)o).getProductJpa().getId()==productJpa.getId();
}
return false;
}
...
}
Adding a new product with a list of herbs works fine.
But when i run this and try to get the products in the store, i get a NullPointerException :
java.lang.NullPointerException at
com.green.store.entities.ContainsJPA.hashCode(ContainsJPA.java:64)
at java.util.HashMap.hash(HashMap.java:339) at
java.util.HashMap.put(HashMap.java:612) at
java.util.HashSet.add(HashSet.java:220) at
java.util.AbstractCollection.addAll(AbstractCollection.java:344) at
org.hibernate.collection.internal.PersistentSet.endRead(PersistentSet.java:327)
at
org.hibernate.engine.loading.internal.CollectionLoadContext.endLoadingCollection(CollectionLoadContext.java:234)
at
org.hibernate.engine.loading.internal.CollectionLoadContext.endLoadingCollections(CollectionLoadContext.java:221)
at
org.hibernate.engine.loading.internal.CollectionLoadContext.endLoadingCollections(CollectionLoadContext.java:194)
at
org.hibernate.loader.plan.exec.process.internal.CollectionReferenceInitializerImpl.endLoading(CollectionReferenceInitializerImpl.java:154)
at
org.hibernate.loader.plan.exec.process.internal.AbstractRowReader.finishLoadingCollections(AbstractRowReader.java:249)
at
org.hibernate.loader.plan.exec.process.internal.AbstractRowReader.finishUp(AbstractRowReader.java:212)
at
org.hibernate.loader.plan.exec.process.internal.ResultSetProcessorImpl.extractResults(ResultSetProcessorImpl.java:133)
at
org.hibernate.loader.plan.exec.internal.AbstractLoadPlanBasedLoader.executeLoad(AbstractLoadPlanBasedLoader.java:122)
at
org.hibernate.loader.plan.exec.internal.AbstractLoadPlanBasedLoader.executeLoad(AbstractLoadPlanBasedLoader.java:86)
at
org.hibernate.loader.entity.plan.AbstractLoadPlanBasedEntityLoader.load(AbstractLoadPlanBasedEntityLoader.java:167)
at
org.hibernate.persister.entity.AbstractEntityPersister.load(AbstractEntityPersister.java:4087)
at
org.hibernate.event.internal.DefaultLoadEventListener.loadFromDatasource(DefaultLoadEventListener.java:508)
at
org.hibernate.event.internal.DefaultLoadEventListener.doLoad(DefaultLoadEventListener.java:478)
at
org.hibernate.event.internal.DefaultLoadEventListener.load(DefaultLoadEventListener.java:219)
at
org.hibernate.event.internal.DefaultLoadEventListener.proxyOrLoad(DefaultLoadEventListener.java:278)
at
org.hibernate.event.internal.DefaultLoadEventListener.doOnLoad(DefaultLoadEventListener.java:121)
at
org.hibernate.event.internal.DefaultLoadEventListener.onLoad(DefaultLoadEventListener.java:89)
at org.hibernate.internal.SessionImpl.fireLoad(SessionImpl.java:1239)
at
org.hibernate.internal.SessionImpl.internalLoad(SessionImpl.java:1122)
at
org.hibernate.type.EntityType.resolveIdentifier(EntityType.java:672)
at org.hibernate.type.EntityType.resolve(EntityType.java:457) at
org.hibernate.engine.internal.TwoPhaseLoad.doInitializeEntity(TwoPhaseLoad.java:165)
at
org.hibernate.engine.internal.TwoPhaseLoad.initializeEntity(TwoPhaseLoad.java:125)
at
org.hibernate.loader.plan.exec.process.internal.AbstractRowReader.performTwoPhaseLoad(AbstractRowReader.java:238)
at
org.hibernate.loader.plan.exec.process.internal.AbstractRowReader.finishUp(AbstractRowReader.java:209)
at
org.hibernate.loader.plan.exec.process.internal.ResultSetProcessorImpl.extractResults(ResultSetProcessorImpl.java:133)
at
org.hibernate.loader.plan.exec.internal.AbstractLoadPlanBasedLoader.executeLoad(AbstractLoadPlanBasedLoader.java:122)
at
org.hibernate.loader.plan.exec.internal.AbstractLoadPlanBasedLoader.executeLoad(AbstractLoadPlanBasedLoader.java:86)
at
org.hibernate.loader.entity.plan.AbstractLoadPlanBasedEntityLoader.load(AbstractLoadPlanBasedEntityLoader.java:167)
at
org.hibernate.persister.entity.AbstractEntityPersister.load(AbstractEntityPersister.java:4087)
at
org.hibernate.event.internal.DefaultLoadEventListener.loadFromDatasource(DefaultLoadEventListener.java:508)
at
org.hibernate.event.internal.DefaultLoadEventListener.doLoad(DefaultLoadEventListener.java:478)
at
org.hibernate.event.internal.DefaultLoadEventListener.load(DefaultLoadEventListener.java:219)
at
org.hibernate.event.internal.DefaultLoadEventListener.doOnLoad(DefaultLoadEventListener.java:116)
at
org.hibernate.event.internal.DefaultLoadEventListener.onLoad(DefaultLoadEventListener.java:89)
at org.hibernate.internal.SessionImpl.fireLoad(SessionImpl.java:1239)
at
org.hibernate.internal.SessionImpl.immediateLoad(SessionImpl.java:1097)
...
The hashCode function of ContainsJPA throws this exception when getting the id for the product. Why is it doing that, considering that the 'contains' table in the DB has this id ?
I can't figure out why this is happening. Please help.

Your hashCode and equals implementations are incorrect.
The problems with it, in a nutshell:
They do not adhere to the 'delegation' style (they do not delegate the job of determining equality to the relevant classes)
They do not answer the central question of what the object represents: Row in a DB, or the notion that the row in the DB is trying to represent.
Delegate equality checks
Both hashCode and equals are specced to require that you do not throw NPEs out of them. For equals, that means you can't just call, say, a.equals(b) - you'd have to make that a == null ? b == null : a.equals(b) (and because this 'never throw' is transitive, a.equals(b) is fine, even if b is null), or use the helper Objects.equal(a, b) instead.
For hashcode, it means that null values must be defined as having some predefined value for the sake of hashing. Also, more generally, whenever you have a 'sub object' (e.g. a field of some non-primitive type', the general idea is for hashCode and equals to cascade: Use productJPA.hashCode() and not productJPA.getId().
Same goes for equals. Don't do this:
(ProductHasHerbJPA)o).getHerbJpa().getId()==herbJpa.getId()
but do this:
Objects.equals(o.getHerbJpa(), herbJpa);
And if 2 herb JPAs are to be considered equal if their IDs are equal, then the HerbJPA class's equals() method should be defined accordingly, and if not, then not. It is not the job of your ContainsJPA class to know how to calculate if 2 herbJPA instances are equal - herbJPA can do that, itself. In passing you avoid a ton of null issues by doing it this way.
Note, you can let lombok take care of all this boilerplate for you.
Next, we get to some hairy issues with JPA and equality in particular.
The common strategy to do equals/hashCode in the java ecosystem (outside of JPA/Hibernate) is to look at all fields that are part of an object's identity, which is usually all of them. The problem is, that doesn't work well with JPA: Most of the getter methods on a JPA object are proxies which cause DB queries if you invoke them. With a sufficiently interconnected db structure (lots of references), that means a single equals call ends up querying half your DB, takes a ton of memory, and half an hour to complete, obviously not a feasible solution.
The key question is: What does your object actually represent, and as far as I know, JPA does not give clear guidance.
An instance of HerbsJPA represents a row in a database
Then we can draw the following conclusions:
As always, by spec, an object is always equal to itself: if (this == other) return true;. Otherwise...
If either or both of the objects have no set unid, then they cannot be equal to each other - 2 unwritten rows, even if entirely identical for every field in the object, still does not represent 'the same row', therefore, not equal!
If both objects have a set unid, then they are equal if the unids are, and otherwise, they are not. Regardless of all the other values! - 2 different rows with identical values are... still two different rows.
This view incidentally is also convenient in that you entirely avoid that 'whoops it queries the entire DB' issue. unids are not expensive to fetch, and are usually prefetched already.
An instance of HerbsJPA represents a 'herb'.
If this is the case, may I suggest your class is misnamed? It should be 'Herb', probably. Maybe 'HerbJpa' (NB: JPA in all-caps is a violation of the most common style rule).
Then the most sensical solution is to AVOID checking the unid entirely, and look only at all the other fields (or at least, all the other ones that represent something about the herb's identity. This is usually most of them, but sometimes you can get away with defining some property that would cause a storm of DB queries, such as 'a list of associated herbs', represented in the DB with a join table, as 'not part of the identity'. After all, 'the unid in the db' is an incidental implementation detail of the notion of a 'herb' and therefore couldn't possibly be part of the identity of it!
The downside of this view is of course that 'storm of DB calls' issue.
Generally I advise you treat these objects as representing 'row in a table' and not 'the actual herb', in which case, your equals and hashCode methods become relatively simple, and the name of the class is fine (well, it should be 'Jpa', not 'JPA', but other than that).
#Override public int hashCode() {
return id == null ? super.hashCode() : (int) id;
// note, other answer's id %1000 is silly;
// it is needlessly inefficient, don't do it that way.
}
#Override public boolean equals(Object other) {
if (other == this) return true;
if (other == null || other.getClass() != ContainsJPA.class) return false;
return id == null ? false : id.equals(other.id);
}

Not 100% sure, but doesn't AbstractRowReader first load the collection and then "hydrate" the associated entities?
AbstractRowReader#finishUp()
...
// now we can finalize loading collections
finishLoadingCollections( context );
// finally, perform post-load operations
postLoad( postLoadEvent, context, hydratedEntityRegistrations, afterLoadActionList
);
Which means when creating the collection, the product_id is known, but the ProductJPA instance hasn't been hydrated yet.
tbh, I think it's not great practice to derive a hashcode from associated entities. I'd probably do something like
public class ContainsJPA {
#Id
private Long id;
#Override
public int hashCode(){
return id == null ? super.hashCode() : id % 1000;
}
to get some distribution (the '1000' is a magic number, depending on what typical collection sizes are).

Related

Object differ hasChanges where no changes should be detected

I'm using java-object-diff to get differences between two objects parsed from xml by JAXB. In below example, I'm using the same string to test if I get no differences, however log.info("has changes: " + diff5.hasChanges()); logs true.
JAXBContext context1 = JAXBContext.newInstance(Item.class);
Unmarshaller m1 = context1.createUnmarshaller();
Item base = (Item) m1.unmarshal(new StringReader(s));
Item working = (Item) m1.unmarshal(new StringReader(s));
DiffNode diff5 = ObjectDifferBuilder
.buildDefault()
.compare(working, base);
log.info("has changes: " + diff5.hasChanges());
diff5.visit((node, visit) -> {
final Object baseValue = node.canonicalGet(base);
final Object workingValue = node.canonicalGet(working);
final String message = node.getPath() + " changed from " +
baseValue + " to " + workingValue;
System.out.println(message);
});
The message I get from System.out.println is always the same, saying it has changed from null to <the actual value> This happens for every property. E.g.
content changed from null to Mit dem Wasserinonisator
I have verified that the both Items have the same content and none of the both actualy is not null, but the exact same content.
Item is a pojo with many subclasses (all getters and setters are present), e.g.
public class Item {
#XmlElement(name = "ASIN", required = true)
protected String asin;
#XmlElement(name = "ParentASIN")
protected String parentASIN;
#XmlElement(name = "Errors")
protected Errors errors;
#XmlElement(name = "DetailPageURL")
protected String detailPageURL;
#XmlElement(name = "ItemLinks")
protected ItemLinks itemLinks;
#XmlElement(name = "SalesRank")
protected String salesRank;
#XmlElement(name = "SmallImage")
protected Image smallImage;
}
Is there any way to make java-object-diff work, to make it compare the values correctly?
After taking a closer look at your code I know what's wrong. The first problem is the fact, that JAXB doesn't generate equals methods. For the most part, that's not a problem, because the ObjectDiffer can establish the relationship between objects based on the hierarchy. Things get more complicated when ordered or unordered Collections are involved, because the ObjectDiffer needs some kind of way to establish the relationship between the collection items in the base and working instance. By default it relies on the lookup mechanism of the underlying collection (which typically involves on or more of the methods hashCode, equals or compareTo.)
In your case this relationship cannot be established, because none of your classes (but especially those contained in Lists and Sets) implement a proper equals method. This means that instances are only ever equal to themselves. This is further complicated by the fact, that the responsible classes represent value objects and don't have any hard identifier, that could be used to easily establish the relationship. Therefore the only option is to provide custom equals methods that simply compare all properties. The consequence is, that the slightest change on those objects will cause the ObjectDiffer to mark the base version as REMOVED and the working version as ADDED. But it will also not mark them as CHANGED, when they haven't actually changed. So that's something.
I'm not sure how easy it is to make JAXB generate custom equals methods, so here are some alternative solutions possible with java-object-diff:
Implement your own de.danielbechler.diff.identity.IdentityStrategy for the problematic types and provide them to the ObjectDifferBuilder, like so (example uses Java 8 Lambdas):
ObjectDifferBuilder
.startBuilding()
.identity()
.ofCollectionItems(ItemLinks.class, "itemLink").via((working, base) -> {
ItemLink workingItemLink = (ItemLink) working;
ItemLink baseItemLink = (ItemLink) base;
return StringUtils.equals(workingItemLink.getDescription(), baseItemLink.getDescription())
&& StringUtils.equals(workingItemLink.getURL(), baseItemLink.getURL());
})
// ...
.and().build();
Ignore problematic properties during comparison. Obviously this may not be what you want, but it's an easy solution in case you don't really care about the specific object.
ObjectDifferBuilder
.startBuilding()
.inclusion()
.exclude().type(Item.ImageSets.class)
.and().build();
A solution that causes JAXB to generate custom equals methods would be my preferred way to go. I found another post that claims it's possible, so maybe you want to give this a try first, so you don't have to customize your ObjectDiffer.
I hope this helps!

Multiple HashCodes for Java Objects

I'm trying to optimize some code, and when I do this I usually end up getting that helping hand from Hash structures.
What I want to do is divide objects into multiples sets based on some attributes in a very fast way. Basically like SQL GROUP BY statement but for Java.
The thing is that I want to use HashMap<Object, ArrayList<Object>> to do this. I want to use multiple grouping ways but an Object can only have one hashCode().
Is there a way to have multiple hashCodes() in order to be able to group by multiple methods? Are there other structures made to solve this kind of issues? Can I use Java 8 lambda expressions to send a hashCode() in the HashMap parameters? Am I silly and there is a super fast way that isn't this complicated?
Note: The hashCodes I want use multiple attributes that are not constant. So for example, creating a String that represents those attributes uniquely won't work because I'd have to refresh the string every time.
Let's say you have a collection of objects and you want to produce different groupings analogous to SQL GROUP BY. Each group-by is defined by a set of common values. Create a group-by-key class for each distinct grouping type, each with an appropriate hashCode() and equals() method (as required by the Map contract).
For the following pseudocode I assume the existence of a MultiMap class that encapsulates the management of your map's List<Object> values. You could use Guava's MultiMap implementation.
// One group key
public class GroupKey1 {
...
public GroupKey1(MyObject o) {
// populate key from object
}
public GroupKey1(...) {
// populate from individual values so we can create lookup keys
}
public int hashCode() { ... }
public boolean equals() { ... }
}
// A second, different group key
public class GroupKey2 {
...
public GroupKey2(MyObject o) {
// populate key from object
}
public GroupKey2(...) {
// populate from individual values so we can create lookup keys
}
...
}
...
MultiMap<GroupKey1,MyObject> group1 = new HashMultiMap<>();
MultiMap<GroupKey2,MyObject> group2 = new HashMultiMap<>();
for (MyObject m : objectCollection)
{
group1.put(new GroupKey1(m), m);
group2.put(new GroupKey2(m), m);
}
...
// Retrieve the list of objects having a certain group-by key
GroupKey2 lookupKey = new Groupkey2(...);
Collection<MyObject> group = group2.get(lookupKey);
What you're describing sounds like a rather convoluted pattern, and possibly a premature optimization. You might have better luck asking a question about how to efficiently replicate GROUP BY-style queries in Java.
That said the easiest way to have multiple hash codes is to have multiple classes. Here's a trivial example:
public class Person {
String firstName;
String lastName;
/** the "real" hashCode() */
public int hashCode() {
return firstName.hashCode() + 1234 * lastName.hashCode();
}
}
public class PersonWrapper1 {
Person person;
public int hashCode() {
return person.firstName.hashCode();
}
}
public class PersonWrapper2 {
Person person;
public int hashCode() {
return person.lastName.hashCode();
}
}
By using wrapper classes you can redefine the notion of equality in a type-safe way. Just be careful about how exactly you let these types interact; you can only compare instances of Person, PersonWrapper1, or PersonWrapper2 with other instances of the same type; each class' .equals() method should return false if a different type is passed in.
You might also look at the hashing utilities in Guava, they provide several different hashing functions, along with a BloomFilter implementation, which is a data structure that relies on being able to use multiple hashing functions.
This is done by abstracting the hashing function into a Funnel class. Funnel-able classes simply pipe the values they use for equality into the Funnel, and callers (like BloomFilter) then actually compute the hash codes.
Your last paragraph is confusing; you cannot hope to store objects in a hash-based data structure and then change the values used to compute the hash code. If you do so, the object will no longer be discoverable in the data structure.
Taking your thoughts into account:
What I want to do is divide objects into multiples sets based on some attributes in a very fast way. Basically like SQL GROUP BY statement but for Java.
Map<City, Set<String>> lastNamesByCity
= people.stream().collect(groupingBy(Person::getCity,
mapping(Person::getLastName, toSet())));

Why Hibernate requires us to implement equals/hashcode methods when I have a private id field?

First, consider the snippet,
public class Employee
{
private Integer id;
private String firstname;
private String lastName;
private String department;
// public getters and setters here, i said PUBLIC
}
I create 2 objects with same ids and rest of all the fields are also same.
Employee e1 = new Employee();
Employee e2 = new Employee();
e1.setId(100);
e2.setId(100);
//Prints false in console
System.out.println(e1.equals(e2));
The whole problem starts here
In a real time application, this must return true.
Consequently, everyone knows a solution exists (to implement equals() and hashcode())
public boolean equals(Object o) {
if(o == null)
{
return false;
}
if (o == this)
{
return true;
}
if (getClass() != o.getClass())
{
return false;
}
Employee e = (Employee) o;
return (this.getId() == e.getId());
}
#Override
public int hashCode()
{
final int PRIME = 31;
int result = 1;
result = PRIME * result + getId();
return result;
}
Now, as usual:
Employee e1 = new Employee();
Employee e2 = new Employee();
e1.setId(100);
e2.setId(100);
//Prints 'true' now
System.out.println(e1.equals(e2));
Set<Employee> employees = new HashSet<Employee>();
employees.add(e1);
employees.add(e2);
//Prints ofcourse one objects(which was a requirement)
System.out.println(employees);
I am going through this excellent article Don't Let Hibernate Steal Your Identity. But one thing I have failed to understand completely. The whole problem and its solution discussed above and the linked article were dealing the problems when the 2 Employee object ids were same.
Consider when we have a private setter for id field with the id field generated by the generator class provided in hbm.xml. As soon as i start to persist the Employee objects(and in no way i would be able to change the id), i find no need to implement equals and hashcode methods. I am sure i am missing something, since my intuition says when a particular concept is too much rotated over the web, it must have always been laid in front of you for the sake of avoiding some common errors ? Do i still have to implement those 2 methods when i have a private setter for id field?
If the entity defines a natural business key, then you should use that for equals and hashCode. The natural identifier or business key is consistent across all entity state transitions, hence the hashCode will not change when the JPA entity state changes (e.g. from New to Managed to Detached).
In your example, you are using the assigned identifier, which doesn't change when you persist your entity.
However, if you don't have a natural identifier and you have a generated PRIMARY KEY (e.g., IDENTITY, SEQUENCE), then you can implement equals and hashCode like this:
#Entity
public class Book implements Identifiable<Long> {
#Id
#GeneratedValue
private Long id;
private String title;
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Book))
return false;
Book other = (Book) o;
return id != null &&
id.equals(other.getId());
}
#Override
public int hashCode() {
return getClass().hashCode();
}
//Getters and setters omitted for brevity
}
The entity identifier can be used for equals and hashCode, but only if the hashCode returns the same value all the time. This might sound like a terrible thing to do since it defeats the purpose of using multiple buckets in a HashSet or HashMap.
However, for performance reasons, you should always limit the number of entities that are stored in a collection. You should never fetch thousands of entities in a #OneToMany Set because the performance penalty on the database side is multiple orders of magnitude higher than using a single hashed bucket.
The reason why this version of equals and hashCode works is that the hashCode value does not change from one entity state to another, and the identifier is checked only when it's not null.

JPA : how to manage id (or business-id) ? Still the same pb of equals/hashcode

i'm a beginner with Hibernate, Spring, JPA frameworks. For moment, i'm trying to make a simple architecture with Spring 3.1.1 - JPA with Hibernate 4 Implementation.
For the moment, in order to not be dependant of my database, i created some ids with TableGenerator :
#Id
#Column(name = "AIR_ID", unique = true, nullable = false)
#TableGenerator(name="aircraftSeqStore",
table="T_S_APP_SEQ_STORE_AST",
pkColumnName="AST_SEQ_NAME",
valueColumnName = "AST_SEQ_VALUE",
pkColumnValue = "T_R_AIRCRAFT_AIR.AIR_ID",
allocationSize=1)
#GeneratedValue(strategy=GenerationType.TABLE,
generator="aircraftSeqStore")
private Integer id;
After my research, and after reading "dont-let-hibernate-steal-your-identity" article, i don't really understood how to manage my ids.
Should i modify my entities to replace them with an assigned value (how to do it in JPA ?) and should i generate an UUID to affect the ids directly at the creation of the transient object ?
In many tables, i have some easy datas (id, name). I thought i could manage the hashcode and equals methods on name properties which are unique, but not affected at the creation of the object too....(so i think same pb with id which is null ?).
For information, i have an entity which represent a multi join table (3 FK in this join table).
So what do you advice to me ?
Is it not bad of generate UUID for performance ?
EDIT :
Is this entity viable ?
#Id
#Column(name = "AIR_ID", unique = true, nullable = false)
#TableGenerator(name="aircraftSeqStore",
table="T_S_APP_SEQ_STORE_AST",
pkColumnName="AST_SEQ_NAME",
valueColumnName = "AST_SEQ_VALUE",
pkColumnValue = "T_R_AIRCRAFT_AIR.AIR_ID",
allocationSize=1)
#GeneratedValue(strategy=GenerationType.TABLE,
generator="aircraftSeqStore")
private Integer id;
#Column(name = "AIR_BUSINESS_ID", unique = true, nullable = false)
private String uuid = IdGenerator.createId();
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || !(o instanceof Aircraft))
return false;
Aircraft other = (Aircraft)o;
if (uuid == null) return false;
return uuid .equals(other.getUuid ());
}
public int hashCode() {
if (uuid != null) {
return uuid .hashCode();
} else {
return super.hashCode();
}
}
Thank you.
As with every question the full, but seldom usefull, answer is: It depends.
The more helpful variant is:
I use GenerationType.Auto most of the time, and do NOT implement equals and hashcode.
The result is:
you are fine comparing entity objects as long as they live in the same session, since hibernate will ensure that each database row is represented by a single instance per session.
equals and hashcode are stable over time, so you can put your objects in HashSets, change the objects and still get them out again.
If you want to work with objects from different Sessions you have to explicitly compare ids or ids + hashcode or some business key, possibly by implementing a Comparator. The extra effort put in deciding what to use and to implement it will remind you that you are actually doing something going against the grain of Hibernate.
About performance: Depending on the database and the use case UUIDs migh cost performance because they are rather large, or gain performance, because they can get created on the client thus saving a database roundtrip. Most of the times other glitches in the application (especially when using Hibernate) are way bigger then any effect of the ID Generation.
Usually i use:
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private Integer id
and let the persistence provider to chose the right one.
Hope this help you
I recently asked a question that explores an alternative to the usual pattern: Are there any gotchas with this JPA "cached hashCode" pattern?
I included an example of what I usually do with #Entity classes - generating UUIDs on construction. The probability of a UUID collision is so small that you'd be best worrying about cosmic rays. Some people don't like UUIDs because they feel there is a performance penalty. I've not seen any change in performance versus Integer, but I think the chance of an Integer collision is small enough to make it a concern.
#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();
}
Sometimes I want to check if the actual data itself matches, in which case I create a 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);
}

Equals for persistent objects

There is well known problem with implementing equals() (and hashCode(), i will speak about equals() only) for persistance object with database managed id.
New object is not stored in database, therefore does not have database identity, therefore its "id" field is null (or 0 if it is primitive type).
If equals look at id, it will consider all new objects equal, and once it gets id, hash code change so if it already was in hash sensitive collection, it will not be found.
One solution is using business key, but sometimes everything except surrogate id is mutable.
Another solution is to generate (one more, or use it as databes id too) surrogate id when object is created.
Approach I haven't seen mentioned is, use id in equals and make equals (and hashCode()) fail (throw IllegalStateException) when id is null. (and document this behavior)
This way it still can't be in hash collections, but it cannot be accidentaly put there. And for puting it in collections when without id, some wrapper can be used.
Is it good/bad idea? Does it have hiddent problems?
As kan pointed out, if child objects should be put in Set property and persisted with their parent, inability to put objects in Set before they are persisted is big problem (and TreeSet does not help, because it uses equals(), even if it does not use hashCode()).
I mostly use list for child entities, so it does not need to manifest, but it definitely is problem.
I always use the auto-generated id and have never had a problem. You could enforce an object when instantiated to also be persisted using your service layer/factory.
I think the likelihood of any other field (composing a biz key) changing is much more probable than using a non-persisted object in a hashmap and then persisting at the same time causing the look up to fail.
This problem, imho, is somewhat over-analysed. The auto-generated id is often the only test I want to do for equality, nothing else makes sense in a lot of cases. I take the approach that if a non-persisted object is being used/compared the problem is in the business logic and not the underlying equals/hashcode methods
To specifically answer the illegalstateexception idea, throwing an exception when objects are not equal and/or have not been persisted seems rather dramatic.
I use the next code. It covers most cases and it could be used for all cases which I think could occur while using ORM.
public class VersionedEntity
{
private static final long serialVersionUID=1L;
private Long id;
private long version;
#Transient
private int hashCode;
...
public void setId(final Long id)
{
if(this.id != null && !this.id.equals(id))
throw new IllegalArgumentException(this+" has an ID already, cannot change it to "+id);
this.id = id;
}
#Override
public String toString() {
return getClass().getName()+'#'+getId();
}
public boolean equals(final Object o)
{
if (this==o) return true;
if (!(o instanceof VersionedEntity))
return false;
final VersionedEntity entity=(VersionedEntity) o;
final Long id1 = entity.getId();
final Long id2 = getId();
if(id1==null && id2==null)
return super.equals(o);
return id1 != null
&& id2 != null
&& id2.equals(id1);
}
public int hashCode()
{
if(hashCode == 0)
{
hashCode = id != null ? id.hashCode() : super.hashCode();
if(hashCode == 0)
hashCode = 42;
}
return hashCode;
}
}
You has to able to use collections before assigning the id. If you want create an object with a Set property which contains pack of other objects, you have to add them as elements to the Set.

Categories

Resources