Problem with equals() method in Hibernate - java

I am developing an application in Hibernate where I have model classes like these:
public class Employee
{
private int ID;
private String name;
private Department department;
//other properties
//constructors, getters and setters
}
Note that the ID is not a value populated by the user and is populated using GenerationType.Identity as the strategy.
Also I have another class Department as follows:
public class Department
{
private int ID;
private String name;
private Set<Employee> employees; //this is actually a HashSet
//other implementations
}
There is a ManyToOne bi-directional relationship between an Employee and a Department.
So to add a new Employee to an existing Department, I do the following
Department existingDepartment = ...;
Employee newEmployee = ...;
existingDepartment.addEmployee(newEmployee);
employee.setDepartent(existinDepartment);
session.save(newEmployee);
Now conceptually two Employee objects are the same if they have the same ID. So my equals() method in the Employee class looks like this:
public boolean equals(Object o)
{
if(!(o instanceOf Employee))
{
return false;
}
Employee other = (Employee)o;
if(this.ID == o.etID())
{
return true;
}
return false;
}
Now the problem is when I create a new Employee(); I do not have its ID, since it will be assigned when it will be persisted. So when I say
existingDepartment.addEmployee(newEmployee);
the internal HashSet of the Department object is effectively using an equals() method which is broken [since it uses a member variable to determine equality that as not been initialized properly].
This seems like a very basic problem but, how do I solve it? Or am I designing my classes totally wrong? Or should my equals method be re-written to compare other values instead of ID, which I guess would be absurd.

This seems like a very basic problem but, how do I solve it? Or am I
designing my classes totally wrong? Or should my equals method be
re-written to compare other values instead of ID, which I guess would
be absurd.
There are two different philosophies concerning this.
a) equals() / hashCode() based on DB id
Drawback: you can't compare persistent and non-persistent objects
b) equals() / hashCode() based on contents
Drawback: two objects with the same id may turn out to be non-equal.
I prefer the second approach, it makes more sense from a Java point of view (although admittedly not from a DB point of view). The only thing I'd want to make sure is that you never mix the approaches.
This has been discussed many times before, btw:
Should I write equals() methods in JPA entities?
What is the best practice when implementing equals() for entities with generated ids
JPA : not overriding equals() and hashCode() in the entities?
(etc.)

Rewrite your equals method, so that it returns false, when o is null:
#Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final Employee other = (Employee) obj;
if (this.id != other.id && (this.id == null || !this.id.equals(other.id))) {
return false;
}
return true;
}

In hibernate usually you can tell it to use a value when it hasn't been saved to db. For example, I have used -1 for an ID which hasn't been stored yet.
You should initialize your ids like this to make sure you get a consistent behavior.
private int ID = -1;

You may add an transient field with an different non-persistent id. (maybe you should upgrade to "long" id).
Something like this for Example
public class Employee {
private static int lastTID = 0;
private int ID = -1;
private transient int tID;
..
public Employee () {
synchronized (getClass()) {
tId = -- lastTID;
}
}
public boolean equals(Object o) {
..
Employee other = (Employee)o;
..
if (ID != -1) {
return ID == other.ID;
} else {
return other.ID == -1 && tID == other.tID;
}
}
In any case you have to assure that there are not saved and unsaved Employee in use.
Another strategy is to save Employee first and then to add it to Department

Related

Unique Objects into list in java

I want to add unique objects into list on the basis of property of object.
class Employee {
protected long employeeId;
protected String firstName;
protected String lastName;
Employee(long employeeId,String firstName,String lastName){
this.employeeId = employeeId;
this.firstName = firstName;
this.lastName = lastName;
}
}
I want to insert object into list on the basis of unique 'employeeId' and 'firstName' , combination should be unique while inserting object into list.
If you want certain containers to work that are able to prevent duplicates of your Employee from being added you need to define how equals is calculated in Employee.
public class Employee {
//...
#Override
public boolean equals(Object obj) {
if (!(obj instanceof Person))
return false;
if (obj == this)
return true;
Person rhs = (Person) obj;
return new EqualsBuilder()
// if deriving: .appendSuper(super.equals(obj))
.append(employeeId, rhs.employeeId)
.append(firstName, rhs.firstName)
.append(lastName, rhs.lastName)
.isEquals();
}
}
If you want certain containers to work that are able to look up your Employee quickly you need to define how hashcode is calculated in Employee.
public class Employee {
//...
#Override
public int hashCode() {
int hash = 1;
hash = hash * 17 + employeeId;
hash = hash * 31 + firstName.hashCode();
hash = hash * 13 + lastName.hashCode();
return hash;
}
}
Most modern IDE's will offer a re-factoring to do this for you. Use the same fields in both so they change together.
Since you don't want duplicates you're best bet is to not use ArrayList but something that pays attention to the hash and will enforce uniqueness for you. Whenever I'm picking a container in java I look at this:
To have all of those available to you, you need to implement not only hashcode and equals but comparator as well.
Just to be clear, the point of the hash is to provide lookup speed. The point of equals is to define what objects are considered identical. The point of comparator is to provide an ordering for the objects.
If you want to include the less frequently used containers try this:
This can be achieved by overwriting the methods equals() and hashCode() - then you can turn to existing java.util collection classes (like HashSet) that use these methods when comparing objects.
Here, equals() & hashCode() methods are overridden to meet the given requirements.
class Employee {
protected long employeeId;
protected String firstName;
protected String lastName;
Employee(long employeeId,String firstName,String lastName){
this.employeeId = employeeId;
this.firstName = firstName;
this.lastName = lastName;
}
public boolean equals(Object o){
if(o == null) return false;
if(!(o instanceof) Employee) return false;
Employee other = (Employee) o;
if(this.employeeId != other.employeeId) return false;
if(! this.firstName.equals(other.firstName)) return false;
return true;
}
public int hashCode(){
return (int) employeeId * firstName.hashCode();
}
}
Here, if two Employee objects are equal, they will also have the same hash code. But, even still two Employee objects can be not equal having the same hash code.
Ex: The hash code is the employeeId is rounded down to an int. That means that many employee id's could result in the same hash code, but these Employee objects would still not be equal, since they don't have the same employee id.

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.

removig items from arraylist

In an ArrayList, I have the same kind of objects. Each object has an id, name, and number as their fields. There is a chance that more than one object will have the same phone number. How can I make the ArrayList in such a way that all the ArrayList objects have distinct phone numbers?
override in your class methods equals() and hashCode(). In equals you will compare by phone number. Generate hashcode from your phone number too.
Now you are ready to use Set interface which will compare your objects by phone numbers automatically and exclude duplicates.
example below:
public class Test {
private int id;
private String name;
private String phoneNumber;
public Test(int id, String name, String phoneNumber) {
this.id = id;
this.name = name;
this.phoneNumber = phoneNumber;
}
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Test test = (Test) o;
return phoneNumber.equals(test.phoneNumber);
}
#Override
public int hashCode() {
return phoneNumber.hashCode();
}
}
now instead of using List<Test> arr = ArrayList<Test>(), use Set<Test> mySet = new HashSet<Test>().
Try Set.
A collection that contains no duplicate elements.
First solution that comes to my mind is to use a HashMap.
Simply create a HashMap with the 'phonenumber' as key and your object as the value. After adding all the elements, you will have the list of objects with unique phone numbers. Simply iterate over this to create the List that you need.

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