Composite Repositories – Extend your Spring Data JPA Repository
Take your skills to the next level!
The Persistence Hub is the place to be for every Java developer. It gives you access to all my premium video courses, monthly Java Persistence News, monthly coding problems, and regular expert sessions.
Almost all applications have at least a few uses cases that require complex database operations. You can’t define them using a few annotations in a Spring Data JPA repository. You need to create a composite repository.
Whenever derived or custom queries are not powerful or flexible enough, you can add your own methods to your repository using a composite repository. It combines the ease of use of Spring Data’s standard repositories with the flexibility of a custom implementation.
Composite repositories are easy to implement. You create an interface that defines your custom methods. This is called a fragment interface. You also need to create a custom implementation of that interface and integrate it with one of Spring Data JPA’s standard repository interfaces.
But more about that in the 2nd part of this article. Let’s first discuss the general motivation for composite repositories in more detail.
How to Define a Composite Repository
The definition of a composite repository looks very similar to a standard repository. You start by extending one of Spring Data JPA’s repository interfaces, e.g., CrudRepository.
public interface AuthorRepository extends CrudRepository<Author, Long>, CustomAuthorRepository {}
In addition to that, you also extend your fragment interface. In this example, I called that interface CustomAuthorRepository.
How to Define a Fragment Repository
The fragment repository is a simple interface that defines the methods for which you want to provide your custom implementation. As you have seen in the previous code snippet, the AuthorRepository extends this and other repository interfaces, which provide the rest of the required functionality.
public interface CustomAuthorRepository { public List<AuthorSummaryDTO> getAuthorsByFirstName(String firstName); }
In the example of this article, my fragment interface only defines the getAuthorsByFirstName method.
In addition to the fragment interface, you need to provide an implementation of it. In this implementation, you can use Spring’s dependency injection to get a reference to the EntityManager and use it to provide the implementation of your repository methods. In this example, I use the Criteria API to get all Authors with a given firstName as a List of AuthorSummaryDTO objects.
public class CustomAuthorRepositoryImpl implements CustomAuthorRepository { @Autowired private EntityManager entityManager; @Override public List<AuthorSummaryDTO> getAuthorsByFirstName(String firstName) { CriteriaBuilder cb = entityManager.getCriteriaBuilder(); CriteriaQuery<AuthorSummaryDTO> query = cb.createQuery(AuthorSummaryDTO.class); Root<Author> root = query.from(Author.class); query.select(cb.construct(AuthorSummaryDTO.class, root.get(Author_.firstName), root.get(Author_.lastName))) .where(cb.equal(root.get(Author_.firstName), firstName)); return entityManager.createQuery(query).getResultList(); } }
Using Multiple Fragment Repositories
Since Spring 5, your repository can extend multiple fragment interfaces. That gives some extra flexibility for complex domain models and persistence layers.
public interface BookRepository extends CrudRepository<Book, Long>, CustomBookRepository, FindAllRepository { }
Resolving Ambiguous Repository Method Declarations
When you use multiple fragment interfaces to compose your repository, you get into the situation that 2 interfaces define a method with the same name. In the previous code snippet, the BookRepository extends the fragment repositories CustomBookRepository and the FindAllRepository.
The CustomBookRepository defines the methods findBooksByAuthorId and findAll.
public interface CustomBookRepository { public List<AuthorBookSummaryDTO> findBooksByAuthorId(long authorId); public List<Book> findAll(); }
The FindAllRepository only defines a findAll method.
public interface FindAllRepository { public List<Book> findAll(); }
As you can see, both fragments define a findAll method. On an interface-level, this is not a problem. As it’s the case for all Java interfaces, the 2 methods get merged into one.
But both fragment repositories can also provide their own, independent implementations. In that case, the order in which you reference the fragment interfaces in your repository definition defines their priority. The implementation of the interface that gets referenced first gets used.
Let’s take another look at the definition of the BookRepository interface used in the previous section.
public interface BookRepository extends CrudRepository<Book, Long>, CustomBookRepository, FindAllRepository { }
As you can see, the CustomBookRepository is listed before the FindAllRepository. Because of that, the implementation of the CustomBookRepository gets the higher priority. All calls of the findAll method of the BookRepository will be forwarded to the implementation of the CustomBookRepository fragment.
Conclusion
Spring Data JPA repositories provide many standardized features that are easy to use and solve the most common use cases. Nonetheless, in some situations, you might need to provide your own implementation of a repository method. You can easily do that by using a Composite Repository.
A Composite Repository extends a standard Spring Data JPA repository interface and one or more fragment interfaces. These are custom interfaces that define the methods for which you want to provide a custom implementation. In addition to the fragment interface, you also need to provide a class that implements that interface. Within that class, you can use Spring’s dependency injection capabilities to get a reference to the EntityManager and to implement the required logic.
If multiple fragment interfaces define the same method and provide their own implementations, Spring uses the implementation of the fragment interface that gets referenced first in the composite repository definition.