Annotation mapping vs. XML mapping and deleting an entity - java

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.

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.

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

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.

Related

Hibernate attempts to delete parent entity in unidirectional ManyToOne relationship

I have a simple unidirectional ManyToOne relationship on an entity, which unfortunately is defined in a schema I cannot change.
It is defined as follows
#Entity
#Table(name="Profile")
...
public class Profile{
#ManyToOne
#JoinColumn(name="usr_id", nullable=false, updatable=false)
private User usr;
...
and all is well. The relationship is enforced with a foreign key in the db, hence the nullable = false and updatable = false. There is no mention of the profiles in user.
When I try to delete a Profile, hibernate also tries to delete the User entity, which is parent to other relationships and therefore fails. I have no CascadeType annotations anywhere.
My intent is to have a simple reference to the user using this profile in the usr field. This is a unidirectional relationship. The user entity should not be affected whenever I delete a profile.
This appers to be achievable when the usr field may be dereferenced before delete (I can see in the hibernate generated sql that hibernate attempts to set the field to null before deletion) - however that fails because of the foreign key.
Is what I'm trying to do achievable? If so, how?
(I'm using spring data on top of hibernate, if that is relevant.)
further Infos: I have tried optional=false, and it leads to the delete the parent entity behaviour. I have tried all fitting combinations of CascadeTypes, #OnDelete with NO_ACTION (still tries to delete the user) and defining a reverse but owned by user relationship - no success so far. On top of that, I tried the search function ;), which lead me to the conclusion that this is just my problem. If I missed an answered question, I'd appreciate a pointer in the right direction. Thanks.
Do you have some kind of other non-nullable association #ManyToOne or #OneToOne to the Profile entity? You can debug into the deletion process by setting a break point in e.g. the JDBC driver in e.g. Connection.prepareStatement and go down the stack frames to the cascading part to figure out why this cascading happens.
If all that doesn't help, please create a reproducing test case and submit an issue to the Hibernate issue tracker.

Hibernate Delete, child violation

I am yet again stuck with trying to delete data with Hibernate..
I am at point where I am starting to just stack annotation, hoping something would work... so far nothing has.
I need to delete old calculation when starting new for same time period.
I have the following query
#Modifying
#QueryHints(value = #QueryHint(name = HINT_FETCH_SIZE, value = "10"))
#Query(value = "DELETE FROM Group a WHERE a.c_date BETWEEN :dateA AND :dateB")
void deleteOld(#Param("dateA") LocalDate dateA, #Param("dateB") LocalDate dateB);
which uses entity Group, which has (on top of normal String, LocalDate and long types) attribute
#OneToMany(cascade=CascadeType.ALL, mappedBy = "owner", orphanRemoval = true)
#JsonManagedReference
#OnDelete(action = OnDeleteAction.CASCADE)
private List<Instrument> instruments = new ArrayList<>();
But I still get violated - child record found every time I try to run delete method.
I keep finding more and more annotations like this, from threads where people have the same kind of problems, but I would love to understand why is this a problem. From what I read Cascade and orphanRemoval should be all I need, but it sure does not seem to be.
Hibernate: 5.2.17.Final
Please help me to understand, why is this happening ?
The #OnDelete will delete records using a ON DELETE rule on the database, when using Hibernate to generate the schema. If you manage your own schema this will have no effect.
The #QueryHints you have specified doesn't really make sense here, for an DELETE query that is. Nothing will be fetched.
The fact that you are using an #Query basically bypasses the configuration in the #OneToMany, simply due to the fact that you write a query and apparently know what you are doing. So the mapping isn't taken into account.
If you want to delete the childs as then you have 3 options:
Add an additional query and first remove the childs, then the parents
Add an ON DELETE rule to your database, to automatically remove the childs
Retrieve the Group and remove using EntityManager.remove which will take the #OneToMany mappings into account as now Hibernate needs to manage the dependencies between the entities.

JPA: is there any annotation with the same behaviour as Hibernate #Where?

I'm working with JPA annotations, and I'd like to use an annotation that behaves the same as the Hibernate #Where.
I want to map an entity from a table, but I have to do add a where clause in every query that uses that table.
This is my case: my table has a column deleted, and when this column is 1, this row should not be appear.
If I'd use Hibernate hbm, this is resolved in this way:
<hibernate-mapping>
<class
name="MyClass"
table="MyTable"
dynamic-insert="false"
dynamic-update="false"
where="deleted = 0">
</hibernate-mapping>
But I don't know how to do this only with JPA.
The only idea that comes to me is to extend the JpaRepository of this entity, but my problem is that there is another entity that uses this one, so extending the JpaRepository of the first entity is useless.
Using the Where clause in the column should work, but I'm trying to find a way to do this without Hibernate.
Anyone have any idea?
No, there isn't anything like that in JPA.
But there's really nothing wrong in having a few provider specific annotations here and there that do the job.

EclipseLink native query and FetchType behaviour

I'm trying to understand EclipseLink behaviour in case if I use native query. So I have Entity like this:
class Entity {
#OneToOne(fetch = FetchType.LAZY)
#JoinColumn(name="other_entity_id")
private OtherEntity otherEntity;
#Column(name = "name")
private String name;
//gets ... sets ...
}
and corresponding table looks like:
**ENTITY**
INTEGER ID;
VARCHAR NAME;
OTHER_ENTITY_ID;
And then I run native query
Query query = getEntityManager().runNativeQuery("select * from ENTITY", Entity.class);
query.getResultList()
Within Entity I have declared OtherEntity otherEntity which is annotated with FetchType.LAZY, however my query selects (*) - all of the columns, including OTHER_ENTITY_ID. The question is - if I run native query that fetches all columns, will fields annotated with FetchType.LAZY populated as if they were FetchType.EAGER or not? I've never worked with EclipseLink before and tyring to decide is it worth using it or not so I would really appreciate any help
Thanks, Cheers
My first advice is to turn on EclipseLink's SQL logging, and execute the equivalent JPQL to load what you are looking for and see the SQL EclipseLink generates to accomplish that to get an understanding of what is required to build objects in your native queries based on your current mappings.
Relationships generally loaded with a secondary query using the values read in from the foreign keys, so eager or lazy fetching is not affected by the native query to read in "Entity" - the query requires the other_entity_id value regardless of the fetch type. When required based on eager/lazy loading, EclipseLink will issue the query required by the mapping.
You can change this though by marking that the relationship is to use joining. In this case, EclipseLink will expect not only the Entity values to be in the query, but the referenced OtherEntity values as well.

JPA one-to-many unidirectional relationship using a join table

I would like to evaluate JPA on an existing project. The database model and the java classes exists and are currently mapped via self generated code. The database model and the java classes do not fit ideally together - but the custom mapping works well. Nevertheless the usage of JPA in general seems worth a try.
As you see I am new to JPA and have to do the work with xml configuration. Currently I am working on a one-to-many unidirectional relationship using a join table (please do not discuss this szenario here).
A (one - relationship owner) <-> AB (JoinTable) <-> B (many)
The tables look like this
A
--
ID
BREF
...
B
--
ID
...
AB
--
A_BREF (foreign key to a reference column in A which is NOT the id)
B_ID
I would like to define a unidirectional one-to-many relationship for class A.
class A {
private List<B> bs;
}
and did it like this:
<one-to-many name="bs">
<join-table name="ab">
<join-column name="a_bref">
<referenced-column-name name="bref" />
</join-column>
<inverse-join-column name="b_id">
<referenced-column-name name="id" />
</inverse-join-column>
</join-table>
</one-to-many>
Althoug this does not force an error it is not working. The problem is that the join table does not work on the ID column of A. The query to select the "B" entities works with the A.ID column value instead of the A.BREF column value to select the entities.
(How) can I make this mapping work (I use eclipselink 2.2.0)?
Thanks for any suggestion!
EDIT:
After looking at a link provided in #SJuan76 answer I slightly modified my mapping to
<one-to-many name="bs">
<join-table name="ab">
<join-column name="a_bref" referenced-column-name="bref" />
<inverse-join-column name="b_id" referenced-column-name="id" />
</join-table>
</one-to-many>
This now causes the following errors (tested with eclipselink 2.1.0 and 2.2.0)
eclipselink 2.1.0
Exception Description: The parameter
name [bref] in the query's selection
criteria does not match any parameter
name defined in the query.
eclipselink 2.2.0
Exception Description: The reference
column name [bref] mapped on the
element [field bs] does not
correspond to a valid field on the
mapping reference.
By the way - if I remove the referenced-column-name="bref" from the definition I get the same exception for the referenced-column-name="id" on the inverse-join-column element. So I doubt that I have understood referenced-column-name correct. I used it to specify the database column name of the tables which are related to the join table. Is this correct?
SOLUTION:
The final error in my szenario was that I did not have the BREF field definied in my class
class A {
private long bref; // missing !
private List<B> bs;
}
and in my orm.xml mapping file for this class
<basic name="bref">
<column name="bref" />
</basic>
I was not aware that I have to define the used join mapping referenced-column-name attributes somewhere in my mapping classes (as I also did not have the join-table itself or the name attributes of join-column/inverse-join-column mapped to a class or class members.)
Also the tip to check the case issue was helpful for me. I feel now quite to verbose in specifying my mapping as I overwrite all default (uppercase) mappings with lowercase values. As my database is not case sensitive I will use upper case notation if special mapping is needed to go with the default.
+1 for all!
Can you try defining the field as "BREF" or the same exact case used if you defined it on the attribute mapping, or you can try setting the eclipselink.jpa.uppercase-column-names persistence property to true. This is likely the issue with "id" when referenced-column-name="bref" is removed, since it is likely the field in the entity defaults to "ID".
In general JPA requires that the foreign keys/join columns reference the primary key/Id of the Entity. But, this should work with EclipseLink, so please include the SQL that is being generated, and if it is wrong, please log a bug.
How is the Id of A defined, is it just ID or ID and BREF?
You can use a DescriptorCustomizer to customize the ManyToManyMapping for the relationship and set the correct foreign key field name.

Categories