Hibernate ManyToMany Method threw 'org.hibernate.LazyInitializationException' exception - java

I have ManyToMany relationships in Hibernate
Project.class
#ManyToMany
#JoinTable(
name = "user_projects",
joinColumns = { #JoinColumn(name = "project_id")},
inverseJoinColumns = { #JoinColumn(name = "user_id")}
)
private Set<User> projectUsers = new HashSet<>();
User.class
#ManyToMany(fetch = FetchType.EAGER,
mappedBy="projectUsers",
cascade = CascadeType.ALL)
private Set<Project> userProjects = new HashSet<>();
And when I get current user thru
#AuthenticationPrincipal User user
I have Method threw 'org.hibernate.LazyInitializationException' exception when user.getUserProjects();

It's because when using #ManyToMany, first all Projects of user are fetched, then all Users of fetched Projects and so on... This cyclic dependency causes the error, because there is eager fetching missing on Project side.
The solution would be to use:
#ManyToMany(fetch = FetchType.EAGER)
#JoinTable(
name = "user_projects",
joinColumns = { #JoinColumn(name = "project_id")},
inverseJoinColumns = { #JoinColumn(name = "user_id")}
)
private Set<User> projectUsers = new HashSet<>();
Although I do not recommend this solution, because of performance issues (and possible side effects). Better solution would be to have an entity for join table.

Try to use #Transactional maybe can solved it because
the #Transactional annotation on the getFavorites() method indicates that the session will be closed at the end of this method

Related

Assigning a role to a user without FetchType.EAGER

I have a #ManyToMany table enter image
The role table contains the roles ROLE_USER, ROLE_ADMIN, and so on.
Below I show how I create a relationship between the user and role tables.
Role.class
#ManyToMany(mappedBy = "roleList", cascade = {CascadeType.PERSIST, CascadeType.MERGE}, fetch = FetchType.EAGER)
private List<User> userList = new ArrayList<>();
public void addPerson(User user) {
userList.add( user );
user.getRoleList().add( this );
}
User.class
#ManyToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE}, fetch = FetchType.LAZY)
#JoinTable(name = "user_role", joinColumns = #JoinColumn(name = "user_id"), inverseJoinColumns = #JoinColumn(name = "role_id"))
private List<RoleUser> roleUserList = new ArrayList<>();
public void addRole(Role role) {
roleList.add(role);
role.getUserList().add( this );
}
Suppose I already have a list of roles in the role table. Now I want to create a user and assign it one of these roles.
In this case, I'm trying to do the following:
I specify FetchType.EAGER (This is not desirable for me. Otherwise it does not work), then I write the following in the controller:
Role role = serviceJpa.findRoleByRoleName("ROLE_USER");
role.addUser(user);
serviceJpa.saveRole(role);
If we take a look, the user has been added perfectly and the role has been successfully assigned to that user.
But as I said earlier, FetchType.EAGER is not desirable for me, because I do not want to get a million users when accessing the role table.
But if I use FetchType.LAZY then an exception is thrown:
Message Request processing failed; nested exception is org.hibernate.LazyInitializationException: failed to lazily initialize
a collection of role: com.testmany.entity.Role.userList, could
not initialize proxy - no Session
Here I immediately have a question - maybe I am not adding a user correctly and assigning a role to him incorrectly?
If I do it in another way, for example this:
User user = new User("Иван");
Role role = serviceJpa.findRoleByRoleName("ROLE_USER");
user.addRole(role);
serviceJpa.saveUser(user);
then another exception occurs:
Message Request processing failed; nested exception is org.springframework.dao.InvalidDataAccessApiUsageException: detached
entity passed to persist: com.testmany.shop.entity.Role; nested
exception is org.hibernate.PersistentObjectException: detached entity
passed to persist: com.testmany.entity.Role
What am I doing wrong? How do I add a user with FetchType.LAZY, that is, without FetchType.EAGER. What is the correct way to add a user and assign him a role? What am I doing wrong. I have reviewed and re-read a bunch of info, hibernate documentation, and nothing helps. How are you doing, show?
In your user model set lazy fetch as follows:
#ManyToMany(fetch = FetchType.LAZY)
#JoinTable(name = "app_user_role", joinColumns = #JoinColumn(name = "userid"), inverseJoinColumns = #JoinColumn(name = "roleid"))
private List<Role> roles;
To compensate lazy loading, initialize it somewhere in your controller like below (just to give you hint):
#ModelAttribute("roles")
public List<Role> initializeRoles() {
return (List<Role>) roleJPARepository.findAll();
}
Then, somewhere in your code (say a Service), set the role to the user like below:
entity.setRoles(user.getRoles());
For a full blown example where you set/edit the role to user through UI, is here in my GitHub Repo - https://github.com/ajkr195/springbootrocks
This is pretty much close to what you are looking for.
#ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
#JoinTable(name = "user_role",
joinColumns = {#JoinColumn(name = "user_id")},
inverseJoinColumns = {#JoinColumn(name = "role_id")}
)
private List<RoleUser> roleUserList;
you can try this it may be helpful

Hibernate many to many cascade type issue

I am currently using Hibernate in a project and I am having trouble setting up a Many to Many relationship. I think my issue is related to the Cascade types.
The scenario is we have an advert and each advert can have many tags but each tag can be related to many adverts. The ManyToMany annotation of the advert entity is:
#ManyToMany(fetch = FetchType.LAZY, cascade = { CascadeType.MERGE, CascadeType.DETACH, CascadeType.REFRESH })
#JoinTable(name = "adMediaAdvertiserTag",
joinColumns = { #JoinColumn(name = "adMediaId") },
inverseJoinColumns = { #JoinColumn(name = "advertiserTagId") })
And the ManyToMany mapping on the tag entity is:
#ManyToMany(mappedBy = "advertiserTags", fetch = FetchType.LAZY, cascade = { CascadeType.PERSIST, CascadeType.MERGE })
I am seeing two different errors depending on the configuration of the cascade types and if the tag already exists in the database.
If the cascade type on the advert doesn't include PERSIST and the tag doesn't already exist then we get this exception:
TransientObjectException: object references an unsaved transient instance
And if the cascade type on the advert does include PERSIST and the tag does already exist we get:
PersistentObjectException: detached entity passed to persist
The only way I can get this working is not have PERSIST as a cascade type but loop through each tag and save them to the session.
Is this the correct approach? Is there a way that hibernate could automatically handle this for me?
Let me know if this isn't clear or you need any more information.
UPDATE
I am saving the objects using a service that uses the save method the CrudRepository object of the springframework. There is a lot of code so I can't really post it all.
I am currently not calling any merge or persist method for any object I am trying to save. I was under the impression Hibernate would handle that sort of thing for me.
Can you please add the code you are using to persist your Advert and Tag objects. I tested with the following code (which I believe similar to yours) and it works:
User user = new User();
Role role = new Role();
role.setId("TEST_ROLE");
user.getRoles().add(role);
entityManager.persist(user);
User user2 = new User();
user2.getRoles().add(role);
entityManager.persist(user2);
System.out.println(user2.getId());
in User I have:
#ManyToMany(fetch = FetchType.EAGER, cascade = CascadeType.PERSIST)
#JoinTable(name = "user_roles",
joinColumns = {#JoinColumn(name = "user_id", nullable = false, updatable = true)},
inverseJoinColumns = {#JoinColumn(name = "role_id", nullable = false, updatable = true)})
protected List<Role> roles = new ArrayList<>();
and in Role I have:
#ManyToMany(mappedBy="roles")
protected List<User> users;

Create one to one relationship with join table in hibernate

I have a many to one relationship from the class client to estimate.
#OneToMany( mappedBy = "fkIdClientEstimate", cascade = CascadeType.ALL)
private List<Estimate> estimateList = new ArrayList<>();
But is it possible to create a one to one relationship from estimate to client. I tried something like this but it does not enter any values into the database ?
#OneToOne( cascade = CascadeType.ALL)
#JoinTable(
name = "tbl_estimate_client",
joinColumns = #JoinColumn( name="id_estimate"),
inverseJoinColumns = #JoinColumn( name="id_client"))
private Client client ;
I also tried it with private Client client = new clientBut it creates a new client into the database instead.

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;
}

ManyToMany remove problem

I have a table: DocumentType:
#ManyToMany(cascade = {CascadeType.REFRESH, CascadeType.MERGE, CascadeType.PERSIST}, fetch = FetchType.LAZY)
#JoinTable(
name = "document_type_property_type",
joinColumns = #JoinColumn(name = "document_type"),
inverseJoinColumns = #JoinColumn(name = "property_type")
)
#Cascade({org.hibernate.annotations.CascadeType.DELETE_ORPHAN})
#ForeignKey(name = "FK_DOCUMENT_TYPE_PROPERTY_TYPE__DOCUMENT_TYPE", inverseName = "FK_DOCUMENT_TYPE_PROPERTY_TYPE__PROPERTY_TYPE")
#Sort(type = SortType.NATURAL)
private SortedSet<PropertyType> propertyTypes = new TreeSet<PropertyType>();
and PropertyType:
#ManyToMany(cascade = {CascadeType.REFRESH, CascadeType.MERGE, CascadeType.PERSIST}, fetch = FetchType.LAZY)
#JoinTable(
name = "document_type_property_type",
joinColumns = #JoinColumn(name = "property_type"),
inverseJoinColumns = #JoinColumn(name = "document_type")
)
#Cascade({org.hibernate.annotations.CascadeType.DELETE_ORPHAN})
#Sort(type = SortType.NATURAL)
protected SortedSet<DocumentType> documentTypes = new TreeSet<DocumentType>();
As you see the bridge table for ManyToMany is: document_type_property_type.
I do not understand why if i remove a property type from a doc type it not only deletes it from bridge table (as i want/expect) but also deletes it from property type itself (which i want to avoid!).
Can you give me a work-around?
Thanks.
Edit: code for deleting a property type - doc type relation:
public void removePropertyType(final PropertyType propertyType) {
super.performDAudit(propertyType);
final DocumentType currentInstance = getInstance();
currentInstance.getPropertyTypes().remove(propertyType);
getEntityManager().persist(propertyType);
FacesMessages.instance().add(StatusMessage.Severity.INFO, "Property Type was succesfully removed from this document type");
}
I notice that you have the cascade type set to DELETE_ORPHAN on both sides of the relationship. I think you may either have to set it on one side or none. I am not sure that DELETE_ORPHAN is relevant in your scenario.
As I understand it, only one side of the relationship actually "owns" the relationship. That is the side that should manage all cascades and so on and the inverse side should do nothing.

Categories