HelloKoding

Practical coding guides

Deleting Entity with JPQL, CascadeType, and orphanRemoval in JPA and Hibernate

In this tutorial, you will learn to delete data using JPA and Hibernate Entity with JPQL, CascadeType.REMOVE or CascadeType.ALL, and orphanRemoval. You will also learn to do JPA integration test in Spring Boot with in-memory database and @DataJpaTest. Let’s start building an example with Spring Boot and Spring Data JPA

What you will need

  • JDK 8+ or OpenJDK 8+
  • Maven 3+
  • MySQL Server 5+ or Docker CE 18+

Init project structure and dependencies

Project structure

├── src
│   ├── main
│   │   ├── java
│   │   │   └── com
│   │   │       └── hellokoding
│   │   │           ├── jpa
│   │   │           │   ├── book
│   │   │           │   │   ├── Book.java
│   │   │           │   │   ├── BookRepository.java
│   │   │           │   │   ├── Category.java
│   │   │           │   │   └── CategoryRepository.java
│   │   │           │   └── JpaApplication.java
│   │   │           └── springboot
│   │   └── resources
│   │       └── application.properties
│   └── test
│       └── java
│           └── com
│               └── hellokoding
│                   └── jpa
│                       └── book
│                           └── DeletingDataTest.java
├── Dockerfile
└── pom.xml

Project dependencies

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.hellokoding.jpa</groupId>
    <artifactId>jpa-hibernate-crud-deleting-entity</artifactId>
    <version>1.0-SNAPSHOT</version>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.4.RELEASE</version>
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
        </dependency>
        <dependency>
            <groupId>org.hsqldb</groupId>
            <artifactId>hsqldb</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

Define JPA Entities and Repositories

Book.java

package com.hellokoding.jpa.book;

import lombok.*;

import javax.persistence.*;
import java.util.Objects;

@Data
@RequiredArgsConstructor
@NoArgsConstructor
@ToString(exclude = "category")

@Entity
public class Book{
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private int id;

    private @NonNull String name;

    @ManyToOne
    @JoinColumn
    private Category category;

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof Book)) return false;
        Book book = (Book) o;
        return Objects.equals(name, book.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name);
    }
}

Category.java

package com.hellokoding.jpa.book;

import lombok.*;

import javax.persistence.*;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;

@Data
@NoArgsConstructor
@ToString(exclude = "books")

@Entity
public class Category {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private int id;

    private String name;

    @OneToMany(mappedBy = "category", cascade = CascadeType.ALL, orphanRemoval = true)
    private Set<Book> books;

    public Category(String name, Book... books) {
        this.name = name;
        this.books = Stream.of(books).collect(Collectors.toSet());
        this.books.forEach(x -> x.setCategory(this));
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof Category)) return false;
        Category category = (Category) o;
        return Objects.equals(name, category.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name);
    }
}

BookRepository.java

package com.hellokoding.jpa.book;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;

import java.util.List;

public interface BookRepository extends JpaRepository<Book, Integer> {
    @Modifying
    @Query("DELETE Book b WHERE b.category.id = ?1")
    void deleteByCategoryId(int categoryId);

    List<Book> findByCategoryId(int categoryId);
}

CategoryRepository.java

package com.hellokoding.jpa.book;

import org.springframework.data.jpa.repository.JpaRepository;

public interface CategoryRepository extends JpaRepository<Category, Integer> {
}

Config and Test

Application Configuration

application.properties

spring.jpa.hibernate.ddl-auto=create-drop
spring.jpa.show-sql=true

JpaApplication.java

package com.hellokoding.jpa;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class JpaApplication {
    public static void main(String[] args) {
        SpringApplication.run(JpaApplication.class, args);
    }
}

Integration test Deleting Data with JPA, Hibernate and @DataJpaTest

DeletingDataTest.java

package com.hellokoding.jpa.book;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManager;
import org.springframework.test.context.junit4.SpringRunner;

import static org.assertj.core.api.Assertions.assertThat;

@RunWith(SpringRunner.class)
@DataJpaTest
public class DeletingDataTest {
    @Autowired
    private TestEntityManager testEntityManager;

    @Autowired
    private BookRepository bookRepository;

    @Autowired
    private CategoryRepository categoryRepository;

    private int givenCategoryId;

    @Before
    public void setUp(){
        // given
        givenCategoryId = testEntityManager.persistAndFlush(new Category("A", new Book("A1"), new Book("A2"))).getId();
    }

    @Test
    public void whenDeleteByJPQL_thenSuccess() {
        // when
        bookRepository.deleteByCategoryId(givenCategoryId);

        // then
        assertThat(bookRepository.findByCategoryId(givenCategoryId)).hasSize(0);
    }

    @Test
    public void whenDeleteByOrphanRemoval_thenSuccess() {
        // when
        Category category = categoryRepository.findById(givenCategoryId).get();
        Book book = category.getBooks().iterator().next();
        int bookId = book.getId();
        category.getBooks().remove(book);
        categoryRepository.flush();

        // then
        assertThat(bookRepository.findById(bookId)).isEmpty();
    }

    @Test
    public void whenDeleteByCascadeType_thenSuccess() {
        // when
        Category category = categoryRepository.findById(givenCategoryId).get();
        categoryRepository.delete(category);

        // then
        assertThat(bookRepository.findByCategoryId(givenCategoryId)).hasSize(0);
    }
}

Keys to take away

  • Using JPQL, you don’t need retrieve the data before deletion, hence this is a best approach for bulk delete.
  • Using cascade = CascadeType.REMOVE or cascade = CascadeType.ALL to delete the associated entities when deleting the relationship owner. JPA and Hibernate will delete one-by-one so considering the performance tradeoff when using this option.
  • Using orphanRemoval if you need to delete the associated entities only.

Source code

https://github.com/hellokoding/hellokoding-courses/tree/master/jpa-hibernate-examples/jpa-hibernate-crud-deleting-entity

Follow HelloKoding