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.
Related
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).
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.
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);
}
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
I'm doing a school project in Java and I the following question have arisen:
I have an entity with attributes - id, name, phone.. with id as the unique primary key. I want to store them in a data structure(such as list..). Then in the application I obtain the data for creating a new instance (name, phone..) and I want to create a new instance of the entity and store it in my data structure with a new unique id. The id shouldn't be random, it would be best if the id rised continuously with the size of the list. Also I dont want to reuse ids.
The first implementation that comes to my mind is to use ArrayList and simply set id as indexes. But ArrayList.remove(int index) after removal shifts all following elements to left. I assume that ArrayList.remove(Object o) works the same, but i would be gratefull i I'm proven wrong. Determining ids from last element would not work either. I could go through all of them but that seems inefiicient.
Thanks in advance for any help :)
You want to keep a counter for them. You could use a static value in the class (you may need to synchronize it for multi-threaded classes.)
import java.util.concurrent.atomic.AtomicInteger;
class MyClass {
// thread safe
private static final AtomicInteger safeCounter = new AtomicInteger();
private final int uniqueId; // can never change uniqueId
private String name; // the data of the class
public MyClass(String name) {
this.name = name;
this.uniqueId = MyClass.safeCounter.getAndIncrement();
}
public boolean equals(Object o) {
if(o instanceof MyClass) { // instanceof also does null check :-)
MyClass mc = (MyClass)o;
return mc.uniqueId == this.uniqueId;
}
return false;
}
public int hashCode() {
return uniqueId;
}
}
If this is for homework, or if threadsafety isn't a concern, you can use a simple static int
class MyClass {
private static int nextUniqueId() {
int result = counter;
counter++;
return result;
}
// not thread safe
private static int counter;
private final int uniqueId; // can never change uniqueId
private String name; // the data of the class
public MyClass(String name) {
this.name = name;
this.uniqueId = nextUniqueId();
}
public boolean equals(Object o) {
if(o instanceof MyClass) { // instanceof also does null check :-)
MyClass mc = (MyClass)o;
return mc.uniqueId == this.uniqueId;
}
return false;
}
public int hashCode() {
return uniqueId;
}
}
How about using a Factory that users a Strategy for generating your identifiers?
Edited to answer question about factories
A Factory is a design pattern that is used to encapsulate the creation of different types of Objects. A Strategy is another design pattern that is used to encapsulate the behavior of specific business logic that might have different rules or that might change over time.
In your case you clearly require a new Identifier for each object that needs to be unique. You also stated in your question comments above that eventually you will be storing your objects in a database, which also would most likely require you to get your identifier from your database in the long run.
Here is a smallish example of using a Factory to create your User Objects instead of just using new(). Please kindly disregard any spelling or compile mistakes, I wrote the following code with out the assistance of a compiler or IDE.
public interface UserFactory {
User createUser();
}
public interface IdentifierStrategy {
// I just picked Long for ease of use.
Long getIdentifier();
}
public class UserFactoryImpl {
private final IdentifierStrategy identifierStrategy;
public UserFactoryImpl(final IdentifierStrategy identifierStrategy) {
this.identifierStrategy = identifierStrategy;
}
public User createUser() {
Long identifier = this.identifierStrategy.getIdentifier();
User user = new User(identifier);
return user;
}
}
public class LongIdentifierStrategy implements IdentifierStrategy {
public Long getIdentifier() {
// Do something here that will return a unique long.
Long long = new Long(1);
return long;
}
}
// In the long term, you would most likely use this IdentiferStrategy
// to get your identifiers from the database.
public class JDBCIdentifierStrategy implements IdentifierStrategy {
public Long getIdentifer() {
// Get a jdbc connection from a jdbc connection pool.
// Get the next identifier from the databsae.
Long long = new Long(1);
return long;
}
}
Now, in the long run, if your requirement change for how you need to identifier your User objects, you would only need to write a new IdentifierStrategy and update your UserFactoryImpl with that new Strategy.
One important question: what's the scope of the uniqueness?
Just for the duration of a run of the application? Do you have a single thread or multiple threads, so unique across those threads? Or could there be several copies of the app running at the same time, so unique across all instances, even across many machines? Will you save the data somewhere and so need uniqueness across future runs of the program too?
Two fundamental schemes:
a). use a database, they usually offer some kind of auto-generated primary key: you insert the record, it gives you a unique key.
b). generate the key yourself, in this case: first isolate the key generation to it's own class, then you can make the generation as clever as you wish. Sketch:
some initialisation, generate an initial value, simple case it's zero, or it derives from the current date/time, or MAC address of your machine, or whatever
provide a getNextId() function, which probably needs to be synchronized if threads are involved.
A very simple scheme, which will be OK for low volume systems, just use
new Date().getTime();
You can also look for GUID generators, which produce something unique, but rather bigger than an int.
My suggestion is to have an Object Pooling for ID generation. When the entity is "deleted", the ID should be returned to the pool, and when needing a new ID, the pool should either
Give you a new ID (if old ID doesn't exists in pool) or
Create a new ID for an entity.
The problem is that you will have to create an entity management system that caters for returning the "used" ID to the pool if entity is "deleted" (bear in mind the multithreading environment, which you will need to manage).
Alternatively, use a database system which provides primary key generation (most uses AUTO_INCREMENT).