Update record in many to many relation by Hibernate - java

I have two classes, Document class and Role class and a #ManyToMany relationship between them.
I tried to get a document then i get all that roles and fill the set of the document with them.
Now when i update the set of the document (insert new roles or remove existing roles) and then update the document object by session.update(doc), the Hibernate does not do any insert or delete statement into the #ManyToMany table, it only updates the document and the roles records.
Note: the lazy attribute is "lazy = true" in both tables.

The problem was i am using the bidirectional #ManyToMany relation (that mean the document have set of role and the role have set of document) and in this way when i create a document and adding some roles to it, even that i have to add this document to each role in the set.
when i use the unidirectional #ManyToMany relation(that mean just one side have a set of the other class) i just fill the document set with some roles.
this article is so helpful
https://howtoprogramwithjava.com/hibernate-manytomany-unidirectional-bidirectional/

Related

Spring Data JPA join 2 tables

I have 2 tables in MySQL database: user and user_additional_details with columns described below.
User
id (auto increment)
userId (unique)
first name
last name
phone
email
User Additional Details
id (auto increment)
userId (matches userId in User)
personalPhone
personalEmail
Table user_additional_details contains 0 or 1 row for each userId in user table.
However, database does not have a foreign key constraint defined. Ideally, columns from user_additional_details should have been added as nullable columns in user table, but that was not done for some unknown reason. Now I need to define the entity for following query.
select user.userId, user.phone, user_a_d.personalPhone
from user
join user_additional_details as user_a_d
on user.userId = user_additional_details.userId
I tried defining JPA entities for the tables, but not able to figure out how to create an entity that uses columns from different tables.
It seems like the SecondaryTable annotation is what you are looking for
Specifies a secondary table for the annotated entity class. Specifying
one or more secondary tables indicates that the data for the entity
class is stored across multiple tables.
Here you find a detailed example of how to use it - http://www.thejavageek.com/2014/09/18/jpa-secondarytable-annotation-example/
Create UserEntity (with all the columns from User table) and UserAdditionalDetailsEntity(with all the columns from user_additional_details table). I assume you are aware how to create JPA entities and map them to database table.
I hope you would have create entity manager factory object in your spring configuration file. With the help of that create entity manager object .
Once EntutyManager Object is created:
Query q= em.createQuery("select user.userId, user.phone, userDetails.personalPhone
from UserEntity user
join UserAdditionalDetailsEntity as userDetails
on user.userId = userDetails.userId");
List<Object[]> resultList= q.getResultList();
Once you get resultList you can iterate over the list of object array and get data.
Each index of the resultList will contain the object array representing one row
Keep in mind that field name mentioned in query should be same as the one mentioned in your JPA Entites.

Hibernate Envers: Initializing ListProxy of related objects

I've two entities: User and UserGroup. Relation between them is #ManyToMany and I'm using envers for auditing these entities, class level #Audited annotation is placed on both of them. However, when I try to execute this query:
AuditReader reader = AuditReaderFactory.get(em);
AuditQuery query = reader.createQuery().forRevisionsOfEntity(User.class, false, true);
Returnted user entities have "org.hibernate.envers.entities.mapper.relation.lazy.proxy.ListProxy" collections of user groups with size equal to zero. Calling size() method on these list proxies doesn't initialize them. Any help will be appreciated.
The problem was the following: I started auditing of entities when there already were users and usergroups in the database. Let's say I was modifying some user's groups. This modification caused in addition of corresponding rows in User_AUD and User_UserGroup_AUD tables, but UserGroup_AUD table was still empty. Later when I was querying for revisions of User entity, it wasn't able to find related UserGroup entities since there was no record in UserGroup_AUD table about those user groups.

JPA EntityExistsException

The problem that I'm currently facing is the following:
Exception in thread "main" javax.persistence.EntityExistsException: a different object with the same identifier value was already associated with the session: [de.entities.Genre#28]
at org.hibernate.ejb.AbstractEntityManagerImpl.convert(AbstractEntityManagerImpl.java:1359)
at org.hibernate.ejb.AbstractEntityManagerImpl.convert(AbstractEntityManagerImpl.java:1310)
at org.hibernate.ejb.AbstractEntityManagerImpl.convert(AbstractEntityManagerImpl.java:1316)
at org.hibernate.ejb.AbstractEntityManagerImpl.persist(AbstractEntityManagerImpl.java:881)
at de.model.DatabaseBuilder.importData(DatabaseBuilder.java:87)
at de.main.Main.main(Main.java:55)
So the Exception tells me that I want to insert two different objects with the same primary key id.
All my datas I want to insert into a database using JPA are coming from an XML file. I parse this file with a SAXParser. Of course there are many genre entries with the id 28 because many movies have the same genre.
If I use auto-generated ids, the data wont be correct anymore because all id's are correctly given by the XML file.
How can I solve this problem? Why isn't JPA not just ignoring the fact, that this object is already present in the database and just inserting the ids of the movies and the genres in my m:n table?
Either you are directly calling persist on your Genre instances, or you are calling persist on movie and the movie->Genre mapping has cascade persist. JPA requires providers to throw an exception when persist is called on a detached entity, since persist means you want the provider to insert it, and it is detached because it already exists. So you will get an exception. Sounds like your parser is not able to tell that the Genre entities are the same instance, and so is creating multiple instances of the same data. If this cannot be fixed, you might try using merge instead. Merge will check first if the entity instance is new or detached. If it is new, it will insert, while if it is detached, it will retrieve the data and update it in the database.
Other options would be to make sure you are not calling persist on detached Genre instances. Remove the cascade persist settings on relationships, and ensure that you manually call persist or merge on new Genre instances rather than rely on cascade persist settings.
You just cannot insert two elements with the same identifier. That id must be unique, if not, the database cannot identify the object (oh the irony), and that's why it gives you the exception.
Create a new field called movieId (or whatever you want to name it) and store the id from the xml in that field, but not in the database identifier "id".
You just cannot insert two objects with the same identifier(id).
It throws EntityExistsException,
if the entity already exists.
(If the entity already exists, the EntityExistsException may be thrown when the persist operation is invoked, or the EntityExistsException or another PersistenceException may be thrown at flush or commit time.)
Solution
You can create a new field for movieId, which will store the id of the movie from the XML. but this will create unnecessary redundancy of the data. For the Genre, you should create a new table where you will define movie_genre mapping. You can use one to many mapping for this purpose.
Your mapping of your genres seems to have a wrong relation. From your use case, I think it should be a many-to-many relation.
A valid mapping for your genres should look like:
#ManyToMany(fetch = FetchType.EAGER, cascade = { CascadeType.MERGE, CascadeType.REFRESH, })
#JoinTable(name = "MovieGenre", joinColumns = { #JoinColumn(name = "Movie_Id") }, inverseJoinColumns = { #JoinColumn(name = "Genre_Id") })
public Set<Genre> getGenres() {
return this.genres;
}

Hibernate: Many To Many Relation with attributes: correct configuration with Annotations

I have a problem with Hibernate (Thanks to Thomas now the problem is more legible).
In Short:
How to configure a ManyToMany association with Hibernate when the relationship has an attribute and we need save, delete and update in cascade?
In Large:
Imagine the following DataBase:
User Profile
M______N
|
attribute
There are 3 tables here:
"User", "Profile" and "User_Profile".
Now imagine User_Profile has 1 attribute for the relation (and obviously the keys).
Ok, now this is translating to Hibernate by the following:
User:
// The relation is Many to Many, but considering that it has an attribute, this is OneToMany with the ManyMany RelationShip
#OneToMany(mappedBy="user", targetEntity=UserProfile.class)
#Cascade({CascadeType.SAVE_UPDATE, CascadeType.DELETE})
#LazyCollection(LazyCollectionOption.TRUE)
private Set<UserProfile> userProfile = new HashSet<UserProfile>();
UserProfile:
#Id
#ManyToOne(targetEntity=User.class,fetch=FetchType.LAZY)
#Cascade({CascadeType.LOCK})
#JoinColumns({ #JoinColumn(name="...", referencedColumnName="...") })
private User user;
#Id
#ManyToOne(targetEntity=Profile.class,fetch=FetchType.LAZY)
#Cascade({CascadeType.LOCK})
#JoinColumns({ #JoinColumn(name="...", referencedColumnName="...") })
private Profile profile;
So, I think the configuration is correct, and the save, independently if the User has Profile childrens save all of them. The problem is when I try to update the user:
getHibernateTemplate().getSessionFactory().getCurrentSession().clear();
getHibernateTemplate().saveOrUpdate( user );
getHibernateTemplate().getSessionFactory().getCurrentSession().flush();
Hibernate don´t delete the Profile relation if there is an empty set of Profile childrens. Only add the profiles (override the old)... That´s rare... What´s the problem?
Thank you in advance
All you actually do is remove the relation and thus theres no DELETE to cascade, that's why nothing gets deleted.
Try adding the Hibernate cascade type DELETE_ORPHAN (using the #Cascade annotation) to make Hibernate delete entities that are not referenced anymore.
Additionally, I'd not remove the Mini entities alone. If there's no relation, i.e. the set of Minis is empty, it normally makes no sense to keep the SuperMini entities that now represent an empty collection (in rare cases it might make sense, just want you to think about whether you need them or not).
Edit:
Note that with DELETE_ORPHAN you should reuse the set, otherwise all the relations might be deleted and reinserted.
Basically Hibernate would then see the set being changed and would issue a delete for the "old" set and a reinsert for the "new" set. This could be wanted but in case you only want an update, i.e. only delete the entities that are not in the set anymore, you should do the following:
clear the set
add the "new" set to the reused and now cleared set using addAll(...)
This should trigger the update (and deletion of orphans) only.

How to batch delete using bulkUpdate

I have a common User / Role setup, with a user_role join table. I'm trying to use Spring's HibernateTemplate to mass delete all locked users like this:
getHibernateTemplate().bulkUpdate("delete from User where locked=?", true);
If the user being deleted does not have any roles (no record in the user_role table), then everything goes fine; however if the user does have a role record, I'm getting the following error:
integrity constraint violated - child
record found
Roles are defined in User.java like this:
#ManyToMany(fetch = FetchType.EAGER)
#JoinTable(name = "user_role", joinColumns = { #JoinColumn(name = "user_id") }, inverseJoinColumns = #JoinColumn(name = "role_id"))
private Set<Role> roles = new HashSet<Role>();
So how can I batch delete users even if a user has child records? Thanks!
Bulk delete operations are not cascaded to related entities as per the JPA specification:
4.10 Bulk Update and Delete Operations
Bulk update and delete operations
apply to entities of a single entity
class (together with its subclasses,
if any). Only one entity abstract
schema type may be specified in the
FROM or UPDATE clause.
...
A delete operation only applies to
entities of the specified class and
its subclasses. It does not cascade
to related entities.
However, I'd expect the JPA provider to deal with join tables. Sadly, Hibernate doesn't and this is logged in HHH-1917. I'm afraid you'll have to fall back on native SQL to clean up the join table yourself or to use cascading foreign keys in the schema.
Application-level cascading (cascading through hibernate annotations or JPA annotations) only work if the actual entity is actually loaded from the db. When you use the hibernate template with HQL, you'll notice that the entities are not loaded, and the HQL is directly converted to SQL to be executed.
If you want to batch delete you have to use an HQL query to delete all relevant tables (ie roles) before deleting the parent table data.
I'm not entirely sure because it's hard for me to recreate this problem, but I think you might need to add a cascade to your #ManyToMany
#ManyToMany(cascade = CascadeType.ALL)
Since you want to bulk delete something that have a ManyToMany related items, you first have to delete the relation (in the join table), or do a loop and for each item, delete manually (insane and too much heavy).
So, since JPQL does not allow to do it, a possible way is to make a native SQL query for deleting the id you want in the related table, and then, do the bulk delete.

Categories