Hibernate Cascade example
In this tutorial we are going to see the use of the cascade feature of relational databases and how it is applied in Hibernate. This tutorial is based on the previous Hibernate One-to-Many Relationship Example (XML Mapping and Annotation). You can download the Eclipse project from there.
So these are the tools we are going to use on a Windows 7 platform:
- JDK 1.7
- Maven 3.0.5
- Hibernate 3.6.3.Final
- MySQL JDBC driver 5.1.9
- Eclipse 4.2 Juno
Cascade feature
The cascade
keyword is frequently applied in relational databases. It is mostly used in in databases that contain tables with one-to-one, one-to-may and many-to-many relationships. I think its name, cascade
, is very revealing about its usage. When the cascade keyword is specified in a property of an entity, it means that the change applied on the entity will also be applied in the property as well. It is a fast way to manage the other side of the relationship automatically, without writing extra code.
1. Cascade save-update
Let’s say we have a one-to-many relationship like this one :
These tables are mapped to Student
and Project
entities:
Student.java:
public class Student implements java.io.Serializable { private static final long serialVersionUID = 1L; private Integer studentId; private String studentName; private String studentAge; private Set<Project> studentProjects = new HashSet(0); ...
Projects.java:
public class Project implements java.io.Serializable{ private Integer projectId; private String title; private String semester; private Student student; ...
And these are the Hibernate Mapping files:
Student.hbm.xml:
<?xml version="1.0"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"> <hibernate-mapping> <class name="com.javacodegeeks.enterprise.hibernate.Student" table="student" catalog="tutorials"> <id name="studentId" type="java.lang.Integer"> <column name="STUDENT_ID" /> <generator class="identity" /> </id> <property name="studentName" type="string"> <column name="STUDENT_NAME" length="10" not-null="true" unique="true" /> </property> <property name="studentAge" type="string"> <column name="STUDENT_Age" length="20" not-null="true" unique="true" /> </property> <set name="studentProjects" inverse="true" table="projects" lazy="true" fetch="select"> <key> <column name="STUDENT_ID" not-null="true" /> </key> <one-to-many class="com.javacodegeeks.enterprise.hibernate.Project" /> </set> </class> </hibernate-mapping>
Project.hbm.xml:
<?xml version="1.0"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"> <!-- Generated 25 April 2011 7:52:33 PM by Hibernate Tools 3.4.0.CR1 --> <hibernate-mapping> <class name="com.javacodegeeks.enterprise.hibernate.Project" table="projects" catalog="tutorials"> <id name="projectId" type="java.lang.Integer"> <column name="PROJECT_ID" /> <generator class="identity" /> </id> <property name="title" type="string"> <column name="TITLE" length="100" not-null="true" unique = "true" /> </property> <property name="semester" type="string"> <column name="SEMESTER" length="100" /> </property> <many-to-one name="student" class="com.javacodegeeks.enterprise.hibernate.Student" fetch="select"> <column name="STUDENT_ID" not-null="true" /> </many-to-one> </class> </hibernate-mapping>
Now let’s take a close look to App.java
:
... Student student = new Student(); student.setStudentName("Sarah"); student.setStudentAge("21"); Project project1 = new Project("L","Spring"); project1.setStudent(student); student.getStudentProjects().add(project1); Project project2 = new Project("M","Spring"); project2.setStudent(student); student.getStudentProjects().add(project2); session.save(student); session.save(project1); session.save(project2); session.getTransaction().commit(); ...
As you can see when we don’t use the cascade keyword on the relationship mapping we have to save both Student
and Project
instances to the hibernate Session
.
The output of the above program will be :
Hibernate: insert into tutorials.student (STUDENT_NAME, STUDENT_Age) values (?, ?)
Hibernate: insert into tutorials.projects (TITLE, SEMESTER, STUDENT_ID) values (?, ?, ?)
Hibernate: insert into tutorials.projects (TITLE, SEMESTER, STUDENT_ID) values (?, ?, ?)
Great! Student was saved
Now let’s add cascade="save-update"
to Student.hbm.xml
.
Student.hbm.xml:
<?xml version="1.0"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"> <hibernate-mapping> <class name="com.javacodegeeks.enterprise.hibernate.Student" table="student" catalog="tutorials"> <id name="studentId" type="java.lang.Integer"> <column name="STUDENT_ID" /> <generator class="identity" /> </id> <property name="studentName" type="string"> <column name="STUDENT_NAME" length="10" not-null="true" unique="true" /> </property> <property name="studentAge" type="string"> <column name="STUDENT_Age" length="20" not-null="true" unique="true" /> </property> <set name="studentProjects" inverse="true" cascade="save-update" table="projects" lazy="true" fetch="select"> <key> <column name="STUDENT_ID" not-null="true" /> </key> <one-to-many class="com.javacodegeeks.enterprise.hibernate.Project" /> </set> </class> </hibernate-mapping>
You can chage App.java
to this:
... Student student = new Student(); student.setStudentName("Sarah"); student.setStudentAge("21"); Project project1 = new Project("R","Spring"); project1.setStudent(student); student.getStudentProjects().add(project1); Project project2 = new Project("P","Spring"); project2.setStudent(student); student.getStudentProjects().add(project2); session.save(student); session.getTransaction().commit(); ...
This is the output:
Hibernate: insert into tutorials.student (STUDENT_NAME, STUDENT_Age) values (?, ?)
Hibernate: insert into tutorials.projects (TITLE, SEMESTER, STUDENT_ID) values (?, ?, ?)
Hibernate: insert into tutorials.projects (TITLE, SEMESTER, STUDENT_ID) values (?, ?, ?)
Great! Student was saved
No need to save Project
instances to the Session
. Because of the cascade
keyword with save-update
value in the studentProjects
property of Student
, whenever a new student
tuple is saved, all projects in studentProjects
set will be also persisted in projects
table. The same thing applies when you update a Student
. All projects
in the aforementioned set will also be updated.
If we leave App.java
as it is (saving just the Student
instance) and remove the cascade
keyword from Student.hbm.xml
this is the the output:
Hibernate: insert into tutorials.student (STUDENT_NAME, STUDENT_Age) values (?, ?)
Great! Student was saved
It’s also possible that you get an Exception, if you’re not using inverse
keyword which we explained in Hibernate One-to-Many Relationship Example (XML Mapping and Annotation).
2. Cascade delete
If you don’t use cascade
with delete
value, when deleting a Student
you have to manually delete all of the projects is it’s studentProject
in its set.
So in App.java
you have to write something like this:
... session.beginTransaction(); Query q = session.createQuery("from Student where studentId = :studentId "); q.setParameter("studentId", 50); Student student = (Student)q.list().get(0); for (Project project : student.getStudentProjects()){ session.delete(project); } session.delete(student); session.getTransaction().commit(); ...
This means that you have to fetch all the projects that this student is working on from the database and then delete them one by one. This is the result:
Hibernate: select student0_.STUDENT_ID as STUDENT1_0_, student0_.STUDENT_NAME as STUDENT2_0_, student0_.STUDENT_Age as STUDENT3_0_ from tutorials.student student0_ where student0_.STUDENT_ID=?
Hibernate: select studentpro0_.STUDENT_ID as STUDENT4_0_1_, studentpro0_.PROJECT_ID as PROJECT1_1_, studentpro0_.PROJECT_ID as PROJECT1_1_0_, studentpro0_.TITLE as TITLE1_0_, studentpro0_.SEMESTER as SEMESTER1_0_, studentpro0_.STUDENT_ID as STUDENT4_1_0_ from tutorials.projects studentpro0_ where studentpro0_.STUDENT_ID=?
Hibernate: delete from tutorials.projects where PROJECT_ID=?
Hibernate: delete from tutorials.projects where PROJECT_ID=?
Hibernate: delete from tutorials.student where STUDENT_ID=?
But if you change Student.hbm.xml
to this:
Student.hbm.xml:
<?xml version="1.0"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"> <hibernate-mapping> <class name="com.javacodegeeks.enterprise.hibernate.Student" table="student" catalog="tutorials"> <id name="studentId" type="java.lang.Integer"> <column name="STUDENT_ID" /> <generator class="identity" /> </id> <property name="studentName" type="string"> <column name="STUDENT_NAME" length="10" not-null="true" unique="true" /> </property> <property name="studentAge" type="string"> <column name="STUDENT_Age" length="20" not-null="true" unique="true" /> </property> <set name="studentProjects" inverse="true" cascade="delete" table="projects" lazy="true" fetch="select"> <key> <column name="STUDENT_ID" not-null="true" /> </key> <one-to-many class="com.javacodegeeks.enterprise.hibernate.Project" /> </set> </class> </hibernate-mapping>
You can change App.java
to this:
... session.beginTransaction(); Query q = session.createQuery("from Student where studentId = :studentId "); q.setParameter("studentId", 46); Student student = (Student)q.list().get(0); session.delete(student); session.getTransaction().commit(); ...
This is the result:
Hibernate: select student0_.STUDENT_ID as STUDENT1_0_, student0_.STUDENT_NAME as STUDENT2_0_, student0_.STUDENT_Age as STUDENT3_0_ from tutorials.student student0_ where student0_.STUDENT_ID=?
Hibernate: select studentpro0_.STUDENT_ID as STUDENT4_0_1_, studentpro0_.PROJECT_ID as PROJECT1_1_, studentpro0_.PROJECT_ID as PROJECT1_1_0_, studentpro0_.TITLE as TITLE1_0_, studentpro0_.SEMESTER as SEMESTER1_0_, studentpro0_.STUDENT_ID as STUDENT4_1_0_ from tutorials.projects studentpro0_ where studentpro0_.STUDENT_ID=?
Hibernate: delete from tutorials.projects where PROJECT_ID=?
Hibernate: delete from tutorials.projects where PROJECT_ID=?
Hibernate: delete from tutorials.student where STUDENT_ID=?
3. Cascade delete-orphan
Now imagine that you don’t want to delete a student
, but you do want to remove it’s assigned projects
. This means that you want to remove some “couples” of the relationship. Without using cascade
with value delete-orphan
, you have to explicitly delete the corresponding tuples from the project
table.
Like so:
... session.beginTransaction(); project project1 = (Project) session.get(Project.class, new Integer(52)); Project project2 = (Project) session.get(Project.class, new Integer(53)); session.delete(project1); session.delete(project2); session.getTransaction().commit(); ...
And the result will be:
Hibernate: select project0_.PROJECT_ID as PROJECT1_1_0_, project0_.TITLE as TITLE1_0_, project0_.SEMESTER as SEMESTER1_0_, project0_.STUDENT_ID as STUDENT4_1_0_ from tutorials.projects project0_ where project0_.PROJECT_ID=?
Hibernate: select project0_.PROJECT_ID as PROJECT1_1_0_, project0_.TITLE as TITLE1_0_, project0_.SEMESTER as SEMESTER1_0_, project0_.STUDENT_ID as STUDENT4_1_0_ from tutorials.projects project0_ where project0_.PROJECT_ID=?
Hibernate: delete from tutorials.projects where PROJECT_ID=?
Hibernate: delete from tutorials.projects where PROJECT_ID=?
But if you change Student.hbm.xml
to this:
Student.hbm.xml:
<?xml version="1.0"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"> <hibernate-mapping> <class name="com.javacodegeeks.enterprise.hibernate.Student" table="student" catalog="tutorials"> <id name="studentId" type="java.lang.Integer"> <column name="STUDENT_ID" /> <generator class="identity" /> </id> <property name="studentName" type="string"> <column name="STUDENT_NAME" length="10" not-null="true" unique="true" /> </property> <property name="studentAge" type="string"> <column name="STUDENT_Age" length="20" not-null="true" unique="true" /> </property> <set name="studentProjects" inverse="true" cascade="delete-orphan" table="projects" lazy="true" fetch="select"> <key> <column name="STUDENT_ID" not-null="true" /> </key> <one-to-many class="com.javacodegeeks.enterprise.hibernate.Project" /> </set> </class> </hibernate-mapping>
You can change App.java
to this:
... session.beginTransaction(); project project1 = (Project) session.get(Project.class, new Integer(48)); Project project2 = (Project) session.get(Project.class, new Integer(49)); Student student = (Student)session.get(Student.class, new Integer(42)); student.getStudentProjects().remove(project1); student.getStudentProjects().remove(project2); session.saveOrUpdate(student); session.getTransaction().commit(); ...
This means that when you want to delete a “couple” of the relationship you just have to to remove the projects from the student’s studentProjects
set. Now when the Student
is updated, the coresponding project tuples will be deleted. This is the result:
Hibernate: select project0_.PROJECT_ID as PROJECT1_1_0_, project0_.TITLE as TITLE1_0_, project0_.SEMESTER as SEMESTER1_0_, project0_.STUDENT_ID as STUDENT4_1_0_ from tutorials.projects project0_ where project0_.PROJECT_ID=?
Hibernate: select project0_.PROJECT_ID as PROJECT1_1_0_, project0_.TITLE as TITLE1_0_, project0_.SEMESTER as SEMESTER1_0_, project0_.STUDENT_ID as STUDENT4_1_0_ from tutorials.projects project0_ where project0_.PROJECT_ID=?
Hibernate: select student0_.STUDENT_ID as STUDENT1_0_0_, student0_.STUDENT_NAME as STUDENT2_0_0_, student0_.STUDENT_Age as STUDENT3_0_0_ from tutorials.student student0_ where student0_.STUDENT_ID=?
Hibernate: select studentpro0_.STUDENT_ID as STUDENT4_0_1_, studentpro0_.PROJECT_ID as PROJECT1_1_, studentpro0_.PROJECT_ID as PROJECT1_1_0_, studentpro0_.TITLE as TITLE1_0_, studentpro0_.SEMESTER as SEMESTER1_0_, studentpro0_.STUDENT_ID as STUDENT4_1_0_ from tutorials.projects studentpro0_ where studentpro0_.STUDENT_ID=?
Hibernate: delete from tutorials.projects where PROJECT_ID=?
Hibernate: delete from tutorials.projects where PROJECT_ID=?
Note that when using annotations you just have to use the @Cascade
annotation, like so:
Student.java:
... @OneToMany(fetch = FetchType.LAZY, mappedBy = "student") @Cascade({CascadeType.SAVE_UPDATE, CascadeType.DELETE}) public Set<Project> getStudentProjects() { return studentProjects; } ...
This was an example on Hibernate Cascade.