Hi Guys,
I have common create copy functionality for every entity in application. There are many jpa entities in which equals and hash methods are based on primaryKey (long or composite key). So create copy means creating clone instance, setting primary key to null and then save (entitymanager.persist()). New copied instance has new primaryKey with properties equal to original one. The same thing I need with SDN entities. I simulate it in this unit test.
Code:
public void cloneUser(){
User findedUser = userDao.findByPropertyValue("userName", "a");
assertThat(findedUser.getId(), notNullValue());
User clone = (User) findedUser.clone();
assertThat(findedUser, not(sameInstance(clone)));
assertThat(findedUser, equalTo(clone));
assertThat(findedUser.getId(), equalTo(clone.getId()));
assertThat(findedUser.getUserName(), equalTo(clone.getUserName()));
clone.setUserName("x");
clone.setId(null);
assertThat(clone.getId(), nullValue());
User savedClone = userDao.save(clone);
assertThat(savedClone.getId(), notNullValue());
assertThat(savedClone.getUserName(), equalTo(clone.getUserName()));
assertThat(findedUser, not(sameInstance(savedClone)));
assertThat(findedUser, not(equalTo(savedClone)));
assertThat(findedUser.getId(), not(equalTo(savedClone.getId())));
}
My first attempt was to implement only clone() method and setter for id() by calling setPersistentState(null);.
Code:
package user;
import org.springframework.data.neo4j.annotation.Indexed;
import org.springframework.data.neo4j.annotation.NodeEntity;
@NodeEntity
public class User implements Cloneable {
@Indexed(unique=true)
String userName;
public Long getId() {
return getNodeId();
}
public void setId(Long id) {
if (id == null){
setPersistentState(null);
}
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
@Override
public User clone() {
try {
return (User) super.clone();
} catch (CloneNotSupportedException e) {
return null;
}
}
}
In this case there is error on this line :
assertThat(findedUser, not(sameInstance(savedClone)));
java.lang.AssertionError:
Expected: not sameInstance(<user.User@5>)
got: <user.User@5>
Clone entity is still the same instance when i call dao.save().
Next I was trying to rewrite clone method by creating empty new instance without reference to backing node and copy the properties from original. Because I think and Michael said that the hash and equals are based only on node id, i must define @GraphId id property.
Code:
package user;
import org.springframework.data.neo4j.annotation.GraphId;
import org.springframework.data.neo4j.annotation.Indexed;
import org.springframework.data.neo4j.annotation.NodeEntity;
@NodeEntity
public class User implements Cloneable {
@GraphId
Long id;
@Indexed(unique=true)
String userName;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
@Override
public User clone() {
User clone = new User();
clone.setId(this.getId());
clone.setUserName(this.getUserName());
return clone;
}
}
In this case error was on line :
assertThat(findedUser, equalTo(clone));
java.lang.AssertionError:
Expected: <user.User@6e3e5e>
got: <user.User@1>
That was the reason why I need to override equals and hash methods.
This version of User passing through unit test.
Code:
package user;
import org.apache.commons.lang.builder.EqualsBuilder;
import org.apache.commons.lang.builder.HashCodeBuilder;
import org.springframework.data.neo4j.annotation.GraphId;
import org.springframework.data.neo4j.annotation.Indexed;
import org.springframework.data.neo4j.annotation.NodeEntity;
@NodeEntity
public class User implements Cloneable {
@GraphId
Long id;
@Indexed(unique=true)
String userName;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
@Override
public int hashCode() {
return new HashCodeBuilder(3, 5).append(getId()).toHashCode();
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || !this.getClass().isAssignableFrom(obj.getClass())) {
return false;
}
User that = (User) obj;
if (getId() == null && that.getId() == null) {
return this == obj;
}
return new EqualsBuilder()
.append(getId(), that.getId())
.isEquals();
}
@Override
public User clone() {
User clone = new User();
clone.setId(this.getId());
clone.setUserName(this.getUserName());
return clone;
}
}
This kind of clone() implementation I use successfully on all SDN entities but only on @RelationshipEntitys there is build error during maven build because aspect defines final equals and hash methods. Eclipse has no problem because there is maybe different build phase. My first post on this thread was about this issue. Sample code and unit test (little bit different) are in first attachment.
Thanks
Vlado