cascade type save update in Hibernate - java

I am using hibernate with JPA annotations for relationship mapping.
I have three entities in my code User Group & User_Group
User & Group are in a ManyToMany relationship.
User_Group is a kinda bridge table but with some additional fields. So here is the modified mapping code.
User
#Entity
#Table(name = "USERS")
public class User {
#OneToMany(mappedBy = "user")
private Set<UserGroup> userGroups
}
Group
#Entity
#Table(name = "GROUPS")
public class Group {
#OneToMany(mappedBy = "group")
private Set<UserGroup> userGroups
}
UserGroup
#Entity
#Table(name = "USERS_GROUPS")
public class UserGroup {
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "USER_ID")
private User user;
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "GROUP_ID")
private Group group;
}
When I set the user & group object to the usergroup & save it.
User user = new User("tommy", "ymmot", "tommy#gmail.com");
Group group = new Group("Coders");
UserGroup userGroup = new UserGroup();
userGroup.setGroup(group);
userGroup.setUser(user);
userGroup.setActivated(true);
userGroup.setRegisteredDate(new Date());
session.save(userGroup);
Things work fine. With CascadeType.ALL the group object & user object are updated too. But when I delete the userGroup object. The child object are deleted too.
Deletion of child objects is a strict no no.
There is no CascadeType.SAVE-UPDATE in JPA, which just does save or update but no delete. How do I achieve this.
If I remove the CascadeType.ALL from the mapping the child objects don't get updated & I need them to be updated.

SAVE_UPDATE is for save(), update(), and saveOrUpdate(), which are 3 Hibernate-proprietary methods. JPA only has persist() and merge(). So, if you want to use cascading on Hibernate-proprietary methods, you'll need to use Hibernate-proprietary annotations. In this case, Cascade.
Or you could stop using the Hibernate Session, and use the standard JPA API instead.

CascadeType.ALL includes CascadeType.REMOVE too.
The solution is to use all CascadeType.* you need except CascadeType.REMOVE, like so:
#ManyToOne(cascade = {CascadeType.PERSIST, CascadeType.REFRESH, CascadeType.MERGE}))
in your UserGroup definitions.

It's almost always a code smell when propagating from child to parent entity, it should be the other way round.
From Cascading best practices:
Cascading only makes sense only for Parent – Child associations (the
Parent entity state transition being cascaded to its Child entities).
Cascading from Child to Parent is not very useful and usually, it’s a
mapping code smell.
From Hibernate best practices:
Avoid cascade remove for huge relationships
Most developers (myself included) get a little nervous when they see a
CascadeType.REMOVE definition for a relationship. It tells Hibernate
to also delete the related entities when it deletes this one. There is
always the fear that the related entity also uses cascade remove for
some of its relationships and that Hibernate might delete more
database records than intended. During all the years I’ve worked with
Hibernate, this has never happened to me, and I don’t think it’s a
real issue. But cascade remove makes it incredibly hard to understand
what exactly happens if you delete an entity. And that’s something you
should always avoid. If you have a closer look at how Hibernate
deletes the related entities, you will find another reason to avoid
it. Hibernate performs 2 SQL statements for each related entity: 1
SELECT statement to fetch the entity from the database and 1 DELETE
statement to remove it. This might be OK, if there are only 1 or 2
related entities but creates performance issues if there are large
numbers of them.

Related

Cascade remove relation without cascade remove target

I have a ManyToMany-relation between student and teacher in a Student_Teacher-table (Entityless).
Student: Teacher(owning-side): Student_Teacher
1= Tim 50= Mrs. Foo 1= 1 50
2= Ann 51= Mr. Bar 2= 1 51
3= 2 50
4= 2 51
As you see above every Student is currently related to every Teacher.
Now I like to remove Ann and I like to use the database's cascading techique to remove entries from the Student_Teacher-table but I do neither like to remove other Students, nor Teacher, nor other relationship.
This is what I have in the Student-Entity:
#ManyToMany(mappedBy="students")
public Set<Teacher> getTeachers() {
return teachers;
}
This is what I have in the Teacher-Entity:
#ManyToMany
#JoinTable(name="Student_Teacher", joinColumns = {
#JoinColumn(name="StudentID", referencedColumnName = "TeacherID", nullable = false)
}, inverseJoinColumns = {
#JoinColumn(name="TeacherID", referencedColumnName = "StudentID", nullable = false)
})
public Set<Student> getStudents() {
return students;
}
Now I like to use the database's delete cascade functionality. I repeat: The database's delete cascade functionality targeting the Student_Teacher-table only!
The problem:
org.h2.jdbc.JdbcSQLException: Referentielle Integrität verletzt: "FK_43PMYXR2NU005M2VNEB99VX0X: PUBLIC.Student_Teacher FOREIGN KEY(StudentID) REFERENCES PUBLIC.Student(StudentID) (2)"
Referential integrity constraint violation: "FK_43PMYXR2NU005M2VNEB99VX0X: PUBLIC.Student_Teacher FOREIGN KEY(StudentID) REFERENCES PUBLIC.Student(StudentID) (2)"; SQL statement:
delete from "Student" where name='Ann'
at org.h2.message.DbException.getJdbcSQLException(DbException.java:345)
at org.h2.message.DbException.get(DbException.java:179)
at org.h2.message.DbException.get(DbException.java:155)
at org.h2.constraint.ConstraintReferential.checkRow(ConstraintReferential.java:425)
What i can not use is the
#ManyToMany(cascade={CascadeType.REMOVE})
Because of the documetation tells me:
(Optional) The operations that must be cascaded to the target of the association.
The "target" is the Teacher, so this cascade would remove the Teacher (what I do not like to remove).
Question:
How to configure the entitys to remove Ann and the relation only using the database's cascade functionality?
Proof of Concept:
I tried another feature, I have noticed the possibility to configure the foreign-key nativly like this:
#ManyToMany(cascade = { CascadeType.REMOVE })
#JoinTable(name="Student_Teacher", joinColumns = {
#JoinColumn(name="StudentID", referencedColumnName = "TeacherID", nullable = false, foreignKey=#ForeignKey(foreignKeyDefinition="FOREIGN KEY (StudentID) REFERENCES Student ON DELETE NO ACTION"))
}, inverseJoinColumns = {
#JoinColumn(name="TeacherID", referencedColumnName = "StudentID", nullable = false, foreignKey=#ForeignKey(foreignKeyDefinition="FOREIGN KEY (TeacherID) REFERENCES Teacher ON DELETE NO ACTION"))
})
public Set<Student> getStudents() {
return students;
}
The problem is: This works fine but to trigger the removal of the entries in Student_Teacher I have to specify #ManyToMany(cascade = { CascadeType.REMOVE }) on both sides. Hibernate do not parse the foreignKeyDefinition and only see the CascadeType.REMOVE and drops the target-entitys (and the referenced Student Tim) out of the cache, but they are still in the database!!! So I have to clear the hibernate-session immendentelly after drop to re-read the existence of the Teachers Mrs. Foo and Mr. Bar and the Student Tim.
Now I like to use the database's delete cascade functionality. I
repeat: The database's delete cascade functionality targeting the
Student_Teacher-table only!
Simply define the cascade deletion on the database schema level, and the database would do it automatically. However, if the owning side of the association is loaded/manipulated in the same persistence context instance, then the persistence context will obviously be in an inconsistent state resulting in issues when managing the owning side, as Hibernate can't know what is done behind its back. Things get even more complicated if second-level caching is enabled.
So you can do it and take care not to load Teachers in the same session, but I don't recommend this and I write this only as an answer to this part of the question.
How to configure the entities to remove Ann and the relation only
using the database's cascade functionality?
There is no such configuration on JPA/Hibernate level. Most DDL declarations in mappings are used only for automatic schema generation, and are ignored when it comes to entity instances lifecycle and association management.
What i can not use is the
#ManyToMany(cascade={CascadeType.REMOVE})
Cascading of entity lifecycle operations and association management are two different notions that are completely independent of each other. Here you considered the former while you need the latter.
The problem you're facing is that you want to break the association from the Student (inverse side marked with mappedBy) when the Teacher is the owning side. You can do it by removing the student from all teachers to which it is associated, but that could lead to loading lots of data (all associated teachers with all their students). That's why introducing a separate entity for the association table could be a good compromise, as already suggested by #Mark, and as I suggested as well in some of my previous answers on similar topics together with some other potential improvements.
You may create a new entity TeacherStudent for the relationship, and then use CascadeType.REMOVE safely:
#Entity
public class Student {
#OneToMany(mappedBy="student",cascade={CascadeType.REMOVE})
public Set<TeacherStudent> teacherStudents;
}
#Entity
public class Teacher {
#OneToMany(mappedBy="teacher",cascade={CascadeType.REMOVE})
public Set<TeacherStudent> teacherStudents;
}
#Entity
public class TeacherStudent {
#ManyToOne
public Teacher teacher;
#ManyToOne
public Student student;
}
You'll have to take care of the composite foreign key for TeacherStudent. You may take a look at https://stackoverflow.com/a/29116687/3670143 for that.
Another relevant thread about ON DELETE CASCADE is JPA + Hibernate: How to define a constraint having ON DELETE CASCADE
As you see above every Student is related to every Teacher.
The point is when in a situation where "every A is related to every B", then there is no need to have a many to many table to keep such relationship. Since logically A and B is independent to each other in this situation. Adding/Deleting/Modifying A makes no effect on B and vice versa. This behaviour is exactly what you are after, because you want the cascading operations stop at the relation table:
delete cascade functionality targeting the Student_Teacher-table only!
Relationship table is only useful when in situation where "every A is related to a subset of B".
So to solve your problem is actually a fairly one: Drop the Student_Teacher table.
As we had a similar problem but finally solved it another way, here is our solution :
We replaced
#ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
in our relationship with
#ManyToMany(fetch = FetchType.LAZY, cascade = {
CascadeType.DETACH,
CascadeType.MERGE,
CascadeType.REFRESH,
CascadeType.PERSIST
})
and it successfully removed the association without removing linked entity.

Many to many bidirectional mapping in JPA

I have the following JPA entities.
A profile have many users and a user have many profiles:
#Entity
public class Profile implements Serializable {
#Id
private Long id;
#ManyToMany(cascade = CascadeType.ALL)
private List<User> users;
...
}
#Entity
public class User implements Serializable {
#Id
private Long id;
#ManyToMany(mappedBy = "users")
private List<Profile> profiles;
...
}
On my application, when a user is merged, the profiles are updated on database.
However, when a profile is merged, the users are not updated.
Is possible to map my entities in order to make both sides merge their lists?
I am using JPA 2.1 and Hibernate.
Your Profile entity is ownind side or relationship. It's up to it, to manage relationship, so in order to update User you'll have to update Profile too or make manual SQL calls.
Java Specification for JPA 2.1 says that:
• For many-to-many bidirectional relationships either side may be the owning side
So if you'd like to make both entities editable from both side, remove mappedBy element and assigne necessacy cascade. But I'm not sure it works in Hibernate (didn't try actually), see this docs on mapping, there's no information about m:m without owning side: http://docs.jboss.org/hibernate/orm/4.3/manual/en-US/html/ch07.html#collections-bidirectional
Otherwise, you may need to iterate through collection in Profile entity and then change them. For example:
for( User user : profile.getUsers() ) {
user.setSomething(.....);
}
session.merge(profile);
Changing List to Set might be needed in order to avoid Hibernate's delete and reinsert, described here: http://assarconsulting.blogspot.fr/2009/08/why-hibernate-does-delete-all-then-re.html
Also, don't forget about equals() and hashCode() methods override

Hibernate value is not deleting from the databse

#Entity
public class EUser {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private int id;
#ManyToMany(fetch=FetchType.EAGER)
private List<UserRole> roles;
}
when doing the following action
EUser approveUser = (EUser) userService.getOne(2);
approveUser.getRoles().clear();
userService.update(approveUser);
System.out.println(approveUser.getRoles().size());
it says the size is zero but when i go the db in the EUser_UserRole table i see the value still present. How to solve this??
also in the EUser_UserRole it says
This table does not contain a unique column. Grid edit, checkbox, Edit, Copy and Delete features are not available
how can i delete add edit delete manually??
Cascading is indeed a way to let Hibernate do the removal and if I see the posted code, that is most likely what is asked for. But since the question is about manually deleting while cascading is more automatic deletion, I have to add the suggestions to:
use EntityManager.remove()
invoke a JPQL delete query
Which more fit more the description of "manual" deletion.
se CascadeType. Reference
#ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
private List<UserRole> roles;
Update :
orphanRemoval attribute can use JPA 2.x version. You have to find out the deleted UserRole data by comparing old rolesList and new rolesList`
orphanRemoval attribute does not support in ManyToMany mapping.
Do not use a cascade for ManyToMany relationships. This can result in an undesired rippling deletion over a wide entity cluster easier than one might hope.
If you want to clear the relationship (delete rows from the join table) for a single user to their roles, you will need to clear the relationship fields on both sides, meaning clearing the List of UserRole in EUser and removing the current EUser from the lists in the respective UserRole instances.
EDIT:
You are not deleting any entities from the database when clearing the lists of related entities. The only result will be that some rows in the join table will be deleted and after the next fetch/refresh, your EUser and UserRole instances will no longer be related.
If you want to remove the UserRoles DB entries, you can do so after removing the relationships to

How to avoid bidirectional insertion on ManyToMany relationship

I have two tables with a m:n relationship. This relationship should be bidirectional when I am extracting the data from the database, so, I need the #ManyToMany in both entity classes. But, at the same time, I need that the non owner class does not insert the owner class when I perform a persist or merge operation on it.
For example, I have the Gene class, that is the owner class, and I have the Ontology class. One Gene has many Ontologies and one Ontology has many Genes. Classical many-to-many relationship. If I persist or merge a Gene I want to insert its Ontologies too, but I don't want that this Ontology insertion insert all other Genes linked to it.
On other hand, if I insert an Ontology I don't want to insert the genes linked to that Ontology.
I have been trying with a lot of JPA tags on the #ManyToMany and nothing works on the way that I want.
Have any one an Idea to solve this problem?
The Gene class
#ManyToMany(cascade = CascadeType.ALL)
#JoinTable(name = GeneTables.GENEINFO_HAS_ONTOLOGY,
joinColumns =
#JoinColumn(name = "GeneInfo_WID", referencedColumnName = "WID"),
inverseJoinColumns =
#JoinColumn(name = "Ontology_WID", referencedColumnName = "WID"))
private Set<Ontology> ontology;
The Ontology class
#ManyToMany(cascade = CascadeType.REFRESH, mappedBy = "ontology")
private Set<GeneInfo> geneInfo;
I tried all the Cascade types and even without the cascade option. The result is the same.
I have this error when I execute this code
EntityManager em = getEntityManager();
m.getTransaction().begin();
em.persist(ontology);
em.getTransaction().commit();
I have this error:
[EL Warning]: 2012-08-29 14:52:13.013--UnitOfWork(544628019)--java.lang.IllegalStateException: During synchronization a new object was found through a relationship that was not marked cascade PERSIST
at org.eclipse.persistence.internal.jpa.transaction.EntityTransactionImpl.commitInternal(EntityTransactionImpl.java:102)
at org.eclipse.persistence.internal.jpa.transaction.EntityTransactionImpl.commit(EntityTransactionImpl.java:63)
at org.jbiowh.core.datasets.ontology.controller.OntologyJpaController.create(OntologyJpaController.java:41)
at org.jbiowh.tools.prototypes.Test.main(Test.java:65)
Solved
I solved the problem. I create controller classes to handle the special cascades.
The create method on the Ontology controller class will be:
if (loadGeneFlag && ontology.getGene() != null && !ontology.getGene().isEmpty()) {
Set<Gene> geneSet = new HashSet<>();
GeneJpaController gController = new GeneJpaController(emf);
for (Gene gene : ontology.getGene()) {
Gene geneOnDB = em.find(Gene.class, gene.getWid());
if (geneOnDB != null) {
geneSet.add(geneOnDB);
} else {
gController.create(gene);
geneSet.add(em.getReference(Gene.class, gene.getWid()));
}
}
ontology.setGene(geneSet);
}
This code will create all gene references using the Gene controller class and not following the cascade operation. This give me the possibility to handle the Gene cascades correctly on the Gene controller class. Now, I don't have any duplicate object neither exceptions.
Where do these Genes come from?
If they are new, then you need to either persist them, or set cascade persist on the genes relationship. If they are existing, then you need to find them in the context of the current EntityManager/transaction.
If you don't want then persisted, then don't add them to the genes collection.
Set cascade=CascadeType.REFRESH on both entities or remove cascade attributes on both entities.
You must use mappedBy on one side of a bidirectional relationship.
See,
http://en.wikibooks.org/wiki/Java_Persistence/ManyToMany#Bi-directional_Many_to_Many

JPA: implicit cascades for relationships mapped as #ManyToMany #JoinTable?

I have the following mapping:
#Entity
#Table(name = "Prequalifications")
public class Prequalification implements Serializable
{
...
#ManyToMany
#JoinTable(name = "Partnerships", joinColumns = #JoinColumn(name = "prequalification_id", referencedColumnName = "id"), inverseJoinColumns = #JoinColumn(name = "company_id", referencedColumnName = "id"))
private Set<Company> companies;
...
}
In a #ManyToMany + #JoinTable mapped relationship, isn't it kind of implicit that the association (link) entities (here Partnerships) are automatically persisted, removed, etc. even though
by default, relationships have an empty cascade set
? The above quote was taken from "Pro JPA 2, by Mike Keith".
Executing
em.merge(prequalification);
on the above entity does persist the associated partnerships without any cascade types specified.
Am I correct that this implicit cascade has to be performed? This isn't mentioned anywhere I looked...
The rows in the join table will be inserted/deleted as part of the owning Entity (if bi-directional the side without the mappedBy). So if you persist or remove or update the Prequalification the join table rows will also be inserted or deleted.
The target Company objects will not be cascaded to. So on remove() they will not be deleted, if the list is updated they will not be deleted unless orphanRemovla is set. Persist should also not be cascaded, but what happens when you have references to "detached" objects is somewhat of a grey area. Technically an error should be thrown, because the object is new and the relationship was not cascade persist. It may also try to insert and get a constraint error. It should not cascade the persist, although your object model is technically in an invalid state, so what occurs may depend on the provider.
Wanted to add a comment, but don't have enough rep for it.
I had the same question as #D-Dᴙum: "Where in the docs can we find a reference to this behaviour?"
I found it in the Hibernate docs (many-to-many).
If you scroll just a bit just below the code example there, you will find:
When an entity is removed from the #ManyToMany collection, Hibernate simply deletes the joining record in the link table. Unfortunately, this operation requires removing all entries associated with a given parent and recreating the ones that are listed in the current running persistent context.
Where the "link table" refers to the "join table".
Hope this helps.

Categories

Resources