OpenJPA - Nested OneToMany relationships merge issue - java

Posting this here as I wasn't seeing much interest here: http://www.java-forums.org/jpa/96175-openjpa-one-many-within-one-many-merge-problems.html
Trying to figure out if this is a problem with OpenJPA or something I may be doing wrong...
I'm facing a problem when trying to use OpenJPA to update an Entity that contains a One to Many relationship to another Entity, that has a One to Many relationship to another. Here's a quick example of what I'm talking about:
#Entity
#Table(name = "school")
public class School {
#Column(name = "id")
protected Long id;
#Column(name = "name")
protected String name;
#OneToMany(mappedBy = "school", orphanRemoval = true, cascade = CascadeType.ALL)
protected Collection<ClassRoom> classRooms;
}
#Entity
#Table(name = "classroom")
public class ClassRoom {
#Column(name = "id")
protected Long id;
#Column(name = "room_number")
protected String roomNumber;
#ManyToOne
#JoinColumn(name = "school_id")
protected School school;
#OneToMany(mappedBy = "classRoom", orphanRemoval = true, cascade = CascadeType.ALL, fetch = FetchType.EAGER)
protected Collection<Desk> desks;
}
#Entity
#Table(name = "desk")
public class Desk {
#Column(name = "id")
protected Long id;
#ManyToOne
#JoinColumn(name = "classroom_id")
protected ClassRoom classRoom;
}
In the SchoolService class, I have the following update method:
#Transactional
public void update(School school) {
em.merge(school);
}
I'm trying to remove a Class Room from the School. I remove it from the classRooms collection and call update. I'm noticing if the Class Room has no desks, there are no issues. But if the Class Room has desks, it throws a constraint error as it seems to try to delete the Class Room first, then the Desks. (There is a foreign key constraint for the classroom_id column)
Am I going about this the wrong way? Is there some setting I'm missing to get it to delete the interior "Desk" instances first before deleting the Class Room instance that was removed?
Any help would be appreciated. If you need any more info, please just let me know.
Thanks,

There are various bug reports around FK violations in OpenJPA when cascading remove operations to child entities:
The OpenJPA FAQ notes that the following:
http://openjpa.apache.org/faq.html#reorder
Can OpenJPA reorder SQL statements to satisfy database foreign key
constraints?
Yes. OpenJPA can reorder and/or batch the SQL statements using
different configurable strategies. The default strategy is capable of
reordering the SQL statements to satisfy foreign key constraints.
However ,you must tell OpenJPA to read the existing foreign key
information from the database schema:
It would seem you can force the correct ordering of the statements by either setting the following property in your OpenJPA config
<property name="openjpa.jdbc.SchemaFactory"> value="native(ForeignKeys=true)"/>
or by adding the org.apache.openjpa.persistence.jdbc.ForeignKey annotation to the mapping:
#OneToMany(mappedBy = "classRoom", orphanRemoval = true, cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#org.apache.openjpa.persistence.jdbc.ForeignKey
protected Collection<Desk> desks;
See also:
https://issues.apache.org/jira/browse/OPENJPA-1936

Related

How to configure Entity class for MySQL database table witch has n:m relationship and multiple primary keys

When I run the spring boot project I get the following error
" Invocation of init method failed; nested exception is org.hibernate.AnnotationException: No identifier specified for entity:"
I have a few other classes with multiple primary keys and foreign keys but they didn't run to an error.
import javax.persistence.*;
#Entity
#Table(name="roles_has_features")
public class RoleFeatures {
#Column(name = "role_id_fk")
private Long roleIdFk;
#Column(name = "feature_id_fk")
private Long featureIdFk;
public Long getRoleIdFk() { return roleIdFk; }
public void setRoleIdFk(Long roleIdFk) { this.roleIdFk = roleIdFk; }
public Long getFeatureIdFk() { return featureIdFk; }
public void setFeatureIdFk(Long featureIdFk) { this.featureIdFk = featureIdFk; }
}
This actually has nothing to do with Spring. This is an error thrown by Hibernate, because JPA specification requires an Identity for each entity. As for your case, I would not suggest to create a separate entity, because as far as I understand from your column names, it's just a mapping for a relation between role and feature tables. I'd suggest to JPA Many-To-Many relationship. Take a look at #ManyToMany and #JoinTable annotations.
Also this looks as a really good tutorial for me
Hibernate – Many-to-Many example
The error message describes the issue pretty well:
No identifier specified for entity
You do not have an #Id annotated column in your RoleFeatures entity. Thus, hibernate is unable to identify an entity in the database and refuses to start.
Your so-called entity looks more like an Many-To-Many relationship. Maybe it's better to go this way.
Something like this:
#Entity
public class Role {
#Id
#Column(name = "role_id")
private Long id;
#ManyToMany
#JoinTable(name = "roles_has_features",
joinColumns = #JoinColumn(name = "feature_id_fk", referencedColumnName = "feature_id"),
inverseJoinColumns = #JoinColumn(name = "role_id_fk", referencedColumnName = "role_id"))
private List<Feature> features;
...
}
See also: https://www.baeldung.com/jpa-many-to-many

orphan removal is not working properly

I have an one-to-many collection annotated as following
#Entity
#Table(name = "students")
public class Student {
#OneToMany(mappedBy = "student", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER)
#OrderColumn(name ="index")
private List<Preference> preferences;
}
Preference entity
#Entity
#Table(name = "preferences")
public class Preference {
// id
#ManyToOne
#JoinColumn(name = "student_id")
private Student student;
#ManyToOne
#JoinColumn(name = "project_id")
private Project project;
private Integer index;
}
I try to remove an element from the list like this:
public void removePreference(Preference preference) {
preferences.remove(preference);
preference.setStudent(null);
}
The above code is not working, the entity is not removed from the table. How can i achieve this?
Assuming that the Project to Preference relationship is defined the same way that Student to Preference then your problem comes from the fact that your Preference entity is the child of two others entity.
When you remove it from the student's preference it is marked as an removed entity by Hibernate but as it is still referenced by the project Hibernate will re-manage it, as it is specified in the JPA specification for this use case. Check section 3.2.2 and 3.2.3 about persisting and removing.
To solve your case you can delete it from all sides or check if a #ManyToMany relationship between Student and Project with Preference as join table wouldn't suit better.

Hibernate OneToOne mapping to different tables

I need to persist a data structure that has value which is either a string, double or date.
Is there a way to do a one-to-one mapping, conditional by table?
I tried this...
#Table(name = "FIELD_CRITERIA")
public class FieldCriteriaEntity implements Identifiable{
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "CRITERIA_KEY", unique = true, nullable = false)
private Long id;
#OneToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL,optional=true)
#JoinColumn(name="CRITERIA_ID")
private StringCriteriaEntity stringCriteria;
#OneToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL,optional=true)
#JoinColumn(name="CRITERIA_ID")
private NumeriCriteriaEntity numericCriteria;
#OneToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL,optional=true)
#JoinColumn(name="CRITERIA_ID")
private DateCriteriaEntity dateCriteria;
}
However, hibernate doesn't like this:
Caused by: org.hibernate.MappingException: Repeated column in mapping for entity:
Is there a way to configure hibernate to handle this? Or should I simply re-model the FIELD_CRITERIA table to include 3 optional OneToMany relationships?
First you may try to make DateCriteriaEntity and NumericCriteriaEntity the owners of the "one-to-one" relation, not the FieldCriteriaEntity. Move the CRITERIA_ID column to tables that correspond to NumericCriteriaEntity and DateCriteriaEntity so that the column will store FieldCriteriaEntity id as foreign key, and use #OneToMany(mappedBy="correspondent field name") in FieldCriteriaEntity instead of your variant.
Consider this article http://uaihebert.com/jpa-onetoone-unidirectional-and-bidirectional/
I guess the better way of achieving this is to use rework your entity design slightly. Please see the following class diagram. You can create an abstract CriteriaEntity which would have the criteriaId as primary key. Please choose carefully the inheritance strategy for your sub classes. If the criteria entities are relatively simple then consider using SINGLE_TABLE or else move to TABLE_PER_CLASS.
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
You will need to rework your FieldCriteriaEntity to use only one mapping. Please see the following
#Table(name = "FIELD_CRITERIA")
public class FieldCriteriaEntity implements Identifiable{
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "CRITERIA_KEY", unique = true, nullable = false)
private Long id;
#OneToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL,optional=true)
#JoinColumn(name="CRITERIA_ID")
private CriteriaEntity criteria;
}
Hope this helps!

fetch data in ManyToOne relation using Restriction

There are two tables with #OneToMany and #ManyToOne bidirectional relation, like this:
#Entity
public class Asset {
private int id;
private int count;
#OneToMany
private Set<Dealing> dealings;
...
}
#Entity
public class Dealing {
private int id;
...
#ManyToOne
#JoinColumn(name = "customer_id", nullable = false, updatable = false)
private Customer customer;
#ManyToOne
#JoinColumn(name = "product_id", nullable = false, updatable = false)
private Product product;
#ManyToOne(cascade = CascadeType.ALL)
private Asset asset;
}
all things sound OK, but when I want to search data using Restriction like this,
session.createCriteria(Asset.class).add(Restrictions.eq("dealings.customer.id", customerId)).add(Restrictions.eq("dealing.product.id", productId)).list();
In this level I get this error,
could not resolve property: dealings.customer of: com.project.foo.model.Asset
one of the solutions are to change my strategy but i wasted time to find this,btw I don't have any idea about it, do you ?
First of all, you don't have a bidirectional OneToMany association, but two unrelated unidirectional associations. In a bidirectional OneToMany association the One side must be marked as the inverse of the Many side using the mappedBy attribute:
#OneToMany(mappedBy = "asset")
private Set<Dealing> dealings;
Second, using the criteria API for such static queries is overkill, and leads to code that is harder to read than necessary.I would simply use HQL which is much easier to read. Criteria should be used for dynamic queries, IMHO, but not for static ones:
select asset from Asset asset
inner join asset.dealings dealing
where dealing.customer.id = :customerId
and dealing.product.id = :productId
Whether you use HQL or Criteria, you can't use asset.dealings.customer, since asset.dealings is a collection. A collection doesn't have a customer attribute. To be able to reference properties from the Dealing entity, you need a join, as shown in the above HQL query. And it's the same for Criteria:
Criteria criteria = session.createCriteria(Asset.class, "asset");
criteria.createAlias("asset.dealings", "dealing"); // that's an inner join
criteria.add(Restrictions.eq("dealing.customer.id", customerId);
criteria.add(Restrictions.eq("dealing.product.id", productId);

JPA and a novice's issue with relationship-mapping

I am trying to get the following type of mapping to work
Table event has the following columns:
id (PK)
prodgroup
errandtype
table errandtype : errandtype
table prodgroup: prodgroup
I have corresponding JPA classes
#Entity
#Table(name="event")
public class MyEvent {
#Id
int id;
// what mapping should go here?
Prodgroup prodgroup;
// what mapping should go here?
ErrandType errandtype;
}
#Entity
public class Prodgroup {
#Id
private String prodgroup;
}
#Entity
public class ErrandType {
#Id
private String errandtype;
}
Ok so questions are marked as comments in the code but I'll try to be explicit anyway.
In the above example I want my Prodgroup and ErrandType fields in the MyEvent class to be set to corresponding Prodgroup and Errandtype instances
I have tried #OneToOne relationships with #joincolumns and with mappedby attribute, but I just can't get it working and I've lost all sense of logical approach. My grasp of JPA entity mapping is clearly weak.
So can anyone bring some clarity?
It should be:
#Entity
#Table(name="event")
public class MyEvent {
#Id
int id;
// what mapping should go here?
#ManyToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JoinColumn(name = "prodgroup_id", insertable = true, updatable = true)
Prodgroup prodgroup;
// what mapping should go here?
#ManyToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JoinColumn(name = "errandtype_id", insertable = true, updatable = true)
ErrandType errandtype;
}
#Entity
public class Prodgroup {
#Id
private String prodgroup;
}
#Entity
public class ErrandType {
#Id
private String errandtype;
}
FetchType Eager means the object will be always loaded (would be "Lazy" by default if not specified).
CascadeType.ALL means mearge/persist/remove will be also done to linked tables.
Sebastian
Your table columns event.prodgroup and event.errandtype are foreign keys to respective tables (prodgroup, errandtype). So you need #ManyToOne association (because many events may share one prodgroup or errantype).

Categories

Resources