JPA - #ManyToMany with duplicate reference to the same foreign key - java

I have 3 entities like this:
#Entity
public class A {
#Id
private String id;
}
#Entity
#IdClass(B.BPK.class)
public class B {
#Id
private String id;
#Id
#ManyToOne
private A a;
}
#Entity
public class C {
#Id
private int refOne;
#Id
private int refTwo;
#Id
private A a;
#ManyToMany
private Set<B> bs;
}
I have a question about the table generated for the relation #ManyToMany of the entity C. There are 5 columns generated: refOne, refTwo, a_id, b_id, b_a_id.
As you could see, a_id and b_a_id are foreign key on the same column and in my model, it's not possible that a_id is different from b_a_id.
Is there a solution to not duplicate the column?
EDIT:
I insist on this point, A is part of the id of C, and bs in C could be empty.
I think about several solutions but none of them are really satisfying:
Use a Converter that used the b_id and a_id to recover bs entities
I tried to manage the columns with #JoinTable like this:
JoinTable approach:
#JoinTable(name = "bs", joinColumns = {
#JoinColumn(name = "refOne", referencedColumnName = "refOne"),
#JoinColumn(name = "refTwo", referencedColumnName = "refTwo"),
#JoinColumn(name = "a_id", referencedColumnName = "a_id")
}, inverseJoinColumns = {
#JoinColumn(name = "b_id", referencedColumnName = "b_id"),
#JoinColumn(name = "b_a_id", referencedColumnName = "b_a_id", insertable = false, updatable = false)
But I face theses problems Mixing insertable and non insertable columns in a property is not allowed orParameter index out of range`
})

Since C has already a reference to A, you can fetch A.bs, without having to declare a collection of bs inside C. You need, however, to have a Set<B> in entity A.
Unless, of course, you can't change the mapping...

You could annotate private A a; with #JoinColumn that references b_a_id. There are also other annotations which let you you configure the mapping even more. Yet there is no issue with having 2 FK columns containing the same value.
Also you should always create your tables by hand. DOn't let it be generated in production.
For more info read the docs. Maybe you have to search for a more specific problem, but resources are out there

Related

Non-Redundant Hibernate List-Mapping (OneToMany)

In a database there are "pastries". A pastry has zero, one, or many allergens.
Hence I'm using a list in Pastry with #OneToMany relation. Now, with this way of managing the relations, we get redundant entries, as for each pastry a new entry with the same allergen is created:
Our current solution looks like this:
A mapping-table in between those two database tables.
How can I achieve a non-redundant solution with direct references to the allergen elements from a list in the Pastry class? I googled a lot but sadly couldn't find anything helpful.
Would look like this:
Regarding my comment on solution:
What you need is a called a JoinTable. Based on your schema definition this could work as a potential example:
#Data
#Entity(name = "Pastry")
#Table(name = "PASTRY")
public class Pastry {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String name;
#ManyToMany
#JoinTable(
name = "PASTRY_ALLERGEN",
foreignKey = #ForeignKey(name = "FK_PASTRY_ALLERGEN_PASTRY"),
inverseForeignKey = #ForeignKey(name = "FK_PASTRY_ALLERGEN_ALLERGEN"),
joinColumns = #JoinColumn(name = "pastry_id", referencedColumnName = "id"),
inverseJoinColumns = #JoinColumn(name = "allergen_id", referencedColumnName = "id")
)
private Set<Allergen> allergens = new HashSet<>();
}
For the Pastry association and:
#Data
#Entity(name = "Allergen")
#Table(name = "ALLERGEN")
public class Allergen {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String name;
}
For the Allergen association.
This mapping will in turn produce an intermediate table called PastryAllergens which will hold reference to the PK of the Pastry table and the PK of the Allergen table.

Decide which class is owning side in Hibernate

I have Role class, which is the main entity for User's roles and 2 FK in this table, which are pointed on two dictionaries independently: Privelege and Unit classes.
So it's many (Role) to one (Privelege/Unit) relationships as I take.
Qustions:
Is the Hibernate's mapping in my code correct?
Which class is the "owning side" in my case and why?
In which class should I write #JoinColumn and where mappedBy?
As I have read in other posts: "#JoinColumn annotation is maintained in the class which owns the foreign key."
But in Hibernate's doc I see that mappedBy is used on the owning side (see Example 163. Bidirectional #OneToOne).
4. What will happen if I remove some Role records? If I remove some records from dictionaries will it affect Role's records? Can I override this behavior to disable cascading?
I assume that my "Role" class is the owning side because it has FK pointed on 2 dictionaries. So it owns FK. Therefore I need to use #JoinColumn here as its owning side and mappedBy at two dictionaries, because it's mapped by owning side. Am I right.
Update: is the "owning side" synonym to "parent side"?
Role class
#Entity
#Table(name="ROLES_NEW", schema="MAPP")
public class Role implements GrantedAuthority {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "ID", nullable = false)
private Long id;
#Column(name = "ROLENAME")
private String roleName;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "UNIT_ID")
private Unit unit;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "PRIVELEGE_ID")
private Privelege privelege;
...
}
Privelege class
#Entity
#Table(name="PRIVELEGES", schema="MAPP")
public class Privelege /*implements GrantedAuthority*/ {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "ID", nullable = false)
private Long id;
#Column(name = "PRIVELEGENAME", length = 255, nullable = false)
private String privelegeName;
#Column(name = "descr", length = 255, nullable = false)
private String descr;
#OneToMany(mappedBy = "privelege")
Set<Role> role;
...
}
Unit class
#Entity
#Table(name="UNITS", schema="MAPP")
public class Unit {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "ID", nullable = false)
private Long id;
#Column(name = "UNITNAME")
private String unitname;
#OneToMany(mappedBy = "unit")
Set<Role> role;
...
}
Yes, the child entity owns the relationship, because that's the side the foreign key is on. This mapping is the most efficient in case of #ManyToOne.
Same thing would apply for many-to-many relationships mapped as two bidirectional #ManyToOne. It can also be done with #JoinTable annotation, but this approach is less efficient.
In case of #OneToOne although child (foreign key holder) owns the relationship, the best performance can be obtained, when using #MapsId and #JoinColumn on the parent side. More about that exception can be found here.
When it comes to mappedBy it's simple - it's used when the relationship is bidirectional and on the side #JoinColumn is not (child has #JoinColumn, parent - mappedBy).
I recommend Vlad Mihalcea's blog when it comes to optimal hibernate mapping: one-to-many, many-to-many.
P.S.: Prefer List to Set to map -to-many relationship (source).

Spring Data JPA - Delete many to many entries

I am attempting to remove entries from a many to many relationship using Spring Data JPA. One of the models is the owner of the relationship and I need to remove entries of the non-owner entity. These are the models:
Workflow entity
#Entity(name = "workflows")
public class Workflow {
#Id
#Column(name = "workflow_id", updatable = false, nullable = false)
#GeneratedValue(strategy = GenerationType.AUTO)
private UUID workflowId;
#ManyToMany(cascade = { CascadeType.PERSIST, CascadeType.MERGE })
#JoinTable(name = "workflow_data",
joinColumns = #JoinColumn(name = "workflow_id", referencedColumnName = "workflow_id"),
inverseJoinColumns = #JoinColumn(name = "data_upload_id", referencedColumnName = "data_upload_id"))
private Set<DataUpload> dataUploads = new HashSet<>();
// Setters and getters...
}
DataUpload entity
#Entity(name = "data_uploads")
public class DataUpload {
#Id
#Column(name = "data_upload_id")
private UUID dataUploadId;
#ManyToMany(cascade = { CascadeType.PERSIST, CascadeType.MERGE }, mappedBy = "dataUploads")
private Set<Workflow> workflows = new HashSet<>();
// Setters and getters...
}
DataUpload repository
#Repository
public interface DataUploadsRepository extends JpaRepository<DataUpload, UUID> {
#Transactional
void delete(DataUpload dataUpload);
Optional<DataUpload> findByDataUploadId(UUID dataUploadId);
}
To delete data uploads, I am trying to execute a couple of query methods of the repository:
First version
dataUploadsRepository.deleteAll(workflow.getDataUploads());
Second version
workflow.getDataUploads().stream()
.map(DataUpload::getDataUploadId)
.map(dataUploadsRepository::findByDataUploadId)
.filter(Optional::isPresent)
.map(Optional::get)
.forEach(dataUploadsRepository::delete);
Problem is that Spring Data JPA is not removing DataUploads nor entries of the association table workflow_data.
How can I tell Spring Data to remove from both data_uploads and workflow_data (association table)?
I would appreciate any help.
I found the solution for this problem. Basically, both entities (in my case) need to be the owner of the relationship and the data from the association table must be deleted first.
Workflow entity (relationship owner)
#Entity(name = "workflows")
public class Workflow {
#Id
#Column(name = "workflow_id", updatable = false, nullable = false)
#GeneratedValue(strategy = GenerationType.AUTO)
private UUID workflowId;
#ManyToMany(cascade = { CascadeType.ALL })
#JoinTable(name = "workflow_data",
joinColumns = #JoinColumn(name = "workflow_id", referencedColumnName = "workflow_id"),
inverseJoinColumns = #JoinColumn(name = "data_upload_id", referencedColumnName = "data_upload_id"))
private Set<DataUpload> dataUploads = new HashSet<>();
// Setters and getters...
}
DataUpload entity (relationship owner)
#Entity(name = "data_uploads")
public class DataUpload {
#Id
#Column(name = "data_upload_id")
private UUID dataUploadId;
#ManyToMany
#JoinTable(name = "workflow_data",
joinColumns = #JoinColumn(name = "data_upload_id", referencedColumnName = "data_upload_id"),
inverseJoinColumns = #JoinColumn(name = "workflow_id", referencedColumnName = "workflow_id"))
private Set<Workflow> workflows = new HashSet<>();
// Setters and getters...
}
Notice that Workflow has ALL as cascade type, since (based on the logic I need), I want Spring Data JPA to remove, merge, refresh, persist and detach DataUploads when modifying workflows. On the other hand, DataUpload does not have cascade type, as I do not want Workflow instances (and records) to be affected due to DataUploads deletions.
In order to successfully delete DataUploads, the associate data should be deleted first:
public void deleteDataUploads(Workflow workflow) {
for (Iterator<DataUpload> dataUploadIterator = workflow.getDataUploads().iterator(); dataUploadIterator.hasNext();) {
DataUpload dataUploadEntry = dataUploadIterator.next();
dataUploadIterator.remove();
dataUploadsRepository.delete(dataUploadEntry);
}
}
dataUploadIterator.remove() deletes records from the association table (workflow_data) and then the DataUpload is deleted with dataUploadRepository.delete(dataUploadEntry);.
It has been a while since I have to fix these kind of mappings so I'm not going to give you a code fix, instead maybe give you another perspective.
First some questions like, do you really need a many to many? does it make sense that any of those entities exist without the other one? Can a DataUpload exist by itself?
In these mappings you are supposed to unassign the relationships on both sides, and keep in mind that you could always execute a query to remove the actual values (a query against the entity and the intermediate as well)
A couple of links that I hope can be useful to you, they explain the mappings best practices and different ways to do the deletion.
Delete Many, Delete Many to Many, Best way to use many to many.

How to map Entity A to Entity B twice in a one to one relation with JPA?

So nothing I tried seems to work. I would like to have something like this:
class A {
B foo;
B bar;
}
class B {
A baz;
}
What I tried in class A is as follows:
#OneToOne(targetEntity = B.class)
#JoinColumn(name = "foo_id")
#Cascade(CascadeType.ALL)
public B getFoo() {
return foo;
}
#OneToOne(targetEntity = B.class)
#JoinColumn(name = "bar_id")
#Cascade(CascadeType.ALL)
public B getBar() {
return bar;
}
which does not seem to work. I always end up where foo_id and bar_id is same for a reason I do not understand.
So when I inspect table "A" in my DB for row with id 1, I would like to have:
foo_id = 1
bar_id = 2
and in Table B, I should have 2 entities with id 1 and 2, where both have baz_id = 1;
Is baz_id intended to be a FK back to A? Because I think the database mapping to model is wrong in that case. You've already established the FK relationship from the PK of B to either A.foo_id or A.bar_id.
Also be careful with your cascading rules on a relationship like this. SQL Server will reject two FKs to the same table unless the DB action for cascading is "no action".
I do happen to know that what you're trying to do is possible in JPA, since I just recently did it on an entity myself:
#OneToOne(cascade = CascadeType.ALL, orphanRemoval = true)
#Cascade(org.hibernate.annotations.CascadeType.DELETE_ORPHAN)
#JoinColumn(name = "portal_logo_id", referencedColumnName = "id", nullable = true)
private PortalResourceModel logo;
#OneToOne(cascade = CascadeType.ALL, orphanRemoval = true)
#Cascade(org.hibernate.annotations.CascadeType.DELETE_ORPHAN)
#JoinColumn(name = "portal_favicon_id", referencedColumnName = "id", nullable = true)
private PortalResourceModel favicon;
I also don't have a mapping in PortalResourceModel for logo or favicon, because that side of the relationship doesn't know how it is being used. And I can't have a generic mapping from multiple relationships on the owning side to a single relationship on the mappedBy side.

Hibernate #ManyToMany multiple entities

I have the following scenario:
Base Domain class:
#MappedSuperclass
public class BaseDomain {
#Id
protected UUID id;
}
Media Object class:
#Entity
public class MediaObject extends BaseDomain {
#ManyToMany
#JoinTable(
joinColumns = {
#JoinColumn(name = "BaseDomain_id", referencedColumnName = "id"
}
inverseJoinColumns = {
#JoinColumn(name = "Media_id", referencedColumnName = "id")
}
private List<BaseDomain> holders;
}
"Holder" A:
#Entity
public class A extends BaseDomain {
#ManyToMany
private List<MediaObject> media;
}
"Holder" B:
#Entity
public class B extends BaseDomain {
#ManyToMany
private List<MediaObject> media;
}
What I want to achieve is, to store a MediaObject and multiple entities may "hold" this object. My approach would be a using a JoinTable that stores the relation between the MediaObject and an arbitrary BaseDomain object (as above). The issue I'm facing is that the persistence provider (in my case Hibernate) would not be able to decide which actual table to join.
I'm thinking about using a unidirectional #OneToMany which is possible in JPA 2.1.
However, I want to ask, if there are some kind of best practices to approach such a situation.
Following snippet is used by me in production environement, it implements ManyToMany assiciation mapping for Hibernate.
#ManyToMany(fetch = FetchType.LAZY)
#JoinTable(name = "printed_mails_logo",
joinColumns = {
#JoinColumn(name = "mails_id", nullable = false, updatable = false)
},
inverseJoinColumns = {
#JoinColumn(name = "logo_id", nullable = false, updatable = false) })
private Set<Logo> printedLogos;
printer_mails_logo is additinal associative table in database.
#JoinColumn(name='x') is the actual name of column in associative table.
This works well for me. I can fetch without no problem all logos that has been printed already.

Categories