Question

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>();
Était-ce utile?

La solution

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>();
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top