I have a setting of JPA2/Hibernate. Furthermore, the entities are audited by Hibernate Envers. I have the following class to signify postal codes with only one field, namely value
.
It all works fine when saving only one instance of any postal code to the Set<PostalCode>
. However, when I try to add another postal code, the auditing fails with the following exception:
Caused by: javax.persistence.EntityExistsException: a different object with the same identifier value was already associated with the session: [PostalCodesAudit#{PostOffice_id=5, revision_id=DefaultRevisionEntity(id = 16, revisionDate = Aug 19, 2013 8:50:05 AM), revisionType=ADD}]
at org.hibernate.ejb.AbstractEntityManagerImpl.convert(AbstractEntityManagerImpl.java:1359)
at org.hibernate.ejb.AbstractEntityManagerImpl.convert(AbstractEntityManagerImpl.java:1310)
at org.hibernate.ejb.TransactionImpl.commit(TransactionImpl.java:80)
at org.springframework.orm.jpa.JpaTransactionManager.doCommit(JpaTransactionManager.java:513)
... 58 more
The curious thing here is that it works fine, when I remove the implementations of equals
and hashCode
from the PostalCode
class, the mechanism works fine. Also, if I change the collection to Set<String>
, similar mapping works perfectly.
I am not absolutely required to implement equals
and hashCode
in the PostalCode
class, but I'm not overly keen on dropping the support, either.
What is the correct way to map this kind of situation?
@Embeddable
public class PostalCode {
@Column(name = "postalCode", length = 5)
@Pattern(regexp = "[0-9}{5}")
private String value;
// Getter and setter for value
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof PostalCode)) {
return false;
}
PostalCode that = (PostalCode) o;
return new EqualsBuilder().append(this.value, that.value).isEquals();
}
@Override
public int hashCode() {
return new HashCodeBuilder().append(this.value).toHashCode();
}
}
In another class I try to use this as an @ElementCollection
:
@Valid
@ElementCollection(targetClass = PostalCode.class)
@CollectionTable(name="PostalCodes", joinColumns = @JoinColumn(name = "postOffice_id"))
private Set<PostalCode> postalCodes = new HashSet<PostalCode>();
Further investigation reveals that if the type of the collection is Collection<PostalCode>
or List<PostalCode>
, the auditing will fail even without the equals/hashCode
implementations:
// No difference in the following
private Collection<PostalCode> postalCodes = new HashSet<PostalCode>();
private Collection<PostalCode> postalCodes = new ArrayList<PostalCode>();
re
And as a further note, the EntityExistsException
thrown does not contain the value of the postal code: only the foreign key, revision_id, and revisionType are included.
// Excerpt from the exception
PostalCodesAudit#{
PostOffice_id=5,
revision_id=DefaultRevisionEntity(id = 16, revisionDate = Aug 19, 2013 11:52:14 AM),
revisionType=ADD
}
Furthermore, after changing the field to List<PostalCode>
and annotating it with @OrderColumn
the audit mechanism works without exception regardless of whether equals/hashCode
is implemented or not. It still breaks without the order column
@ElementCollection(targetClass = PostalCode.class, fetch = FetchType.LAZY)
@CollectionTable(name = "PostOfficePostalCodes", joinColumns = @JoinColumn(name = "postOffice_id"))
@OrderColumn(name = "listOrder")
private List<PostalCode> postalCodes = new ArrayList<PostalCode>();
You only need to override equals() and hashcode() if the entity will be used in a Set (which is very common) AND the entity will be detached from, and subsequently re-attached to, hibernate sessions (which is an uncommon usage of hibernate).
Hibernate makes sure to return the same object if you read the same entity twice within a Session. Due to this, the default equals() and hashCode() implementations are OK as long as an entity stays in the context of one Session.
I ended up solving it by converting the field to a List<PostalCode>
even though this is not the optimal solution since the list allows duplicate entries. The final mapping is as follows:
@ElementCollection(fetch = FetchType.LAZY)
@CollectionTable(joinColumns = @JoinColumn(name = "postOffice_id"))
@Column(name = "postalCode", unique = true)
@OrderColumn(name = "listOrder")
private List<PostalCode> deliveredPostalCodes = new ArrayList<PostalCode>();
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With