Hibernate #ManyToMany multiple entities - java

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.

Related

How to map many to many as nested embedded object

I have a dictionary structure, that we need to pass to frontend. For now it provides different values for one dropdown, and available values for the rest of them dropdowns depending on the choice of first one
#Entity
#Table(name = "CAR")
#Data
public class Car{
#Id
#Setter(NONE)
Long id;
String name;
#ManyToMany
#JoinTable(name = "CAR_WHEELS",
joinColumns = #JoinColumn(name = "CAR_FK"),
inverseJoinColumns = #JoinColumn(name = "WHEEL_FK"))
Set<Wheel> wheels;
//some other similar sets
}
Now along the available values we want to have a default value for those, so we're thinking about something like:
#Entity
#Table(name = "CAR")
#Data
public class Car{
#Id
#Setter(NONE)
Long id;
String name;
AvailableValues availableValues;
DefaultValues defaultValues;
}
and then
#Embeddable
class AvailableValues{
Set<Wheels> wheels;
//...
}
#Embeddable
class DefaultValues{
Wheel wheel;
//...
}
But I don't know how to deal with the mapping side of it. The defaultValues should be plain and simple, either through adding those values to the CAR table, or through one-to-one relation instead of using Embeddable, but can't come up with any idea for the collections inside embeddable object, that would use already present structure.
I am not sure if I understood the question correctly, but DB schema would be preserved if a relationship mapping was just moved into AvailableValues
#Embeddable
#Data
class AvailableValues {
#ManyToMany
#JoinTable(name = "CAR_WHEELS",
joinColumns = #JoinColumn(name = "CAR_FK"),
inverseJoinColumns = #JoinColumn(name = "WHEEL_FK"))
Set<Wheel> wheels;
}
Optionally, if for some reason you wanted to change that mapping in an encompassing entity, you can use #AssociationOverride
#Embedded
#AssociationOverride(name = "wheels",
joinTable = #JoinTable(name = "MY_CAR_WHEELS",
joinColumns = #JoinColumn(name = "MY_CAR_FK"),
inverseJoinColumns = #JoinColumn(name = "MY_WHEEL_FK")
)
)
AvailableValues availableValues;

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.

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

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

Optional Many to Many relationship in Hibernate

I want to create M:N relationship as below
Each user can have zero or many ebooks
Each ebook must belongs to one or many users
My mappings in Hibernate :
User.java
#Entity
#Table(name = "USERS")
public class User {
//...
#ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinTable(name = "USER_EBOOK", joinColumns = #JoinColumn(name = "USER_ID", nullable = false),
inverseJoinColumns = #JoinColumn(name = "EBOOK_ID", nullable = false))
private List<Ebook> listOfEbooks = new ArrayList<Ebook>();
//...
}
Ebook.java
#Entity
#Table(name="EBOOK")
public class Ebook {
//...
#ManyToMany(mappedBy = "listOfEbooks", fetch = FetchType.EAGER)
#NotFound(action = NotFoundAction.EXCEPTION)
private List<User> listOfEbookUsers = new ArrayList<User>();
//...
}
How can I add this additional constraints for example one or many - zero or many?, when I save only ebook object to database there is ebook that does not belongs to anyone.
See this question and the answer of the thread:
Mapping a bidirectional list with Hibernate
And see also this tutorial:
http://viralpatel.net/blogs/hibernate-many-to-many-annotation-mapping-tutorial/
The tutorial provides very good examples of how to implement a proper Many-to-Many mapping.

Hibernate ManyToMany does not work when using Inheritance

I just refactor a Project to use Hibernate (4.2.4.Final) with Inheritance. But I got trouble with ManyToMany annotation.
I have a base File Class like this:
#Entity
#Table(name = "file")
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
#DiscriminatorColumn(name = "descriminator", length = 25)
public abstract class File {
#Id
#Column(name = "id", unique = true, nullable = false, length = 256)
private String id;
}
and a special Inheritance class like this:
#Entity
#DiscriminatorValue("ISSUE_HISTORY_ATTACHMENT")
#Data
public class IssueHistoryAttachment extends File {
#ManyToOne(fetch = FetchType.LAZY)
#JoinTable(name = "issue_history_attachment", joinColumns = {
#JoinColumn(name = "attachment_id", nullable = false, unique = true) }, inverseJoinColumns = {
#JoinColumn(name = "issue_history_id", nullable = false)})
private IssueHistory history;
}
This IssueHistoryAttachment Class is also referenced in my IssueHistory Class.
#Entity
#Table(name = "issue_history")
#TableGenerator(name="tg", table="hibernate_sequences",pkColumnName="sequence_name", valueColumnName="sequence_next_hi_value", allocationSize=1)
public class IssueHistory implements java.io.Serializable {
#Id
#GeneratedValue(strategy = GenerationType.TABLE, generator = "tg")
#Column(name = "id", unique = true, nullable = false)
private int id;
// some other fields
#ManyToMany(fetch = FetchType.LAZY)
#JoinTable(name = "issue_history_attachment", joinColumns = {
#JoinColumn(name = "issue_history_id", nullable = false)
}, inverseJoinColumns = {
#JoinColumn(name = "attachment_id", nullable = false, unique = true)
})
private Set<IssueHistoryAttachment> attachments = new HashSet<IssueHistoryAttachment>();
}
When i now store a IssueHistory Instance with two Attachments, all this fields are correctly saved in my database.
I got 2 new entries in the file table, one new entry in the *issue_history* table and two correct entries in the relation table *issue_history_attachment*.
So at this points all thinks are looking fine. But when I try to read the Values Attachment Set in the IssueHistory Instance only contains one element instead of two like stored in the database.
Any suggestions how to solve this?
I just found the source of the Problem.
It was a missing/wrong equals method. :-)
I can't comment yes so I have to make an answer.
I see one problem in your code (or maybe I don't understand it):
In IssueHistory you are using #ManyToMany to IssueHistoryAttachment but in IssueHistoryAttachment you are using #ManyToOne.
In my opinion it is the reason of your problem.

Categories