JPA and a novice's issue with relationship-mapping - java

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).

Related

How do I cascade persist an #OneToMany relationship with an #EmbeddedId using Spring Data / Hibernate

I've seen a lot of similar questions asked about this but haven't found a solution that fixes the problem I'm seeing, so apologies up front if this is a redundant question. In my situation I have various types of entities and they're each going to have their own tag associations. So I want a generic Tag class that won't have it's own id, but rather an id / composite key made of the id of the entity it's tagging, plus the tag type. To (attempt to) achieve this I made an #Embeddable id class:
#Embeddable
public class TagId implements Serializable {
#Column(columnDefinition = "BINARY(16)")
private UUID parentId;
private String value;
// Getters, setters...
}
That Id is in turn used by a #MappedSuperclass:
#MappedSuperClass
public class Tag {
#EmbeddedId
private TagId id;
// Other attributes, getters, setters...
}
... and then when I want to tag a specific entity, for example using a BookTag, the table would have a book_id column as a foreign key to a Book table taking the place of parentId :
#Entity
#Table(name = "book_tag")
#AttributeOverride(name = "parentId", column = #Column(name = "book_id"))
public class BookTag extends Tag {
// other attributes, getters, setters...
}
Then finally, I have a Book entity:
#Entity
#Table(name = "book")
public class Book {
#Id
#GeneratedValue
#Column(columnDefinition = "Binary(16)")
private UUID id;
// other attributes, getters, setters...
#OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL, mappedBy = "id.parentId")
private List<BookTag> tags;
}
When I then try to save a new Book, with a populated BookTag collection, using a Spring Data JPA repository to repo.save(book), my desired behavior is that the Book is saved, then the id is copied to the BookTag objects, and those are saved. Unfortunately, what I'm seeing in the log is that Book is inserted as expected, then the inserts for the Tag objects are run, but book_id is being bound as null for each of the entries.
I've tried a few other approaches:
#JoinColumn instead of mappedBy
#MapsId with a #ManyToOne reference to Book on BookTag
#GeneratedValue on parentId
None worked, but it is possible my syntax was off. Thanks in advance for anyone who knows how to tackle this problem.
To anyone who wants to do something similar, I finally found a solution that meets my criteria.
TagId was modified to this:
#Embeddable
public class TagId<T> implements Serializable {
#ManyToOne
private T taggedEntity;
private String value;
// Getters, setters...
}
...which leads to a slight modification to Tag...
#MappedSuperClass
public class Tag<T> {
#EmbeddedId
private TagId id;
// Other attributes, getters, setters...
}
...and then BookTag...
#Entity
#Table(name = "book_tag")
public class BookTag extends Tag<Book> {
// other attributes, getters, setters...
}
...and finally Book:
#Entity
#Table(name = "book")
public class Book {
#Id
#GeneratedValue
#Column(columnDefinition = "Binary(16)")
private UUID id;
// other attributes, getters, setters...
#OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL, mappedBy = "id.taggedEntity")
private List<BookTag> tags;
}
Now I can add 1...* BookTags to a Book, and in turn I have to set the Book on all the BookTags, but then it's one call to bookRepository.save() and everything cascades down. It would have been nicer to just do it with an id, but a generic is flexible enough. I'll just have it implement an interface so that toString/hashCode/equals can call getId on parent.
The only other drawback is I couldn't get #AttributeOverride to work, so while I'd prefer that my BookTag table have a book_id column, tagged_entity_id will have to suffice.

JPA OnetoOne mapping returns null

I have the following table in the DB:
material (id_mat, name, initial_weight, cargo_number, exp_date, left_amount)
I had to add an additional table, which shows constructions that were built using the materials from material table. Here how it looks:
material_construction (mat_id, construction_number)
I then created an entity class called MatConstructionMapping for the table material_construction:
#Entity(name = "material_construction")
public class MatConstructionMapping implements Serializable {
private static final long serialVersionUID = 1739614249257235075L;
#Id
#OneToOne
#JoinColumn(name = "mat_id", referencedColumnName = "id_mat", insertable=false, updatable=false)
private Material mat;
#Column(name="construction_number")
private Integer number;
public Integer getConNumber() {
return number;
}
}
And, added following getter in the Materialentity:
#OneToOne
#JoinColumn(name = "mat_id")
public MatConstructionMapping getMaterialConstructionNumber() {
return conNumber;
}
The issue is that, when I am retrieving the conNumber for any materials, its always null, however there are the values in the DB. What am I doing wrong?
you cannot have JoinColumn at both sides, #JoinColumn should be at the owning entity which you can define in any side in one to one relation, the other side should have mappedBy attribute to indicate the reverse relation, say for example MatConstructionMapping is the owning entity, then you should edit your Material
#OneToOne(mappedBy="mat")
public MatConstructionMapping getMaterialConstructionNumber() {
return conNumber;
}

OpenJPA - Nested OneToMany relationships merge issue

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

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!

JPA 2 Hibernate mapping with composite key in primary key using #IdClass with 3 tier structure

This question is very similar to: JPA (Hibernate, EclipseLink) mapping: why doesn't this code work (chain of 2 relationships using JPA 2.0, #EmbeddedId composite PK-FK)?
Actually my only (from meaningful that I spotted) difference is that I use #IdClass and that I most probably won't be able to switch to a different provider than hibernate.
but anyway here is the code (removed parts that where unimportant):
PermissionContextType.java:
#Entity
#IdClass(PermissionContextTypePk.class)
public class PermissionContextType{
#Id
private String id;
#Id
#JoinColumn (name = "PROJECT", referencedColumnName = "ID")
#ManyToOne ()
private Project project;
public static class PermissionContextTypePk implements Serializable{
public String project;
public String id;
// ... eq and hashCode here ...
}
}
PermissionContext.java:
#Entity
#IdClass(PermissionContextPk.class)
public class PermissionContext{
#Id
private String id;
#Id
#JoinColumns ({
#JoinColumn (name = "PROJECT", referencedColumnName = "PROJECT"),
#JoinColumn (name = "PERMISSIONCONTEXTTYPE", referencedColumnName = "ID")
})
#ManyToOne
private PermissionContextType permissionContextType;
public static class PermissionContextPk implements Serializable{
public String id;
public PermissionContextTypePk permissionContextType;
// ... eq and hashCode here ...
}
}
Permission.java:
#Entity
#IdClass(PermissionPk.class)
public class Permission{
#Id
private String id;
#Id
#JoinColumns ({
#JoinColumn (name = "PROJECT", referencedColumnName = "PROJECT"),
#JoinColumn (name = "PERMISSIONCONTEXTTYPE", referencedColumnName = "PERMISSIONCONTEXTTYPE"),
#JoinColumn (name = "PERMISSIONCONTEXT", referencedColumnName = "ID")
})
#ManyToOne
private PermissionContext permissionContext;
public static class PermissionPk implements Serializable{
public String id;
public PermissionContextPk permissionContext;
// ... eq and hashCode here ...
}
}
and what I get is:
org.hibernate.AssertionFailure: Unexpected nested component on the referenced entity when mapping a #MapsId: PermissionContext
Caused by: org.hibernate.AssertionFailure: org.hibernate.AssertionFailure: Unexpected nested component on the referenced entity when mapping a #MapsId: PermissionContext
does anybody know if this is a hibernate bug and I should post it on their issue tracking system (and pray that I would be able to update to given hibernate version) or is there something fundamentally wrong with my way of binding the entities?
I've checked it with the hibernate implementation on EAP 6.1 (4.2.0) as well as on wildfly (don't really know which one.)
Ok, so this is what I found so far :
Thanks fr my friend : https://hibernate.atlassian.net/browse/HHH-5764 which most probably is the reason for this behaviour.
And I found a workaround :
Permission.java:
#Entity
#IdClass(PermissionPk.class)
public class Permission{
#Id
private String id;
// for the next 3 fields there are no public acessors, so the public API of the class was not changed !
#Id
#Column(name = "PROJECT")
private String projectId;
#Id
#Column(name = "PERMISSIONCONTEXTTYPE")
private String permissionContextTypeId;
#Id
#Column(name = "PERMISSIONCONTEXT")
private String permissionContextId;
#JoinColumns ({
#JoinColumn (name = "PROJECT", referencedColumnName = "PROJECT", updatable = false, insertable = false),
#JoinColumn (name = "PERMISSIONCONTEXTTYPE", referencedColumnName = "PERMISSIONCONTEXTTYPE", updatable = false, insertable = false),
#JoinColumn (name = "PERMISSIONCONTEXT", referencedColumnName = "ID", updatable = false, insertable = false)
})
#ManyToOne
private PermissionContext permissionContext;
public static class PermissionPk implements Serializable{
// previously they where private as well, but removed public constructor for the sake of simplicity of the question - so no changes where necesary in public API of the class !
private String id;
private String projectId;
private String permissionContextTypeId;
private String permissionContextId;
public PermissionPk () {}
public PermissionPk (String aId, PermissionContextPk aPermissionContext) {
this.id = aId;
permissionContextId = aPermissionContext.id;
permissionContextTypeId = aPermissionContext.permissionContextType.id;
projectId = aPermissionContext.permissionContextType.project;
}
... eq and hashCode here ...
}
}
The good thing about this workaround is that it does not change the public API of the class in any way
(the only change was that I needed to make fields in Pk's of context and contexttype visible to the PermissionPk - they where private before with only a public constructor [but again simplified for the question]), nor did it change the jpql queries, and at the same time workaround is scalable (to any tier amount - as long as every even pk does not contain another pk), so if the bug will be resolved it will be easy to remove the workaround.
I would still gladly accept any comments on either my workaround or the question in itself.
Today I found another workaround :)
You can omit #IdClass entirely and use hibernate specific ability to create composite keys on the fly as apparently it is not affected by this bug.
The drawback here is that:
it is entirely Hibernate specific not covered by JPA at all.
you cannot do em.find(ClassName.class,new ClassPk(args...)) as there is no ClassPk at all.
But if you could use anything else than hibernate you could just as well use something without this bug - so probably 1 is not a problem really. and there is a possibility that you don't really need the em.find for this entity (or can live with creating it thru session or jpql query).

Categories

Resources