Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Auditing collection of @Embeddables with overridden equals/hashCode in JPA2/Hibernate

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?

Entities

@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>();

Edit 1

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>();
like image 585
RJo Avatar asked Aug 19 '13 06:08

RJo


People also ask

In what cases equals () and hashCode () should be overridden when using hibernate?

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).

Does hibernate use equals method?

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.


1 Answers

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>();
like image 131
RJo Avatar answered Sep 27 '22 17:09

RJo