Are you looking for an advent calendar? You found it!

Spring Data JPA Tutorial Part Eight: Adding Functionality to a Repository

A hotrod

The previous part of my tutorial described how you can paginate query results with Spring Data JPA. The example application of this blog entry has the same functional requirements, but it will use Querydsl instead of JPA criteria API. This blog entry assumes that you have got experience from both Querydsl And Spring Data JPA. If this is not the case, please read my Spring Data JPA and Querydsl tutorial.

If you have read the previous parts of my Spring Data JPA tutorial, you might have noticed that the described solutions introduced a dependency between the service layer and the Spring Data JPA. This blog entry describes how you can eliminate that dependency (or at least most of it) by adding custom functionality to a Spring Data repository.

Adding Functionality to a Spring Data Repository

You can add custom functionality to a Spring Data repository by following these steps:

  • Create an interface for the custom functionality.
  • Implement the created interface.
  • Create the Spring Data JPA repository.

Enough with the theory. It is time to take a look, how I added custom functionality to the person repository of my example application.

Implementing The Example Application

This Section describes how I added the custom functionality to the person repository of my example application. The steps described previously are discussed with more details in following Subsections.

Creating an interface for the custom functionality.

In order to eliminate the dependency between the service layer and the Spring Data JPA, I created an interface called PaginatingPersonRepository. My goal was to move all pagination and sorting logic behind this interface. The source code of my custom interface is given in following:

public interface PaginatingPersonRepository {

    /**
     * Finds all persons stored in the database.
     * @return
     */
    public List<Person> findAllPersons();

    /**
     * Finds the count of persons stored in the database.
     * @param searchTerm
     * @return
     */
    public long findPersonCount(String searchTerm);

    /**
     * Finds persons for the requested page whose last name starts with the given search term.
     * @param searchTerm    The used search term.
     * @param page  The number of the requested page.
     * @return  A list of persons belonging to the requested page.
     */
    public List<Person> findPersonsForPage(String searchTerm, int page);
}

Implementing the Declared Interface

The next step is to create an implementation for the PaginatingPersonRepository interface. This implementation has following responsibilities:

  • Creating a new QueryDslJPARepository instance and delegating the method calls to it.
  • Obtaining the required Querydsl predicate when a search is performed.
  • Creating the Sort object which is used to specify the sort order of query results.
  • Creating the PageRequest object which id used the specify the wanted page and sort order of the search results.

The implementation of the PaginatingPersonRepository interface is using the PersonPredicates class for creating the required Querydsl predicates. The source code of this class is given in following:

import com.mysema.query.types.Predicate;
import net.petrikainulainen.spring.datajpa.model.QPerson;

public class PersonPredicates {

    public static Predicate lastNameIsLike(final String searchTerm) {
        QPerson person = QPerson.person;
        return person.lastName.startsWithIgnoreCase(searchTerm);
    }
}

The repository architecture of Spring Data JPA is looking for the custom implementation from the package where the repository was found. The name of the class implementing the custom interface must be constructed by adding a special postfix to the name of the custom interface. The default value of this postfix is Impl, but you can change the postfix by adding a repository-impl-postfix attribute to the namespace configuration element of Spring Data JPA. An example of this is given in following:

<!-- Declares the base package for repositories and states that postfix used to identify custom implementations is FooBar. -->
<jpa:repositories base-package="net.petrikainulainen.spring.datajpa.repository" repository-impl-postfix="FooBar"/>

However, I am going to use the default configuration. Thus, the name of my custom repository class must be PaginatingPersonRepositoryImpl.

Note: If you are using Spring Data JPA 1.2.0 (or newer version), the name of the custom repository implementation must follow the following formula: [The name of the actual repository interface][postfix]. Because the example application use the default configuration, the name of the custom repository implementation must be PersonRepositoryImpl.

The source code of the PaginatingPersonRepositoryImpl class is given in the following:

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.repository.support.JpaEntityInformation;
import org.springframework.data.jpa.repository.support.JpaMetamodelEntityInformation;
import org.springframework.data.jpa.repository.support.QueryDslJpaRepository;
import org.springframework.stereotype.Repository;

import javax.annotation.PostConstruct;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;

import static net.petrikainulainen.spring.datajpa.repository.PersonPredicates.lastNameIsLike;

@Repository
public class PaginatingPersonRepositoryImpl implements PaginatingPersonRepository {

    private static final Logger LOGGER = LoggerFactory.getLogger(PaginatingPersonRepositoryImpl.class);

    protected static final int NUMBER_OF_PERSONS_PER_PAGE = 5;

    @PersistenceContext
    private EntityManager entityManager;

    private QueryDslJpaRepository<Person, Long> personRepository;

    public PaginatingPersonRepositoryImpl() {

    }

    @Override
    public List<Person> findAllPersons() {
        LOGGER.debug("Finding all persons");
        
        //Passes the Sort object to the repository
        return personRepository.findAll(sortByLastNameAsc());
    }

    @Override
    public long findPersonCount(String searchTerm) {
        LOGGER.debug("Finding person count with search term: " + searchTerm);

        //Passes the predicate to the repository
        return personRepository.count(lastNameIsLike(searchTerm));
    }

    @Override
    public List<Person> findPersonsForPage(String searchTerm, int page) {
        LOGGER.debug("Finding persons for page " + page + " with search term: " + searchTerm);

        //Passes the predicate and the page specification to the repository
        Page requestedPage =  personRepository.findAll(lastNameIsLike(searchTerm), constructPageSpecification(page));

        return requestedPage.getContent();
    }

    /**
     * Returns a new object which specifies the the wanted result page.
     * @param pageIndex The index of the wanted result page
     * @return
     */
    private Pageable constructPageSpecification(int pageIndex) {
        Pageable pageSpecification = new PageRequest(pageIndex, NUMBER_OF_PERSONS_PER_PAGE, sortByLastNameAsc());
        return pageSpecification;
    }

    /**
     * Returns a Sort object which sorts persons in ascending order by using the last name.
     * @return
     */
    private Sort sortByLastNameAsc() {
        return new Sort(Sort.Direction.ASC, "lastName");
    }


    /**
     * An initialization method which is run after the bean has been constructed.
     * This ensures that the entity manager is injected before we try to use it.
     */
    @PostConstruct
    public void init() {
        JpaEntityInformation<Person, Long> personEntityInfo = new JpaMetamodelEntityInformation<Person, Long>(Person.class, entityManager.getMetamodel());
        personRepository = new QueryDslJpaRepository<Person, Long>(personEntityInfo, entityManager);
    }

    /**
     * This setter method should be used only by unit tests
     * @param personRepository
     */
    protected void setPersonRepository(QueryDslJpaRepository<Person, Long> personRepository) {
        this.personRepository = personRepository;
    }
}

We also have to verify that the created repository implementation is working as expected. This means that unit tests must be written. The source code of the unit tests is given in following:

import com.mysema.query.types.Predicate;
import org.junit.Before;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.repository.support.QueryDslJpaRepository;

import static junit.framework.Assert.assertEquals;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.*;

public class PaginatingPersonRepositoryImplTest {

    private static final int PAGE_INDEX = 2;
    private static final long PERSON_COUNT = 5;
    private static final String PROPERTY_LASTNAME = "lastName";
    private static final String SEARCH_TERM = "searchTerm";

    private PaginatingPersonRepositoryImpl repository;

    private QueryDslJpaRepository personRepositoryMock;

    @Before
    public void setUp() {
        repository = new PaginatingPersonRepositoryImpl();

        personRepositoryMock = mock(QueryDslJpaRepository.class);
        repository.setPersonRepository(personRepositoryMock);
    }

    @Test
    public void findAllPersons() {
        repository.findAllPersons();

        ArgumentCaptor<Sort> sortArgument = ArgumentCaptor.forClass(Sort.class);
        verify(personRepositoryMock, times(1)).findAll(sortArgument.capture());

        Sort sort = sortArgument.getValue();
        assertEquals(Sort.Direction.ASC, sort.getOrderFor(PROPERTY_LASTNAME).getDirection());
    }

    @Test
    public void findPersonCount() {
        when(personRepositoryMock.count(any(Predicate.class))).thenReturn(PERSON_COUNT);

        long actual = repository.findPersonCount(SEARCH_TERM);

        verify(personRepositoryMock, times(1)).count(any(Predicate.class));
        verifyNoMoreInteractions(personRepositoryMock);

        assertEquals(PERSON_COUNT, actual);
    }

    @Test
    public void findPersonsForPage() {
        List<Person> expected = new ArrayList<Person>();
        Page foundPage = new PageImpl<Person>(expected);

        when(personRepositoryMock.findAll(any(Predicate.class), any(Pageable.class))).thenReturn(foundPage);

        List<Person> actual = repository.findPersonsForPage(SEARCH_TERM, PAGE_INDEX);

        ArgumentCaptor<Pageable> pageSpecificationArgument = ArgumentCaptor.forClass(Pageable.class);
        verify(personRepositoryMock, times(1)).findAll(any(Predicate.class), pageSpecificationArgument.capture());
        verifyNoMoreInteractions(personRepositoryMock);

        Pageable pageSpecification = pageSpecificationArgument.getValue();

        assertEquals(PAGE_INDEX, pageSpecification.getPageNumber());
        assertEquals(PaginatingPersonRepositoryImpl.NUMBER_OF_PERSONS_PER_PAGE, pageSpecification.getPageSize());
        assertEquals(Sort.Direction.ASC, pageSpecification.getSort().getOrderFor(PROPERTY_LASTNAME).getDirection());

        assertEquals(expected, actual);
    }
}

Creating the Spring Data JPA repository

Now it is time to make the custom functionality available to the users of the repository. This is done by making the repository interface extend the created custom interface. As you might remember, the repository interface of my example application is called PersonRepository. Its source code is given in following:

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

public interface PersonRepository extends JpaRepository<Person, Long>, PaginatingPersonRepository, QueryDslPredicateExecutor<Person> {

}

Using the Custom Functionality

Now when the logic related to pagination and sorting has been moved to the custom repository implementation, the service layer will basically just delegate the method calls forward to the repository and act as a transaction boundary. The PersonService interface has stayed intact, but there has been some changes to the following methods of the RepositoryPersonService class:

  • public long count(String searchTerm)
  • public List<Person> findAll()
  • public List<Person> search(String searchTerm, int pageIndex)

All these methods are simply delegating the method call forward to the repository. The source code of the relevant parts of the RepositoryPersonService class is given in following:

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;

@Service
public class RepositoryPersonService implements PersonService {
    
    private static final Logger LOGGER = LoggerFactory.getLogger(RepositoryPersonService.class);

    protected static final int NUMBER_OF_PERSONS_PER_PAGE = 5;

    @Resource
    private PersonRepository personRepository;

    @Transactional
    @Override
    public long count(String searchTerm) {
        LOGGER.debug("Getting person count for search term: " + searchTerm);
        return personRepository.findPersonCount(searchTerm);
    }

    @Transactional(readOnly = true)
    @Override
    public List<Person> findAll() {
        LOGGER.debug("Finding all persons");
        return personRepository.findAllPersons();
    }

    @Transactional(readOnly = true)
    @Override
    public List<Person> search(String searchTerm, int pageIndex) {
        LOGGER.debug("Searching persons with search term: " + searchTerm);
        return personRepository.findPersonsForPage(searchTerm, pageIndex);
    }
}

Changes made to the RepositoryPersonService class means that its unit tests must be changed as well. The source code of the changed unit tests is given in following:

import org.junit.Before;
import org.junit.Test;
import org.mockito.ArgumentCaptor;

import static junit.framework.Assert.assertEquals;
import static org.mockito.Mockito.*;

public class RepositoryPersonServiceTest {

    private static final int PAGE_INDEX = 1;
    private static final String SEARCH_TERM = "foo";
    
    private RepositoryPersonService personService;

    private PersonRepository personRepositoryMock;

    @Before
    public void setUp() {
        personService = new RepositoryPersonService();

        personRepositoryMock = mock(PersonRepository.class);
        personService.setPersonRepository(personRepositoryMock);
    }

    @Test
    public void count() {
        personService.count(SEARCH_TERM);

        verify(personRepositoryMock, times(1)).findPersonCount(SEARCH_TERM);
        verifyNoMoreInteractions(personRepositoryMock);
    }

    @Test
    public void findAll() {
        List<Person> persons = new ArrayList<Person>();
        when(personRepositoryMock.findAllPersons()).thenReturn(persons);

        List<Person> returned = personService.findAll();

        verify(personRepositoryMock, times(1)).findAllPersons();
        verifyNoMoreInteractions(personRepositoryMock);

        assertEquals(persons, returned);
    }

    @Test
    public void search() {
        personService.search(SEARCH_TERM, PAGE_INDEX);

        verify(personRepositoryMock, times(1)).findPersonsForPage(SEARCH_TERM, PAGE_INDEX);
        verifyNoMoreInteractions(personRepositoryMock);
    }
}

What is Next?

I have now described to you how you can add custom functionality to a Spring Data repository. Even though this can be a handy in some situations, I think that the situation described in this blog entry is not one of them. To be honest, the benefits obtained from the proposed solution are purely theoretical, and I cannot recommend using this approach for eliminating the dependency between the service layer and Spring Data JPA. You simply don’t get enough bang for your buck.

Sometimes adding functionality to a single repository is not enough. You can also add custom functionality to all repositories. The implementation of this is left as an exercise for the reader.

The last part of my Spring Data JPA tutorial will summarize what we have learned and give some suggestions about the usage of Spring Data JPA.

PS. If you want to a take a closer look of my example application, you can get it from Github.

If you want to learn more about Spring Data JPA, you should read all parts of my Spring Data JPA tutorial.
If you enjoyed reading this blog post, you should follow me on Twitter:

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 →

18 comments… add one

  • Hi,

    Excellent tutorials – You have a gift for explaining things. Thank you for taking the time!
    I am new to Spring 3.x and JPA and have been struggling to understand it as tutorials for the right versions seem hard to come by. This series of 5 tutorials are easily the best ones I have read so far.

    That said, while the first 3 tutorials made perfect sense to me, I had difficulty with the last three as I did not quite understand how I would use them despite you examples. Perhaps a re-read will help :)

    I could not see any JOIN queries (INNERJOIN, OUTERJOIN) in your tutorials. I would appreciate another tutorial with examples with JOIN, perhaps an address table and/or an employment table for the Person will be helpful to explain how to join two or more tables.

    Also would appreciate a tutorial in which the java code calls a stored proc to do the query and get the results.

    Once again, thank you for your excellent tutorials. Finally things are beginning to make sense :)

    Reply
  • A fast comment concerning the naming strategy that should be used to select a name for the custom repository implementation:

    This behavior has changed in the newer versions of Spring Data JPA (newer than 1.0.2.RELEASE that is used in the example application), and you must create the name of the custom repository implementation by adding the configured post fix to the simple name of the actual repository interface. In this case, the name of the custom implementation should be changed from PaginatingPersonRepositoryImpl to PersonRepositoryImpl.

    Reply
  • Hi,
    Nice article, actually nice series of articles about spring-data-jpa, however I didn’t have the time so far to run throught all of them.
    But your last comment about new naming strategy saved my day.Thank you!

    Reply
    • Thanks for your comment. It is good to hear that I could help you out!

      Reply
  • One of my readers (Thanks Eric!) told me about an another way to fulfill the requirements described in this blog entry. Check it out: Spring Data JPA with QueryDSL: Repositories made easy.

    Reply
  • Very Nice article
    We have dynamic search requirement on specific entity from UI.
    How can we add Specification dynamically?

    Presently we are directly using Criteria API like

    
    @SuppressWarnings({ "rawtypes", "unchecked" })
    public static TypedQuery createCriteriaQueryForEntity(EntityManager em, 
    Class entityClazz, List criteriaFilters)
    {
    	CriteriaBuilder cb = em.getCriteriaBuilder();
    	CriteriaQuery createQuery = cb.createQuery(entityClazz);
    	Root root = createQuery.from(entityClazz);
    	CriteriaQuery select = createQuery.select(root);
    	Predicate[] predicates = buildPredicates(criteriaFilters, root, cb);
    	TypedQuery typedQuery = em.createQuery(select.where(cb.and(predicates)));
    	return typedQuery;
    }
    
    

    Is there any way to get it done with Specification?

    Reply
    • Hi Mohit,

      I have written another blog entry that describes the usage of specifications with Spring Data JPA. However, here is skeleton of a specification builder class which you can use to build your query by using specifications:

      
      public class Specifications {
      
      	public static Specification search(final List filters) {
      		return new Specification() {
      			@Override
      			public Predicate toPredicate(Root root, 
      						CriteriaQuery< ?> query, 
      						CriteriaBuilder cb) {
      				Predicate[] p = buildPredicates(filters, root, cb);
      				return cb.and(p);
      			}
                 
      			private Predicate[] buildPredicates(List filters, 
      							Root root, 
      							CriteriaBuilder cb) {
      				//Build your predicates here.
      			}
      		};
      	}
      }
      
      

      I assume that the type of your entity class is EntityClass. I hope that this answered to your question.

      Reply
  • Hi Petri, I have a problem to connect my application to multiple datasources, I saw It’s possible to use @@PersistenceContext(unitName=, but I don’t know how to implement it. Currently I have configuration using .java and not .xml files, other thing is that I use DTO’s and jparepository.

    Thanks a lot for helping me.
    Pablo

    Reply
  • Hello Petri ,
    As usual, reading your blog is always a pleasure and informative.
    I am trying to solve a problem and hoping your get your insights.
    I am trying to write a generic repository that loads entities that are subclasses of a base type.
    Now, I have a base repository implementation working and am able to manipulate specific entities For E-g Car. I am attempting to write a generic repo that is able to load all reference data like “CarType”
    So I am adding custom methods to that repo and extending from the base repo.
    http://stackoverflow.com/questions/19765684/spring-jpa-adding-custom-functionality-to-all-repositories-and-at-the-same-time?rq=1
    I am running into an error :
    Caused by: java.lang.IllegalArgumentException: Not an managed type: class xxx.BaseEntity
    at org.hibernate.ejb.metamodel.MetamodelImpl.managedType(MetamodelImpl.java:200) ~[hibernate-entitymanager-4.2.7.Final.jar:4.2.7.Final]
    at org.springframework.data.jpa.repository.support.JpaMetamodelEntityInformation.(JpaMetamodelEntityInformation.java:68) ~[spring-data-jpa-1.4.3.RELEASE.jar:na]
    at org.springframework.data.jpa.repository.support.JpaEntityInformationSupport.getMetadata(JpaEntityInformationSupport.java:65) ~[spring-data-jpa-1.4.3.RELEASE.jar:na]
    at org.springframework.data.jpa.repository.support.JpaRepositoryFactory.getEntityInformation(JpaRepositoryFactory.java:146) ~[spring-data-jpa-1.4.3.RELEASE.jar:na]

    I saw the below post but could not figure it out :

    http://stackoverflow.com/questions/14286841/can-i-use-a-generic-repository-for-all-children-of-a-mappedsuperclass-with-sprin

    I have created a SO question with complete implementation details(which might be a bit long to post here).

    http://stackoverflow.com/questions/21415551/generic-spring-data-jpa-repository-implementation-to-load-data-by-class-type

    If possible, kindly have a look and advice. It will be much appreciated.

    Thanks

    Reply
    • Hi,

      I am sorry that it took me a few days to answer to this question.

      The root cause of this problem is that the BaseEntity class isn’t mapped as an entity like suggested in the answer of your StackOverflow question.

      Reply
  • Thank you so much! Great tutorial!

    Reply
    • You are welcome! I am happy to hear that this blog post was useful to you.

      Reply
  • Hi Petri,

    These are all examples with every explanation is excellence.
    And every examples are very helpful for us.
    Thanks

    Reply
    • Hi,

      you are welcome. I am happy to hear that this blog post was useful to you.

      Reply
  • Hi Petri,

    All ur examples are very easy to understand. Can you pl explain how to call stored procedures in spring data jpa?

    Thanks in advance

    Shyamala

    Reply
    • I think that you have two options: you can either create a native SQL query by using the @Query annotation, or add a custom method to your repository which calls the stored procedure by using JDBC. You can get more information about the second option by reading this StackOverflow question.

      Reply

Leave a Comment