Main Tutorials

Hibernate – Cascade example (save, update, delete and delete-orphan)

Cascade is a convenient feature to save the lines of code needed to manage the state of the other side manually.

The “Cascade” keyword is often appear on the collection mapping to manage the state of the collection automatically. In this tutorials, this one-to-many example will be used to demonstrate the cascade effect.

Cascade save / update example

In this example, if a ‘Stock’ is saved, all its referenced ‘stockDailyRecords’ should be saved into database as well.

1. No save-update cascade

In previous section, if you want to save the ‘Stock’ and its referenced ‘StockDailyRecord’ into database, you need to save both individually.


Stock stock = new Stock();
StockDailyRecord stockDailyRecords = new StockDailyRecord();
//set the stock and stockDailyRecords  data

stockDailyRecords.setStock(stock);        
stock.getStockDailyRecords().add(stockDailyRecords);

session.save(stock);
session.save(stockDailyRecords);

Output


Hibernate: 
    insert into mkyong.stock (STOCK_CODE, STOCK_NAME) 
    values (?, ?)

Hibernate: 
    insert into mkyong.stock_daily_record
    (STOCK_ID, PRICE_OPEN, PRICE_CLOSE, PRICE_CHANGE, VOLUME, DATE) 
    values (?, ?, ?, ?, ?, ?)

2. With save-update cascade

The cascade=”save-update” is declared in ‘stockDailyRecords’ to enable the save-update cascade effect.


<!-- Stock.hbm.xml -->
<set name="stockDailyRecords" cascade="save-update" table="stock_daily_record"...>
      <key>
            <column name="STOCK_ID" not-null="true" />
      </key>
      <one-to-many class="com.mkyong.common.StockDailyRecord" />
</set>

Stock stock = new Stock();
StockDailyRecord stockDailyRecords = new StockDailyRecord();
//set the stock and stockDailyRecords  data

stockDailyRecords.setStock(stock);        
stock.getStockDailyRecords().add(stockDailyRecords);

session.save(stock);

Output


Hibernate: 
    insert into mkyong.stock (STOCK_CODE, STOCK_NAME) 
    values (?, ?)

Hibernate: 
    insert into mkyong.stock_daily_record
    (STOCK_ID, PRICE_OPEN, PRICE_CLOSE, PRICE_CHANGE, VOLUME, DATE) 
    values (?, ?, ?, ?, ?, ?)

The code session.save(stockDailyRecords); is no longer required, when you save the ‘Stock’, it will “cascade” the save operation to it’s referenced ‘stockDailyRecords’ and save both into database automatically.

Cascade delete example

In this example, if a ‘Stock’ is deleted, all its referenced ‘stockDailyRecords’ should be deleted from database as well.

1. No delete cascade

You need to loop all the ‘stockDailyRecords’ and delete it one by one.


Query q = session.createQuery("from Stock where stockCode = :stockCode ");
q.setParameter("stockCode", "4715");
Stock stock = (Stock)q.list().get(0);
    
for (StockDailyRecord sdr : stock.getStockDailyRecords()){
         session.delete(sdr);
}
 session.delete(stock);

Output


Hibernate: 
    delete from mkyong.stock_daily_record 
    where DAILY_RECORD_ID=?

Hibernate: 
    delete from mkyong.stock 
    where STOCK_ID=?

2. With delete cascade

The cascade=”delete” is declared in ‘stockDailyRecords’ to enable the delete cascade effect. When you delete the ‘Stock’, all its reference ‘stockDailyRecords’ will be deleted automatically.


<!-- Stock.hbm.xml -->
<set name="stockDailyRecords" cascade="delete" table="stock_daily_record" ...>
      <key>
            <column name="STOCK_ID" not-null="true" />
      </key>
      <one-to-many class="com.mkyong.common.StockDailyRecord" />
</set>

Query q = session.createQuery("from Stock where stockCode = :stockCode ");
q.setParameter("stockCode", "4715");
Stock stock = (Stock)q.list().get(0);
session.delete(stock);

Output


Hibernate: 
    delete from mkyong.stock_daily_record 
    where DAILY_RECORD_ID=?

Hibernate: 
    delete from mkyong.stock 
    where STOCK_ID=?

Cascade delete-orphan example

In above cascade delete option, if you delete a Stock , all its referenced ‘stockDailyRecords’ will be deleted from database as well. How about if you just want to delete two referenced ‘stockDailyRecords’ records? This is called orphan delete, see example…

1. No delete-orphan cascade

You need to delete the ‘stockDailyRecords’ one by one.


StockDailyRecord sdr1 = (StockDailyRecord)session.get(StockDailyRecord.class, 
                                            new Integer(56));
StockDailyRecord sdr2 = (StockDailyRecord)session.get(StockDailyRecord.class, 
                                            new Integer(57));

session.delete(sdr1);
session.delete(sdr2);

Output


Hibernate: 
    delete from mkyong.stock_daily_record 
    where DAILY_RECORD_ID=?
Hibernate: 
    delete from mkyong.stock_daily_record 
    where DAILY_RECORD_ID=?

2. With delete-orphan cascade

The cascade=”delete-orphan” is declared in ‘stockDailyRecords’ to enable the delete orphan cascade effect. When you save or update the Stock, it will remove those ‘stockDailyRecords’ which already mark as removed.


<!-- Stock.hbm.xml -->
<set name="stockDailyRecords" cascade="delete-orphan" table="stock_daily_record" >
      <key>
            <column name="STOCK_ID" not-null="true" />
      </key>
      <one-to-many class="com.mkyong.common.StockDailyRecord" />
</set>

StockDailyRecord sdr1 = (StockDailyRecord)session.get(StockDailyRecord.class, 
                                       new Integer(56));
StockDailyRecord sdr2 = (StockDailyRecord)session.get(StockDailyRecord.class, 
                                       new Integer(57));

Stock stock = (Stock)session.get(Stock.class, new Integer(2));
stock.getStockDailyRecords().remove(sdr1);
stock.getStockDailyRecords().remove(sdr2);
		
session.saveOrUpdate(stock);

Output


Hibernate: 
    delete from mkyong.stock_daily_record 
    where DAILY_RECORD_ID=?
Hibernate: 
    delete from mkyong.stock_daily_record 
    where DAILY_RECORD_ID=?

In short, delete-orphan allow parent table to delete few records (delete orphan) in its child table.

How to enable cascade ?

The cascade is supported in both XML mapping file and annotation.

1. XML mapping file

In XML mapping file, declared the cascade keyword in your relationship variable.


<!-- Stock.hbm.xml -->
<set name="stockDailyRecords" cascade="save-update, delete" 
        table="stock_daily_record" ...>
      <key>
            <column name="STOCK_ID" not-null="true" />
      </key>
      <one-to-many class="com.mkyong.common.StockDailyRecord" />
</set>

2. Annotation

In annotation, declared the CascadeType.SAVE_UPDATE (save, update) and CascadeType.REMOVE (delete) in @Cascade annotation.


        //Stock.java
        @OneToMany(mappedBy = "stock")
        @Cascade({CascadeType.SAVE_UPDATE, CascadeType.DELETE})
	public Set<StockDailyRecord> getStockDailyRecords() {
		return this.stockDailyRecords;
	}

Further study – Cascade – JPA & Hibernate annotation common mistake.

inverse vs cascade

Both are totally different notions, see the differential here.

Conclusion

Cascade is a very convenient feature to manage the state of the other side automatically. However this feature come with a price, if you do not use it wisely (update or delete), it will generate many unnecessary cascade effects (cascade update) to slow down your performance, or delete (cascade delete) some data you didn’t expected.

About Author

author image
Founder of Mkyong.com, love Java and open source stuff. Follow him on Twitter. If you like my tutorials, consider make a donation to these charities.

Comments

Subscribe
Notify of
35 Comments
Most Voted
Newest Oldest
Inline Feedbacks
View all comments
Mahesh vasudevan
8 years ago

Excellent!!! and Simple Articulation..

Alex
9 years ago

I have de same configuration on hbm, but.. the result is :

Hibernate:
insert into TLMS005_CONTRATO
(CD_CLIENTE, CD_CUENTA, NB_PRODUCTO, CD_FOLIODIGIT, TM_RECEPCION, CD_ESTATUS, CD_CONTRATO) values (?, ?, ?, ?, ?, ?, ?)
Hibernate:
update TLMS004_RECUARCH set
TP_ARCHIVO=?,
NB_RUTAARCH=?,
TX_MSGERR=?,
TM_ACTUALIZACION=?,
ST_RECUPERACION=?
where
CD_CONTRATO=?

why get an update??? .. should be a two inserts… whats is wrong???

Alex
9 years ago
Reply to  Alex

any body can help me!!!

Juan Ignacio
2 years ago
Reply to  Alex

sorry if I’m very late. Surely you have solved it but it can be used for other people
to be able to insert you need the id to be null
If you are looking for an object (or you bring it from the DTO, or you have EAGER configured) and it has changes, update is done.
With the id in null it has no where to update and executes the insert

suresh
5 years ago

Hi I have a doubt on Hibernate multilevel deletion

Binh Thanh Nguyen
8 years ago

Thanks, nice tips

Daniel Mota
8 years ago

Hi, here is shown a error:

Hibernate:
insert
into
QUESTAO
(QUESTAO)
values
(?)
Hibernate:
insert
into
ALTERNATIVA
(IDQUESTAO, ALTERNATIVA)
values
(?, ?)
Jul 06, 2015 3:59:05 PM org.hibernate.engine.jdbc.spi.SqlExceptionHelper logExceptions
WARN: SQL Error: 1048, SQLState: 23000
Jul 06, 2015 3:59:05 PM org.hibernate.engine.jdbc.spi.SqlExceptionHelper logExceptions
ERROR: Column ‘IDquestao’ cannot be null
Jul 06, 2015 3:59:05 PM org.apache.catalina.core.StandardWrapperValve invoke
Grave: Servlet.service() for servlet [dispatcher] in context with path [/Research] threw exception [Request processing failed; nested exception is org.hibernate.exception.ConstraintViolationException: could not execute statement] with root cause
com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException: Column ‘IDquestao’ cannot be null

@Entity

@Table(name = "QUESTAO")
public class Question {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "IDQUESTAO")
private long id;

@NotEmpty
@Column(name = "QUESTAO", nullable = false)
private String text;

@Column(name = "STATUS", nullable = false, insertable = false)
private int status;

@Valid
@OneToMany(mappedBy = "question")
@Cascade({CascadeType.PERSIST})
private List answers;

-------------------------------------------------------
@Entity
@Table(name = "ALTERNATIVA")
public class Answer {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "IDALTERNATIVA")
private Long id;

@NotEmpty
@Column(name = "ALTERNATIVA", nullable = false)
private String text;

@ManyToOne
@JoinColumn(name = "IDQUESTAO")
public Question question;

Can you help me?

Sridhar.Goranti
8 years ago

Very nice article. Thanking you mkyong 🙂

riham
10 years ago

very nice thanks alot

johny
10 years ago

I read topic about inverse=”false” and inverse=”true”.
when using cascade=”delete” (cascade=”all” ) and inverse=”false” in file xml.
example code :
Stock stock = new Stock();
stock = stockDao.getStock ById(7);
stock Dao.deleteStock (stock);

Error : could not execute statement
Because Hibernate will update StockId=null

I want explain more ! thank.

when using inverse=”false” everything is good.

nithiyasri
10 years ago
Reply to  johny

Yes.. Even I had the same issue of when combining invere=true and cascade=”delete”, it doesnt work. Tried

session.delete(stock_daily_record) also.

JESUS DAVID RUBIO
11 years ago

I have a question, when you write about the “delete” you explained the cascade effect would delete all the objects related to that object (one to many), then you talked about the “delete-orphan”, which it is used to delete just related objects to a main one, now my question is how can I write the clause to get both effects at the same time? the clause would be “delete-delete-ophan” or “delete-orphan” will do it?

xfire.com
11 years ago

Hey there! I understand this is somewhat off-topic but I
had to ask. Does operating a well-established blog such
as yours require a large amount of work? I’m brand new to running a blog however I do write in my journal on a daily basis. I’d like to start a blog
so I will be able to share my experience and feelings online.

Please let me know if you have any suggestions or tips for brand new aspiring blog
owners. Appreciate it!

Nick
11 years ago

In a example similar to this
for stock.getStockDailyRecords().add(stockDailyRecords);

I am getting null pointer exception for stock.getStockDailyRecords().

How do I avoid this?

johny
10 years ago
Reply to  Nick

you try :

if (stock.getStockDailyRecords() == null) {
stock.setStockDailyRecords(new HashSet());
}
stock.getStockDailyRecords().add(stockDailyRecords);

Lord0
11 years ago

Thanks – your delete-orphan stuff helped me a lot

Lulu88
11 years ago

THANK YOU SO MUCH!!!

I struggled for TWO DAYS with an error and this solved it for me!! 😀

Genius you are 😉

chejomolina
11 years ago

this it really help me a lot thank you

Tamatha Sailor
11 years ago

Hello, Neat post. There is a problem along with your web site in internet explorer, might check this¡K IE nonetheless is the marketplace chief and a big component to other folks will leave out your excellent writing because of this problem.

Naresh Pokuri
12 years ago

All these cascade operations works with only Bi-directional associations, right? or It can support unidirectional associations? What about inverse then…I think that should work with Bidirectional.

Thank you

Surender
12 years ago

Hi

your article is too good . very easy to undersatnd and get the basics right.
i have a requiremnt where my StockDetail extend my stock class and when i say
session.save(StockDetail ); which is my child class
a record should be inserted into parent table(Stock) and then insertion should happen in my child table(StockDetail)

Samyr Moises
12 years ago

Thanks indeed!It was simple and great!
It helped me a lot!

See ya in the future!

Scott
12 years ago

Thanks, it’s very easy to understand the cascade from your article!

George
12 years ago

Thanks for your excellent work. I am confused with your delete-orphan demo:
if Stock side is not ownership (inverse=”false”), remove StockDailyRecord from its list will only lead to set StockDailyRecord.STOCK_ID to be null but won’t remove StockDailyRecord from DB.

What’s more:
1) I am using Hibernate 3.2.6
2) if I set many-to-one(insert=”false” update=”false”) in StockDailyRecord side. delete-orphan works properly.

Bill Leonard
12 years ago

I liked your article on fetch strategies but this one, imo, could be improved for explaining delete-orphan. I found the following better: http://docs.jboss.org/hibernate/core/3.3/reference/en/html/example-parentchild.html

I’d emphasize that ‘delete-orphan’ handles the case when we want to delete the child object from the parent’s collection and have this reflected in the database without having to do an explicit session.delete() on the child objects. e.g.
1) Without delete-orphan:
stock.getStockDailyRecords().remove(sdr1);
stock.getStockDailyRecords().remove(sdr2);
sdr1.setStock(null);
sdr2.setStock(null);
session.saveOrUpdate(stock);
results in an update to the the StockDailyRecord table to set the Foreign Key to Stock for sdr1 and sdr2 to null (keeps those records in that table)

2) With delete-orphan:
The same code as above results in sdr1 and sdr2 being deleted.

jeffmacn
13 years ago

why delete-orphan doesn’t work?

Session session = HibernateUtil.getSession();
Transaction tx = session.beginTransaction();

Book book = (Book)session.get(Book.class, 1);

tx.commit();
session.close();

Session session2 = HibernateUtil.getSession();
Transaction tx2 = session2.beginTransaction();

Publisher publisher = (Publisher)session2.get(Publisher.class, 1);
publisher.getBooks().remove(book);

session.saveOrUpdate(publisher);

tx2.commit();
session2.close();

Huy
12 years ago
Reply to  jeffmacn

Not work cause you close session 1 too soon

Tarun Sapra
13 years ago

Excellent Work! best explanation of inverse i could find on the internet.

Sudhakar
13 years ago

Your examples arequite simple and easy to understand..thanks and make I wish to have some more examples in spring mvc.

chetan
12 years ago
Reply to  mkyong

You simply doing great job man. With such simple example you making people knowledgeable about complex concpets of hibernate.

SIMPLY GREAT!!!

Rodrigo Machado
13 years ago

Thanks man! It’s help so much!

Ram
13 years ago

good article buddy. keep it up.. thx.

selvakumar
9 years ago

Hi, What is the use of cascade=merge? can you explain me like above cascade= delete-orphan.

Cristian Daniel Ortiz
11 years ago

hi. Mkyong. how you doing.. i have a One to many relationship.. and working very well. but when i try to update the many relationship is insert new rows instead of deleting the old records and updating the new records…

i have a table Users and other table usersbyline. suppose the relationship like this.

Users have one or many Usersbyline. my code is the next.

    public static void main(String...Arg) throws Exception
    {                
        Session Session = HibernateUtil.getSessionFactory().openSession();
        System.out.println("--------Start-----------------------");
        Long A = System.currentTimeMillis();
        Integer idUser     = 25;
        String Operation = "edit";        
        org.com.Models.Usuarios Users = (org.com.Models.Usuarios)Session.get(org.com.Models.Usuarios.class,idUser);//Ignore this.
        if(Users==null){System.out.println("User Not Found...");return;}
        Boolean isInsert = Operation.equalsIgnoreCase("add")?true:false;//CRUD operation               
        UsuarioController Controller = new UsuarioController();
        Controller.Dao.setClazz(Users);
        Controller.Dao.setSession(Session);
        Controller.Dao.setShouldIUseSessionFromChild(true);
        String  idLinea[] = "1000009".split(",");
        java.util.Set<org.com.Models.UsuariosLinea>ULineas = new java.util.HashSet<org.com.Models.UsuariosLinea>(idLinea.length);
        for(String Item:idLinea)//adding the new lines to the user. i want to delete the previous. 
        { 
               org.com.Models.UsuariosLinea UsuariosLinea = new org.com.Models.UsuariosLinea();
               UsuariosLinea.setLinea(new org.com.Models.Lineas(Item));              
               UsuariosLinea.setUsuario(Controller.Dao.Clazz);
               ULineas.add(UsuariosLinea);
        }
        Controller.Dao.Clazz.setUsuariosLineas(ULineas);    
        System.out.println(Controller.Dao.Clazz.getUsuariosLineas().size());
        Controller.Dao.saveOrUpdate(isInsert);                                                
        Long B = System.currentTimeMillis();        
        String Action = (isInsert)?(System.currentTimeMillis()-A)+"ms..[En Executives[Inserting]]":((System.currentTimeMillis()-A)+"ms..[En Executives[Updating]]");    
        System.out.println(Action);//String the client.
        JavaMisc.cleanResource(Session);
    }     

suppose the user have related 2 Users line with idLine is 1000011 and 1000012; all i want to do is.. when the user is update delete the old records the user have and insert the new ones. in this case only 1000011. but i am getting the old records + the new ones 4 records related to the user. what i am doing wrong.

Users.java

    @OneToMany(fetch = FetchType.EAGER, mappedBy = "Usuario")
    @Cascade(CascadeType.ALL)
    private Set<org.com.Models.UsuariosLinea> UsuariosLineas = new HashSet<org.com.Models.UsuariosLinea>();  

UsersByLine.java

    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    @Column(name="id",nullable=false,updatable=false,length=10,insertable=true)
    private Integer Id;
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "iduser",nullable=false,insertable=true,updatable=true)
    private org.com.Models.Usuarios Usuario;
    @ManyToOne(fetch = FetchType.EAGER)
    @JoinColumn(name = "idlinea",nullable=false,insertable=true,updatable=true)
    private org.com.Models.Lineas Linea;
    public Integer getId(){return Id;}
    public void setId(Integer Id){this.Id = Id;}
    public Lineas getLinea(){return Linea;}
    public void setLinea(Lineas Linea){this.Linea = Linea;}
    public Usuarios getUsuario(){return Usuario;}
    public void setUsuario(Usuarios Usuario){this.Usuario = Usuario;}

any help is huge appreciate.