I released five new sample lessons from my Test With Spring course: Introduction to Spock Framework

Spring Data JPA Tutorial: Adding Custom Methods Into All Repositories

The previous part of this tutorial taught us how we can add custom methods into a single repository.

Although that is a very useful skill, it doesn’t help us when we have to add the same method into all repositories of our application.

Luckily for us, Spring Data provides a way to add custom methods into all repositories.

My "Test With Spring" course helps you to write unit, integration, and end-to-end tests for Spring and Spring Boot Web Apps:

CHECK IT OUT >>


This blog post describes how we can add custom methods into all Spring Data JPA repositories. During this blog post we will implement a method that fulfills the following requirements:

  • It deletes the entity whose id is given as a method parameter.
  • It returns an Optional which contains the deleted entity. If no entity is found with the given id, it returns an empty Optional.

Let’s get started.

Additional Reading:

If you are not familiar with Spring Data JPA, you should read the following blog posts before you continue reading this blog post:

Creating a Base Repository Interface

When we want to add custom methods into all Spring Data JPA repositories, the first thing that we have to is to create a base interface which declares the custom methods.

We can create the base repository interface by following these steps:

  1. Create an interface called BaseRepository that has the following type parameters:
    • The T type parameter is the type of the managed entity.
    • The ID type parameter is the type of the managed entity’s primary key. Note that this type parameter must extend the Serializable interface.
  2. Extend the Repository interface and provide the required type parameters.
  3. Annotate the created interface with the @NoRepositoryBean annotation. This ensures that Spring Data JPA won’t try to create an implementation for the BaseRepository interface.
  4. Add the deleteById() method to the created interface. This methods takes the id of the deleted entity as a method parameter and returns an Optional<T> object.

The source code of the BaseRepository interface looks as follows:

import org.springframework.data.repository.NoRepositoryBean;
import org.springframework.data.repository.Repository;

import java.io.Serializable;
import java.util.Optional;

@NoRepositoryBean
public interface BaseRepository<T, ID extends Serializable> extends Repository<T, ID> {

    Optional<T> deleteById(ID id);
}
Remember that the base repository interface must extend the same interface that is extended by your repository interfaces. In other words, if your repository interfaces extend the CrudRepository interface, the BaseRepository interface must extend it as well.

Additional Reading:

After we have created our base repository interface, we have to naturally implement it. Let’s find out how we can create a base repository class that deletes the requested entity by using EntityManager.

Implementing the Base Repository Interface

The SimpleJpaRepository is the default implementation of Spring Data JPA repository interfaces. Because we want to add the methods declared by our base repository interface into all repositories, we have to create a custom base repository class that extends the SimpleJpaRepository class and implements the BaseRepository interface.

We can create a custom base repository class by following these steps:

  1. Create a BaseRepositoryImpl class that has two type parameters:
    • The T type parameter is the type of the managed entity.
    • The ID type parameter is the type of the managed entity’s primary key. Note that this type parameter must extend the Serializable interface.
  2. Ensure that the BaseRepositoryImpl class extends the SimpleJpaRepository class and implements the BaseRepository interface. Remember to provide the required type parameters.
  3. Add a private EntityManager field to created class and mark the field as final.
  4. Add a constructor that takes two constructor arguments:
    1. A Class object that represents the managed entity class.
    2. An EntityManager object.
  5. Implement the constructor by invoking the constructor of the super class (SimpleJpaRepository) and storing a reference to the EntityManager object into the private EntityManager field.
  6. Add the deleteById() method to created class and implement it by following thse steps:
    1. Annotate the method with the @Transactional annotation. This ensures that the method is always invoked inside a read-write transaction.
    2. Find the deleted entity object by using the provided id as a search criteria.
    3. If an entity object is found, delete the found entity object and return an Optional object that contains the deleted entity object.
    4. If no entity object is found, return an empty Optional object.

The source code of the BaseRepositoryImpl class looks as follows:

import org.springframework.data.jpa.repository.support.SimpleJpaRepository;
import org.springframework.transaction.annotation.Transactional;

import javax.persistence.EntityManager;
import java.io.Serializable;
import java.util.Optional;

public class BaseRepositoryImpl <T, ID extends Serializable>
        extends SimpleJpaRepository<T, ID>  implements BaseRepository<T, ID> {
		
    private final EntityManager entityManager;

    public BaseRepositoryImpl(Class<T> domainClass, EntityManager entityManager) {
        super(domainClass, entityManager);
        this.entityManager = entityManager;
    }

    @Transactional
    @Override
    public Optional<T> deleteById(ID id) {
        T deleted = entityManager.find(this.getDomainClass(), id);
        Optional<T> returned = Optional.empty();

        if (deleted != null) {
            entityManager.remove(deleted);
            returned = Optional.of(deleted);
        }
        return returned;
    }
}

After we have created our base repository class, we have to create a custom RepositoryFactoryBean. Let’s find out how we can do it.

If you are using Spring Data JPA 1.9.X or newer, you can skip this step because creating the RepositoryFactoryBean is not mandatory anymore (you can still do it if you want to).

Creating a Custom RepositoryFactoryBean

The RepositoryFactoryBean is a component that is responsible of providing implementations for Spring Data JPA repository interfaces. Because we want to replace the default implementation (SimpleJpaRepository) with our custom implementation (BaseRepositoryImpl), we have to create a custom RepositoryFactoryBean.

We can do this by following these steps:

  1. Create a BaseRepositoryFactoryBean class that has three type parameters:
    • The R type parameter is the type of the repository. This type parameter must extend the JpaRepository interface.
    • The T type parameter is the type of the managed entity.
    • The I type parameter is the type of the entity’s private key. Note that this type parameter must extend the Serializable interface.
  2. Extend the JpaRepositoryFactoryBean class and provide the required type parameters.
  3. Add a private static BaseRepositoryFactory class to the created class and extend the JpaRepositoryFactory class. Implement this class by following these steps:
    1. Add two type parameters to the BaseRepositoryFactory class:
      • The T type parameter is the type of the managed entity.
      • The I type parameter is the type of the entity’s private key. Note that this type parameter must extend the Serializable interface.
    2. Add a private final EntityManager field to the BaseRepositoryFactory class and mark the field as final
    3. Add constructor that takes an EntityManager object as a constructor argument and implement it by following these steps:
      1. Invoke the constructor of the super class and pass the EntityManager object as constructor argument.
      2. Store a reference to the EntityManager object into the private EntityManager field.
    4. Override the getTargetRepository(RepositoryMetadata metadata) method and implement it by returning a new BaseRepositoryImpl object.
    5. Override the getRepositoryBaseClass(RepositoryMetadata metadata) method and implement it by returning BaseRepositoryImpl.class.
  4. Override the createRepositoryFactory(EntityManager em) method of the JpaRepositoryFactoryBean class and implement it by returning a new BaseRepositoryFactory object.

The source code of the BaseRepositoryFactoryBean class looks as follows:

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.support.JpaRepositoryFactory;
import org.springframework.data.jpa.repository.support.JpaRepositoryFactoryBean;
import org.springframework.data.repository.core.RepositoryMetadata;
import org.springframework.data.repository.core.support.RepositoryFactorySupport;

import javax.persistence.EntityManager;
import java.io.Serializable;

public class BaseRepositoryFactoryBean<R extends JpaRepository<T, I>, T,
        I extends Serializable> extends JpaRepositoryFactoryBean<R, T, I> {

    @Override
    protected RepositoryFactorySupport createRepositoryFactory(EntityManager em) {
        return new BaseRepositoryFactory(em);
    }

    private static class BaseRepositoryFactory<T, I extends Serializable>
            extends JpaRepositoryFactory {

        private final EntityManager em;

        public BaseRepositoryFactory(EntityManager em) {
            super(em);
            this.em = em;
        }

        @Override
        protected Object getTargetRepository(RepositoryMetadata metadata) {
            return new BaseRepositoryImpl<T, I>((Class<T>) metadata.getDomainType(), em);
        }

        @Override
        protected Class<?> getRepositoryBaseClass(RepositoryMetadata metadata) {
            return BaseRepositoryImpl.class;
        }
    }
}

Let’s find out how we have to configure Spring Data JPA to use our custom RepositoryFactoryBean.

Configuring Spring Data JPA

We can configure Spring Data JPA by using one of the following methods:

Configuring Spring Data JPA When We Use Spring Data JPA < 1.9.X

If we are using Spring Data JPA < 1.9.X, we can configure the repository factory bean by setting the value of the repositoryFactoryBeanClass attribute of the @EnableJpaRepositories annotation.

The relevant part of the PersistenceContext class looks as follows:

import org.springframework.context.annotation.Configuration;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.transaction.annotation.EnableTransactionManagement;

@Configuration
@EnableJpaAuditing(dateTimeProviderRef = "dateTimeProvider")
@EnableJpaRepositories(basePackages = {"net.petrikainulainen.springdata.jpa.todo"},
        repositoryFactoryBeanClass = BaseRepositoryFactoryBean.class
)
@EnableTransactionManagement
class PersistenceContext {
	
}
If you are using XML configuration, you can configure the used RepositoryFactoryBean by setting the value of the repository element’s factory-class attribute.

Configuring Spring Data JPA When We Use Spring Data JPA 1.9.X or newer

If we are using Spring Data JPA 1.9.X or newer, we don’t have to create the RepositoryFactoryBean class. We can simply configure the base repository class by setting the value of the repositoryBaseClass attribute of the @EnableJpaRepositories annotation.

The relevant part of the PersistenceContext class looks as follows:

import org.springframework.context.annotation.Configuration;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.transaction.annotation.EnableTransactionManagement;

@Configuration
@EnableJpaAuditing(dateTimeProviderRef = "dateTimeProvider")
@EnableJpaRepositories(basePackages = {"net.petrikainulainen.springdata.jpa.todo"},
        repositoryBaseClass = BaseRepositoryImpl.class
)
@EnableTransactionManagement
class PersistenceContext {
	
}
If you are using XML configuration, you can configure the repository base class by setting the value of the repository element’s repository-base-class attribute.

We are now ready to add the add the new deleteById() method into our repository interfaces. Let’s find out how we can do it.

Modifying the Actual Repository Interfaces

Before we can use our new deleteById() method, we have to make some changes to our repository interfaces. We can make these changes to the TodoRepository interface by following these steps:

  1. Extend the BaseRepository interface and provide the following type parameters:
    • The type of the managed entity is Todo.
    • The type of the entity’s private key is Long.
  2. Remove the “old” delete() method.

The source code of the TodoRepository interface looks as follows:

import net.petrikainulainen.springdata.jpa.common.BaseRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;

import java.util.List;
import java.util.Optional;


interface TodoRepository extends BaseRepository<Todo, Long> {

    List<Todo> findAll();

    @Query("SELECT t FROM Todo t WHERE " +
            "LOWER(t.title) LIKE LOWER(CONCAT('%',:searchTerm, '%')) OR " +
            "LOWER(t.description) LIKE LOWER(CONCAT('%',:searchTerm, '%')) " +
            "ORDER BY t.title ASC")
    List<Todo> findBySearchTerm(@Param("searchTerm") String searchTerm);

    Optional<Todo> findOne(Long id);

    void flush();

    Todo save(Todo persisted);
}

That’s it. We can now use our new deleteById() method. Let’s summarize what we learned from this blog post.

My "Test With Spring" course helps you to write unit, integration, and end-to-end tests for Spring and Spring Boot Web Apps:

CHECK IT OUT >>

Summary

This blog post has taught us three things:

  • If we want to add custom methods into all repositories, we have to replace the default repository implementation (SimpleJpaRepository) with our own repository implementation.
  • If we are using Spring Data JPA 1.9.X or newer, we don’t have to create a custom RepositoryFactoryBean.
  • Our repository interfaces must extend the base repository interface that declares the methods which are added into all repositories.

The next part of my Spring Data JPA tutorial describes how we can write integration tests for Spring Data JPA repositories.

P.S. You can get the example application of this blog post from Github.

If you want to learn how to use Spring Data JPA, you should read my Spring Data JPA tutorial.

About the Author

Petri Kainulainen is passionate about software development and continuous improvement. He is specialized in software development with the Spring Framework and is the author of Spring Data book.

About Petri Kainulainen →

4 comments… add one
  • And what is the best practice to mark this class that extends SimpleJpaRepository to be annotated with @Repository and be able to inject the dependencies using constructor annotations?

    Reply
  • Example for Spring-Data-Cassandra? I am not able to get it wokring.

    Reply

Leave a Comment