Hibernate Cascade all-delete-orphan Example

0

In this article, we will use hibernate to build a model with parent/child relationship and then analyse the behavior when we try to delete a parent without deleting the child. At times we may want the children to be automatically deleted when we delete the parent. We will see how we can use the cascading attribute to achieve it.

Parent Child Relationship

Below model is between order and order item. The association from Order to OrderItem is a one-to-many association whereas the inverse association from OrderItem to Order is many-to-one association.

Order-OrderItem Relationship

Order-OrderItem Relationship

Here is the java implementation of Order as you can see it maintains a set of OrderItems related to it.

Order.java:

package com.javarticles.hibernate;

import java.util.HashSet;
import java.util.Set;

public class Order {
    private Long orderKey;
    private String orderNbr;
    private Set<OrderItem> orderItems;

    public Long getOrderKey() {
        return orderKey;
    }

    public void setOrderKey(Long orderKey) {
        this.orderKey = orderKey;
    }

    public String getOrderNbr() {
        return orderNbr;
    }

    public void setOrderNbr(String orderNbr) {
        this.orderNbr = orderNbr;
    }
    
    public void addOrderItem(String desc, Long qty) {
        OrderItem orderItem = new OrderItem();
        orderItem.setOrderItemDesc(desc);
        orderItem.setOrderItemQty(qty);
        Set<OrderItem> orderItems = getOrderItems();
        if (orderItems == null) {
            orderItems = new HashSet<>();
            setOrderItems(orderItems);
        }
        orderItems.add(orderItem);
        orderItem.setOrderItemOrder(this);
    }

    public Set<OrderItem> getOrderItems() {
        return orderItems;
    }

    public void setOrderItems(Set<OrderItem> orderItems) {
        this.orderItems = orderItems;
    }

}

Hibernate will use getOrder() to map it to a foreign key column in orders table.

OrderItem:

package com.javarticles.hibernate;

public class OrderItem {
    private Long orderItemKey;
    private String orderItemDesc;
    private Long orderItemQty;
    private Order orderItemOrder;

    public Long getOrderItemKey() {
        return orderItemKey;
    }

    public void setOrderItemKey(Long orderItemKey) {
        this.orderItemKey = orderItemKey;
    }

    public String getOrderItemDesc() {
        return orderItemDesc;
    }

    public void setOrderItemDesc(String orderItemDesc) {
        this.orderItemDesc = orderItemDesc;
    }

    public Long getOrderItemQty() {
        return orderItemQty;
    }

    public void setOrderItemQty(Long orderItemQty) {
        this.orderItemQty = orderItemQty;
    }

    public Order getOrderItemOrder() {
        return orderItemOrder;
    }

    public void setOrderItemOrder(Order orderItemOrder) {
        this.orderItemOrder = orderItemOrder;
    }
}

Here is the ORM of Order and OrderItem.
Let’s look at the relation from OrderItem to Order.

<many-to-one name="orderItemOrder" column="order_id"
			class="com.javarticles.hibernate.Order" not-null="true" />

This relation is MUST for a parent-child model. Column ORDER_ID of table ORDER_ITEMS is a foreign key to the primary key of ORDER table.

errorhbm/order.hbm.xml:

<!DOCTYPE hibernate-mapping
PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="com.javarticles.hibernate">

	<class name="com.javarticles.hibernate.Order" table="orders">
		<id name="orderKey" column="order_id" type="java.lang.Long"
			unsaved-value="null">
			<generator class="native" />
		</id>
		<property name="orderNbr" column="nbr" type="string" length="30" />
		<set name="orderItems" lazy="true" inverse="true" table="order_items">
			<key column="order_id" />
			<one-to-many class="com.javarticles.hibernate.OrderItem" />
		</set>
	</class>

	<class name="com.javarticles.hibernate.OrderItem" table="order_items">
		<id name="orderItemKey" column="order_item_id" type="java.lang.Long"
			unsaved-value="null">
			<generator class="native" />
		</id>
		<many-to-one name="orderItemOrder" column="order_id"
			class="com.javarticles.hibernate.Order" not-null="true" />
		<property name="orderItemDesc" column="order_item_desc" type="string"
			length="45" />
        <property name="orderItemQty" column="qty" type="java.lang.Long" not-null="true" length="3"/>
	</class>

</hibernate-mapping>

Since Order also contains a set of OrderItems, association between them is a bidirectional.
In order to manage the association, we have addOrderItem() method in Order.

public void addOrderItem(String desc, Long qty) {
        OrderItem orderItem = new OrderItem();
        orderItem.setOrderItemDesc(desc);
        orderItem.setOrderItemQty(qty);
        Set<OrderItem> orderItems = getOrderItems();
        if (orderItems == null) {
            orderItems = new HashSet<>();
            setOrderItems(orderItems);
        }
        orderItems.add(orderItem);
        orderItem.setOrderItemOrder(this);
    }

This is all good but what about mapping one-to-many association? Here is the mapping in Order class.

<set name="orderItems" lazy="true" inverse="true" table="order_items">
			<key column="order_id" />
			<one-to-many class="com.javarticles.hibernate.OrderItem" />
		</set>

We have defined that the set is populated based on the foreign key ORDER_ITEM column of ORDERS table. The <set> element contains <key> and <one-to-many> element. The column mapping defined by the <key> element is a foreign key column ORDER_ID of the associated ORDER_ITEMStable, column is specified in attribute column. <one-to-many> contains the child class.

errorhbm/hibernate.cfg.xml:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-configuration PUBLIC
		"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
		"http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
	<session-factory>
		<property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property>
		<property name="hibernate.connection.password">mnrpass</property>
		<property name="hibernate.connection.url">jdbc:mysql://localhost/test</property>
		<property name="hibernate.connection.username">root</property>
		<property name="hibernate.dialect">org.hibernate.dialect.MySQLDialect</property>
		<property name="hbm2ddl.auto">update</property>
		<property name="show_sql">true</property>
		<property name="current_session_context_class">thread</property>
		<mapping resource="errorhbm/order.hbm.xml" />
	</session-factory>
</hibernate-configuration>

In the below example, we create order and order item and then try to delete order. It throws an exception Cannot delete or update a parent row: a foreign key constraint fails

HibernateWithoutCascadeDeleteExample:

package com.javarticles.hibernate;

import java.io.IOException;

import org.hibernate.MappingException;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.cfg.Configuration;

public class HibernateWithoutCascadeDeleteExample {

    public static void main(String[] args) throws MappingException, IOException {
        Configuration configuration = new Configuration().configure("errorhbm/hibernate.cfg.xml");
        SessionFactory sessionFactory = configuration.buildSessionFactory();
        try {
            Session session = sessionFactory.getCurrentSession();
            Transaction tx = session.getTransaction();
            tx.begin();
            Order order = new Order();
            order.setOrderNbr("ORN8118");
            order.addOrderItem("Laptop", 2L);            
            session.save(order);
            OrderItem orderItem = order.getOrderItems().iterator().next();
            session.save(orderItem);
            tx.commit();

            session = sessionFactory.getCurrentSession();
            tx = session.getTransaction();
            tx.begin();
            order = session.load(Order.class, order.getOrderKey());
            session.delete(order);
            tx.commit();

            session = sessionFactory.getCurrentSession();
            tx = session.getTransaction();
            tx.begin();
            Object orderItemDeleted = session.get(OrderItem.class, orderItem.getOrderItemKey());
            if (orderItemDeleted == null) {
                System.out.println("OrderItem is deleted");
            }
            tx.commit();
        } finally {
            sessionFactory.close();
        }
    }
}

Output:

Hibernate: insert into orders (nbr) values (?)
Hibernate: insert into order_items (order_id, order_item_desc, qty) values (?, ?, ?)
Hibernate: select order0_.order_id as order_id1_1_0_, order0_.nbr as nbr2_1_0_ from orders order0_ where order0_.order_id=?
Hibernate: delete from orders where order_id=?
17:13| WARN | SqlExceptionHelper.java 127 | SQL Error: 1451, SQLState: 23000
17:13| ERROR | SqlExceptionHelper.java 129 | Cannot delete or update a parent row: a foreign key constraint fails (`test`.`order_items`, CONSTRAINT `FKbioxgbv59vetrxe0ejfubep1w` FOREIGN KEY (`order_id`) REFERENCES `orders` (`order_id`))
17:13| INFO | AbstractBatchImpl.java 193 | HHH000010: On release of batch it still contained JDBC statements
17:13| ERROR | SessionImpl.java 2921 | HHH000346: Error during managed flush [could not execute statement]17:13| INFO | DriverManagerConnectionProviderImpl.java 264 | HHH000030: Cleaning up connection pool [jdbc:mysql://localhost/test]
Exception in thread "main" org.hibernate.exception.ConstraintViolationException: could not execute statement
	at org.hibernate.exception.internal.SQLExceptionTypeDelegate.convert(SQLExceptionTypeDelegate.java:59)
	at org.hibernate.exception.internal.StandardSQLExceptionConverter.convert(StandardSQLExceptionConverter.java:42)
	at org.hibernate.engine.jdbc.spi.SqlExceptionHelper.convert(SqlExceptionHelper.java:109)
	at org.hibernate.engine.jdbc.spi.SqlExceptionHelper.convert(SqlExceptionHelper.java:95)
	at org.hibernate.engine.jdbc.internal.ResultSetReturnImpl.executeUpdate(ResultSetReturnImpl.java:207)
	at org.hibernate.engine.jdbc.batch.internal.NonBatchingBatch.addToBatch(NonBatchingBatch.java:45)
	at org.hibernate.persister.entity.AbstractEntityPersister.delete(AbstractEntityPersister.java:3132)
	at org.hibernate.persister.entity.AbstractEntityPersister.delete(AbstractEntityPersister.java:3369)
	at org.hibernate.action.internal.EntityDeleteAction.execute(EntityDeleteAction.java:97)
	at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:447)
	at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:333)
	at org.hibernate.event.internal.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:335)
	at org.hibernate.event.internal.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:39)
	at org.hibernate.internal.SessionImpl.flush(SessionImpl.java:1224)
	at org.hibernate.internal.SessionImpl.managedFlush(SessionImpl.java:464)
	at org.hibernate.internal.SessionImpl.flushBeforeTransactionCompletion(SessionImpl.java:2890)
	at org.hibernate.internal.SessionImpl.beforeTransactionCompletion(SessionImpl.java:2266)
	at org.hibernate.engine.jdbc.internal.JdbcCoordinatorImpl.beforeTransactionCompletion(JdbcCoordinatorImpl.java:485)
	at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl.beforeCompletionCallback(JdbcResourceLocalTransactionCoordinatorImpl.java:144)
	at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl.access$100(JdbcResourceLocalTransactionCoordinatorImpl.java:37)
	at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl$TransactionDriverControlImpl.commit(JdbcResourceLocalTransactionCoordinatorImpl.java:214)
	at org.hibernate.engine.transaction.internal.TransactionImpl.commit(TransactionImpl.java:64)
	at com.javarticles.hibernate.HibernateWithoutCascadeDeleteExample.main(HibernateWithoutCascadeDeleteExample.java:33)
Caused by: com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException: Cannot delete or update a parent row: a foreign key constraint fails (`test`.`order_items`, CONSTRAINT `FKbioxgbv59vetrxe0ejfubep1w` FOREIGN KEY (`order_id`) REFERENCES `orders` (`order_id`))

If we want the OrderItems to be delete as we delete the parent Order, we need to use cascade="all-delete-orphan".
With the usage of cascade="all-delete-orphan", following will happen.

  1. Any newly instantiated OrderItem becomes persistent if the OrderItem is referenced by the Order.
  2. If we delete Order, the associated items will automatically gets deleted.
  3. If we remove any persistent OrderItem from a persistent Order‘s set of OrderItems, the OrderItem will automatically gets deleted.

order.hbm.xml:

<!DOCTYPE hibernate-mapping
PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="com.javarticles.hibernate">

	<class name="com.javarticles.hibernate.Order" table="orders">
		<id name="orderKey" column="order_id" type="java.lang.Long"
			unsaved-value="null">
			<generator class="native" />
		</id>
		<property name="orderNbr" column="nbr" type="string" length="30" />
		<set name="orderItems" lazy="true" inverse="true" table="order_items"
			cascade="all-delete-orphan">
			<key column="order_id" />
			<one-to-many class="com.javarticles.hibernate.OrderItem" />
		</set>
	</class>

	<class name="com.javarticles.hibernate.OrderItem" table="order_items">
		<id name="orderItemKey" column="order_item_id" type="java.lang.Long"
			unsaved-value="null">
			<generator class="native" />
		</id>
		<many-to-one name="orderItemOrder" column="order_id"
			class="com.javarticles.hibernate.Order" not-null="true" />
		<property name="orderItemDesc" column="order_item_desc" type="string"
			length="45" />
        <property name="orderItemQty" column="qty" type="java.lang.Long" not-null="true" length="3"/>
	</class>

</hibernate-mapping>

HibernateCascadeDeleteExample:

package com.javarticles.hibernate;

import java.io.IOException;

import org.hibernate.MappingException;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.cfg.Configuration;

public class HibernateCascadeDeleteExample {

    public static void main(String[] args) throws MappingException, IOException {
        Configuration configuration = new Configuration().configure();
        SessionFactory sessionFactory = configuration.buildSessionFactory();
        try {
            Session session = sessionFactory.getCurrentSession();
            Transaction tx = session.getTransaction();
            tx.begin();
            Order order = new Order();
            order.setOrderNbr("ORN8118");
            order.addOrderItem("Laptop", 2L);            
            session.save(order);
            OrderItem orderItem = order.getOrderItems().iterator().next();
            tx.commit();

            session = sessionFactory.getCurrentSession();
            tx = session.getTransaction();
            tx.begin();
            order = session.load(Order.class, order.getOrderKey());
            session.delete(order);
            tx.commit();

            session = sessionFactory.getCurrentSession();
            tx = session.getTransaction();
            tx.begin();
            Object orderItemDeleted = session.get(OrderItem.class, orderItem.getOrderItemKey());
            if (orderItemDeleted == null) {
                System.out.println("OrderItem is deleted");
            }
            tx.commit();
        } finally {
            sessionFactory.close();
        }
    }
}

Since we are using cascade="all-delete-orphan", there is no foreign key error.

Output:

Hibernate: insert into orders (nbr) values (?)
Hibernate: insert into order_items (order_id, order_item_desc, qty) values (?, ?, ?)
Hibernate: select order0_.order_id as order_id1_1_0_, order0_.nbr as nbr2_1_0_ from orders order0_ where order0_.order_id=?
Hibernate: select orderitems0_.order_id as order_id2_1_0_, orderitems0_.order_item_id as order_it1_0_0_, orderitems0_.order_item_id as order_it1_0_1_, orderitems0_.order_id as order_id2_0_1_, orderitems0_.order_item_desc as order_it3_0_1_, orderitems0_.qty as qty4_0_1_ from order_items orderitems0_ where orderitems0_.order_id=?
Hibernate: delete from order_items where order_item_id=?
Hibernate: delete from orders where order_id=?
Hibernate: select orderitem0_.order_item_id as order_it1_0_0_, orderitem0_.order_id as order_id2_0_0_, orderitem0_.order_item_desc as order_it3_0_0_, orderitem0_.qty as qty4_0_0_ from order_items orderitem0_ where orderitem0_.order_item_id=?
OrderItem is deleted
17:48| INFO | DriverManagerConnectionProviderImpl.java 264 | HHH000030: Cleaning up connection pool [jdbc:mysql://localhost/test]

Download source code

This was an example about Hibernate Cascade all-delete-orphan.

You can download the source code here: hibernateCascadeDeleteExample.zip
Share.

Comments are closed.