1

I am working migrating project to a pure JPA annotation based mapping from an XML mapping and I am running into an issue when trying to delete (remove) and entity and its children. It works in with XML mapping and does not work with the annotation mapping.

The XML mapping looks like this:

<set name="evaluations" order-by="evalDate desc" table="Evaluation" lazy="true" inverse="true" cascade="delete">
    <key column="requestId" />
    <one-to-many class="org.stuff.model.Evaluation" />
</set>

The annotation mapping, as far as I can tell is this:

@OneToMany(orphanRemoval=true)
@JoinColumn(name = "requestId")
@OrderBy("evalDate DESC")
private Set<Evaluation> evaluations = new TreeSet<>();

This is a uni-directional relationship.

The JPA code to delete the entity is:

ServiceRequest sr = em.getReference(ServiceRequest.class, id);
em.remove(sr);

Where the above Evaluation is a child object of ServiceRequest. Hibernate 4.3.7 is the JPA Impl I am using, running on WildFly 8.2.

With Hibernate set to barf out its SQL, executing the remove with the annotation mapping in place Hibernate produces a query to look up the Entity reference and then when the remove is called it produce an update trying to update the child record in Evaluation FK back to ServiceRequest to be null:

Hibernate: update Evaluation set requestId=null where requestId=?

And that blows up because there is a not null constraint on requestId.

If I do the same operation using the XML mapping (see above snippet) it works just fine. All child entities are deleted along with the parent. and Hibernate only produces selects and deletes, if never tries to update anything.

This feels like I have the annotation mapping wrong, but I cannot figure where I went wrong. Please help.

Jason Holmberg
  • 291
  • 2
  • 17

3 Answers3

1

I think you need to specify the cascade annotation. Beware of this issue though.

troy
  • 71
  • 9
1

You xml config actually says the relationship between your ServiceRequest and Set are bi-directional because inverse = "true".

But your JPA annotation is uni-directional. so this should work (edited after OP's comment)

@OneToMany(orphanRemoval=true,mappedBy="requestId")
@OrderBy("evalDate DESC")
private Set<Evaluation> evaluations = new TreeSet<>();

Here mappedBy="requestId"tells Hibernate that this is owner side of the relationship. So it will issue statement to remove Evaluation.

sarahTheButterFly
  • 1,894
  • 3
  • 22
  • 36
  • Thanks, this was the right direction, with some slight modifications. After putting this mapping change in Hibernate complained about using both `mappedBy` and `@JoinColumn` with the exception: `Associations marked as mappedBy must not define database mappings like @JoinTable or @JoinColumn: hci.hrccadmin.model.ServiceRequest.evaluations`. And then after removing `@JoinColumn`, it didn't like `mappedBy="ServiceRequest"`, so I changed it to `mappedBy="requestId"` since that is the FK linking ServiceRequest to Evaluation and that seemed to work. Seems a little weird, but I guess it makes sense. – Jason Holmberg Jun 03 '15 at 13:56
  • Thanks @sarahthebutterfly for the answer. It really got moving again. – Jason Holmberg Jun 03 '15 at 14:11
0

Thanks @troy for some direction. The addition of the cascade alone didn't work, but adding insertable=flase, updateable=false did. So the annotation mapping now looks like this:

@OneToMany(cascade=CascadeType.REMOVE)
@JoinColumn(name = "requestId", insertable=false, updatable=false)
@OrderBy("evalDate DESC")
private Set<Evaluation> evaluations = new TreeSet<>();

I am not sure exactly why this works, so if someone can explain it I would be very grateful.

I got here rather indirectly. First I added a nullable-false to this mapping and when I deployed it Hibernate complained about it and told me I needed to add insert=false update=false to requestId on the Evaluation entity. That sort of worked. I could delete like I wanted to, but I couldn't save or insert an evaluation. I kind of expected that to happen. So I just tired this solution and it worked.

Jason Holmberg
  • 291
  • 2
  • 17
  • 1
    I think your xml config is actually bi-directional relationship as you set inverse = "true". With JPA annotation, now you have set "insertable=false, updatable=false" which created a virtual inverse = "true". That's why it worked. Oh, and I think if you put back @OneToMany(orphanRemoval=true), it will also work. – sarahTheButterFly Jun 03 '15 at 01:19
  • And JPA has it's own inverse = true annotation, see here http://stackoverflow.com/questions/4865285/inverse-true-in-jpa-annotations. @OneToMany(mappedBy="ServiceRequest") – sarahTheButterFly Jun 03 '15 at 01:23
  • Yes, putting back `orphanlRemoval=true` also works, but not alone. `insertable=false, updatable=false` still need to be there. Thanks. – Jason Holmberg Jun 03 '15 at 14:02
  • In this process, I've also learned that `orphanRemoval=true` includes an implicit `CascadeType.REMOVE`, so when specifying `orphanRemoval=true` you don't need to specify `cascade=CascadeType/REMOVE` – Jason Holmberg Jun 03 '15 at 14:09