1. Overview
In this tutorial, we’ll have a look at how deleting is done in Spring Data JPA.
2. Sample Entity
As we know from the Spring Data JPA reference documentation, repository interfaces provide us some basic support for entities.
If we have an entity, like a Book:
@Entity public class Book { @Id @GeneratedValue private Long id; private String title; // standard constructors // standard getters and setters }
Then, we can extend Spring Data JPA’s CrudRepository to give us access to CRUD operations on Book:
@Repository public interface BookRepository extends CrudRepository<Book, Long> {}
3. Delete from Repository
Among others, CrudRepository contains two methods: deleteById and deleteAll.
Let’s test these methods directly from our BookRepository:
@RunWith(SpringRunner.class) @SpringBootTest(classes = {Application.class}) public class DeleteFromRepositoryUnitTest { @Autowired private BookRepository repository; Book book1; Book book2; List<Book> books; // data initialization @Test public void whenDeleteByIdFromRepository_thenDeletingShouldBeSuccessful() { repository.deleteById(book1.getId()); assertThat(repository.count()).isEqualTo(1); } @Test public void whenDeleteAllFromRepository_thenRepositoryShouldBeEmpty() { repository.deleteAll(); assertThat(repository.count()).isEqualTo(0); } }
And even though we are using CrudRepository, note that these same methods exist for other Spring Data JPA interfaces like JpaRepository or PagingAndSortingRepository.
4. Derived Delete Query
We can also derive query methods for deleting entities. There is a set of rules for writing them, but let’s just focus on the simplest example.
A derived delete query must start with deleteBy, followed by the name of the selection criteria. These criteria must be provided in the method call.
Let’s say that we want to delete Books by title. Using the naming convention, we’d start with deleteBy and list title as our criteria:
@Repository public interface BookRepository extends CrudRepository<Book, Long> { long deleteByTitle(String title); }
The return value, of type long, indicates how many records the method deleted.
Let’s write a test and make sure that is correct:
@Test @Transactional public void whenDeleteFromDerivedQuery_thenDeletingShouldBeSuccessful() { long deletedRecords = repository.deleteByTitle("The Hobbit"); assertThat(deletedRecords).isEqualTo(1); }
Persisting and deleting objects in JPA requires a transaction, that’s why we should use a @Transactional annotation when using these derived delete queries, to make sure a transaction is running. This is explained in detail in the ORM with Spring documentation.
5. Custom Delete Query
The method names for derived queries can get quite long, and they are limited to just a single table.
When we need something more complex, we can write a custom query using @Query and @Modifying together.
Let’s check the equivalent code for our derived method from earlier:
@Modifying @Query("delete from Book b where b.title=:title") void deleteBooks(@Param("title") String title);
Again, we can verify it works with a simple test:
@Test @Transactional public void whenDeleteFromCustomQuery_thenDeletingShouldBeSuccessful() { repository.deleteBooks("The Hobbit"); assertThat(repository.count()).isEqualTo(1); }
Both solutions presented above are similar and achieve the same result. However, they take a slightly different approach.
The @Query method creates a single JPQL query against the database. By comparison, the deleteBy methods execute a read query, then delete each of the items one by one.
6. Delete in Relationships
Let’s see now what happens when we have relationships with other entities.
Assume we have a Category entity, that has a OneToMany association with the Book entity:
@Entity public class Category { @Id @GeneratedValue private Long id; private String name; @OneToMany(mappedBy = "category", cascade = CascadeType.ALL, orphanRemoval = true) private List<Book> books; // standard constructors // standard getters and setters }
The CategoryRepository can just be an empty interface that extends CrudRepository:
@Repository public interface CategoryRepository extends CrudRepository<Category, Long> {}
We should also modify the Book entity to reflect this association:
@ManyToOne private Category category;
Let’s now add two categories and associate them with the books we currently have. Now, if we try to delete the categories, the books will also be deleted:
@Test public void whenDeletingCategories_thenBooksShouldAlsoBeDeleted() { categoryRepository.deleteAll(); assertThat(bookRepository.count()).isEqualTo(0); assertThat(categoryRepository.count()).isEqualTo(0); }
This is not bi-directional, though. That means that if we delete the books, the categories are still there:
@Test public void whenDeletingBooks_thenCategoriesShouldAlsoBeDeleted() { bookRepository.deleteAll(); assertThat(bookRepository.count()).isEqualTo(0); assertThat(categoryRepository.count()).isEqualTo(2); }
We can change this behavior by changing the properties of the relationship, such as the CascadeType.
7. Conclusion
In this article, we looked at different ways to delete entities in Spring Data JPA. We looked at the provided delete methods from CrudRepository, as well as our derived queries or custom ones using @Query annotation.
We also had a look at how deleting is done in relationships. As always, all of the code snippets mentioned in this article can be found on our GitHub repository.