Eugen
Eugen

Reputation: 8783

Hibernate issue with @OneToMany @JoinTable and update operation

I am running into a somewhat strange issue; I have the following JPA mapping:

@Entity
public class Location {

  @Id
  @GeneratedValue(strategy = GenerationType.AUTO)
  @Column(name = "LOCATION_ID")
  private Long id;

  @OneToMany( cascade = { CascadeType.ALL }, fetch = FetchType.EAGER )
  @JoinTable( joinColumns = { @JoinColumn( name = "LOCATION_ID" ) },
  inverseJoinColumns = { @JoinColumn( name = "ATTRIBUTE_ID"  ) } )
  private Set<Attribute> attributes;

and:

@Entity
public class Attribute implements IAttributeSupport {

  @Id
  @GeneratedValue(strategy = GenerationType.AUTO)
  @Column(name = "ATTRIBUTE_ID")
  private Long id;

  @Column(nullable = false) private String name;
  @Column(nullable = false) private String value;
  ...

And I'm doing a simple test:

My expectation (considering the propagation) is that merging the Location would simply propagate to the Attributes, which would get updated. This happens (broadly) - the value of the Attribute that was changed is indeed updated, but then a new INSERT is attempted in the Join Table, where the mapping already exist. Because of this new and unnecessary insert, the failure (as expected) is:

Caused by: com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException: Duplicate entry '1-1' for key 'PRIMARY'

Now, the culprit seems to be the way hashCode and equals are implemneted - it seems that, when the set of attributes gets persisted by Hibernate, the collection persister check if each entry (each Attribute) needs to be inserted:

if ( collection.needsInserting( entry, i, elementType ) )

So, since the name of one of the attributes did change, now this is picked up as needing to be inserted (which is not really correct - inserting is not needed, only update is) - hence the insert operation in the Join Table. I could of course use the id for equals and hashcode, but that's not the way Hibernate recommends it, and also I would rather not do that. Am I missing something in the mapping that may lead to this? This is a pretty standard mapping - simple one to many and simple merge operation - any suggestions to make it work?

Any help is appreciated.

Thanks.

Eugen.

Upvotes: 1

Views: 7793

Answers (1)

atrain
atrain

Reputation: 9255

First point: in a OneToMany, you don't really need a JoinTable, as the child entity will hold a ManyToOne relationship back to the parent. I would take that out.

You might be missing a couple of annotations. On the parent class, add mappedBy to the @OneToMany annotation to indicate which member of the child class holds the association to the parent. In your case, it would probably be

@OneToMany(fetch = FetchType.EAGER, mappedBy = "location")

Additionally, sometimes Hibernate doesn't play nicely with standard JPA cascade annotations (I was dealing with this same issue, and moved from straight JPA to Hibernate cascade annotations). The full complement would end up being:

@Cascade(value = { org.hibernate.annotations.CascadeType.ALL, 
        org.hibernate.annotations.CascadeType.DELETE_ORPHAN })
@OneToMany(fetch = FetchType.EAGER, mappedBy = "location")
private Set<Attribute> attributes;

Then in the child, you need to have a reference back to the parent:

@ManyToOne
@JoinColumn(name = "location_id")
private Location location;

The cascade should then work correctly.

Upvotes: 1

Related Questions