Spring Data JPA Tutorial: Adding Custom Methods to 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.
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.

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.
16 comments… add one
  • Kisna Apr 26, 2016 @ 10:52

    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?

  • ALLAHBAKSH MOHAMMEDALI ASADULLAH Nov 12, 2016 @ 7:27

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

  • Ali Jul 31, 2017 @ 22:59

    Hi, thank you for your useful post
    I'm trying to do it in Spring Boot, but there is a problem, can you help me please?
    https://stackoverflow.com/questions/45403857/extend-simplejparepository

  • Vinoth Rajendran Nov 23, 2017 @ 11:37

    How this helps multiple data sources? Because your BaseRepository class has only one entity manager(default transaction) will be set.

    • Petri Nov 23, 2017 @ 21:51

      Like you said, this doesn't help you at all. This blog post simply demonstrates how you can add custom methods to all Spring Data JPA repositories. If you want to use multiple data sources, you should take a look at this blog post.

  • Gaurav Jan 14, 2018 @ 6:08

    Hello,

    Could you create a tutorial, on how to call user-defined Stored functions/procedures from SpringDataJpa

    • Petri Jan 14, 2018 @ 13:02

      Hi,

      Thank you for your feedback. I added your idea to my to-do list, and I will write the blog post eventually. In the meantime, you could take a look at this example: Support for stored procedure execution.

  • Ezequiel Mar 7, 2018 @ 21:47

    Hi, i'm facing this compilation error on class BaseRepositoryImpl:

    The return types are incompatible for the inherited methods QueryByExampleExecutor.findOne(Example<S>), SimpleJpaRepository.findOne(Example<S>)

    If you could help i would appreciate!

    • Petri Mar 12, 2018 @ 10:40

      Hi,

      If you are using the same BaseRepositoryImpl class that is described in this blog post, the problem most likely is that this example is not compatible with your Spring Data JPA version. This example works with any Spring Data JPA version as long as the SimpleJpaRepository class doesn't have a method called deleteById().

      It seems that the Spring Data JPA team added this method after 1.7.2 was released (unfortunately I don't know the exact version). In other words, the easiest way to solve this problem is to simple change the name of the method.

      • Ezequiel Mar 13, 2018 @ 21:17

        Right, thanks! Another thing i'm stuck at, when i'm trying to start up the server, i got a error saying:
        Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'testRepository': Invocation of init method failed; nested exception is java.lang.IllegalArgumentException: Failed to create query method public abstract java.lang.Object br.com.framework.repository.BaseRepositoryCustom.insert(java.lang.Object)! No property insert found for type Teste!

        Looks like spring is looking for the "insert" method inside the entity, any idea about this one?
        Thanks again!

        • Ezequiel Mar 13, 2018 @ 23:21

          Solved adding this to a configuration bean:
          @EnableTransactionManagement
          @EnableJpaRepositories(basePackages = {"br.com.framework.repository"},
          repositoryBaseClass = BaseRepositoryImpl.class
          )

          • Petri Mar 14, 2018 @ 20:36

            Hi,

            I am sorry for the delay. I was outside enjoying the last day of my winter holiday. Anyway, it's good to hear that you were able to solve your problem!

  • Anonymous Jun 28, 2018 @ 13:01

    You are God

    • Petri Jul 3, 2018 @ 21:49

      Thanks!

Leave a Reply