Optional Many to Many relationship in Hibernate - java

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.

Related

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 can I make Bridge table from 2 Model classes using hibernate annotation configuration

How to make Bridge table using annotation using Hibernate/JPA configuration.
1: BookModel
2: UserModel
now I have to create a bridge table by these two by fields
book_id and user_id
You are trying to implement Many to Many relationship between your entities. So for this, If you have list of Books in User model, then you can annotate the list as following:
public class UserModel {
#ManyToMany(cascade = CascadeType.REMOVE)
#JoinTable(name = "book_user_table", joinColumns = { #JoinColumn(name = "book_id") }, inverseJoinColumns = { #JoinColumn(name = "user_id") })
private List<BookModel> books;
//Getters and setters
}
and in BookModel, if you have list of users, then you need to use #mappedBy() annotation like following:
#ManyToMany(mappedBy = "books")
private List<UserModel> users;
This will generate the 3rd table, which will have the name book_user_table, with your required columns. See this for detailed explanation: https://dzone.com/tutorials/java/hibernate/hibernate-example/hibernate-mapping-many-to-many-using-annotations-1.html
From what I understand from your quesrtion is, you want to know how to map book and user so that there is many to many association between these entities.
If so, you need to specify #ManyToMany on both the associations and make one of them as inverse and the other end with #JoinTable. An example mapping here. Snippet below.
#ManyToMany(cascade = { CascadeType.ALL })
#JoinTable(
name = "Employee_Project",
joinColumns = { #JoinColumn(name = "employee_id") },
inverseJoinColumns = { #JoinColumn(name = "project_id") }
)
Set<Project> projects = new HashSet<>();
On the inverse end,
#ManyToMany(mappedBy = "projects")
private Set<Employee> employees;

Eclipselink merge does not insert a new row in many-to-many relationship

I have a many-to-many relationship between user and role entities. when I try to merge a role by adding new user objects to that, after merging i expect a new row in the joined table (in sql server database) to be inserted but nothing happens.
I guess the problem is the direction of the relationship which the owner is User entity now and should be switched to Role. But if I change it then my spring security wont work. Is there any other way to solve this? except changing many-to-many sides?
Thanks in advance.
User class
#ManyToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JoinTable(
name = "SEC_USER_ROLE",
joinColumns = {
#JoinColumn(name = "SEC_USER_ID", referencedColumnName = "SEC_USERS")},
inverseJoinColumns = {
#JoinColumn(name = "SEC_ROLE_ID", referencedColumnName = "SEC_ROLES")})
private List<Role> roles;
--
Role class
#ManyToMany(mappedBy = "roles", fetch = FetchType.EAGER, cascade = CascadeType.ALL)
private List<User> users;
And here is my merge function
#Override
#Transactional(readOnly = false, propagation = Propagation.REQUIRED)
public T merge(T o) {
o = em.merge(o);
em.flush();
return o;
}

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.

Can an entity have more than one many-to-many relations?

I have a table that has two different many-to-many relations to two different tables. Let's say I have User <---> UserRole <--> Role and User <--> UserGroups <--> Groups. Since I am new to hibernate and database mapping I was wondering if having my User entity have attributes roles and groups in it, both with #ManytoMany annotations is good practice and acceptable?
i.e.:
#Entity(name = "User")
#Table(name = "USER")
public class User {
.... /* Obviously Id would go here and all other attributes */
#ManyToMany(cascade = CascadeType.ALL)
#JoinTable(name = "UserRole", joinColumns = { #JoinColumn(name="USER_ID") },
inverseJoinColumns = { #JoinColumn(name = "ROLE_ID") } )
private Set<Role> roles = new HashSet<Role>();
#ManyToMany(cascade = CascadeType.ALL)
#JoinTable(name = "UserGroup", joinColumns = { #JoinColumn(name="USER_ID") },
inverseJoinColumns = { #JoinColumn(name = "GROUP_ID") } )
private Set<Group> groups = new HashSet<Group>();
Sure; lots of types have multiple many-to-many.
I think minimizing many-to-many relationships is a good idea, but it's a natural structure, and is found all over the place.

Categories