1. Introduction
When modeling a real-world system or process, domain-driven design (DDD) style repositories are a good option. For this very purpose, we can use Spring Data JPA as our data access abstraction layer.
If you are new to this concept check out this introductory tutorial to help get you up to speed.
In this tutorial, we’ll focus on the concept of creating custom as well as composable repositories which are created using smaller repositories called fragments.
2. Maven Dependencies
The option to create composable repositories is available starting with Spring 5.
Let’s add the required dependency for Spring Data JPA:
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-jpa</artifactId>
<version>2.7.11</version>
</dependency>
We’d also need to set up a data source in order for our data access layer to work. It’s a good idea to set up an in-memory database like H2 for development and quick testing.
3. Background
3.1. Hibernate as JPA Implementation
Spring Data JPA, by default, uses Hibernate as the JPA implementation. We can easily confuse one with the other or compare them but they serve different purposes.
Spring Data JPA is the data access abstraction layer below which we can use any implementation. We could, for instance, switch out Hibernate in favor of EclipseLink.
3.2. Default Repositories
In a lot of cases, we wouldn’t need to write any queries ourselves.
Instead, we only need to create interfaces which in turn extend the generic Spring data repository interfaces:
public interface LocationRepository extends JpaRepository<Location, Long> {
}
And this, in itself, would allow us to do common operations – CRUD, paging, and sorting – on the Location object which has a primary key of type Long.
Furthermore, Spring Data JPA comes equipped with a query builder mechanism which provides the ability to generate queries on our behalf using method name conventions:
public interface StoreRepository extends JpaRepository<Store, Long> {
List<Store> findStoreByLocationId(Long locationId);
}
3.3. Custom Repositories
If required, we can enrich our model repository by writing a fragment interface and implementing the desired functionality. This can then be injected in our own JPA repository.
For example, here we are enriching our ItemTypeRepository by extending a fragment repository:
public interface ItemTypeRepository
extends JpaRepository<ItemType, Long>, CustomItemTypeRepository {
}
Here CustomItemTypeRepository is another interface:
public interface CustomItemTypeRepository {
void deleteCustomById(ItemType entity);
}
Its implementation can be a repository of any kind, not just JPA:
public class CustomItemTypeRepositoryImpl implements CustomItemTypeRepository {
@Autowired
private EntityManager entityManager;
@Override
public void deleteCustomById(ItemType itemType) {
entityManager.remove(itemType);
}
}
We just need to make sure that it has the postfix Impl. However, we can set a custom postfix by using the following XML configuration:
<repositories base-package="com.baeldung.repository" repository-impl-postfix="CustomImpl" />
or by using this annotation:
@EnableJpaRepositories(
basePackages = "com.baeldung.repository", repositoryImplementationPostfix = "CustomImpl")
4. Composing Repositories Using Multiple Fragments
Up until a few releases ago, we could only extend our repository interfaces using a single custom implementation. This was a limitation because of which we’d have to bring all related functionality into a single object.
Needless to say, for larger projects with complex domain models, this lead to bloated classes.
Now with Spring 5, we have the option to enrich our JPA repository with multiple fragment repositories. Again, the requirement remains that we have these fragments as interface-implementation pairs.
To demonstrate this, let’s create two fragments:
public interface CustomItemTypeRepository {
void deleteCustom(ItemType entity);
void findThenDelete(Long id);
}
public interface CustomItemRepository {
Item findItemById(Long id);
void deleteCustom(Item entity);
void findThenDelete(Long id);
}
Of course, we’d need to write their implementations. But instead of plugging these custom repositories – with related functionalities – in their own JPA repositories, we can extend the functionality of a single JPA repository:
public interface ItemTypeRepository
extends JpaRepository<ItemType, Long>, CustomItemTypeRepository, CustomItemRepository {
}
Now, we’d have all the linked functionality in one single repository.
5. Dealing with Ambiguity
Since we’re inheriting from multiple repositories, we may have trouble figuring out which of our implementations would be used in case of a clash. For instance, in our example, both fragment repositories have a method, findThenDelete(), with the same signature.
In this scenario, the order of the declaration of the interfaces is used to resolve the ambiguity. Consequently, in our case, the method inside CustomItemTypeRepository will be used since it’s declared first.
We can test this by using this test case:
@Test
public void givenItemAndItemTypeWhenDeleteThenItemTypeDeleted() {
Optional<ItemType> itemType = composedRepository.findById(1L);
assertTrue(itemType.isPresent());
Item item = composedRepository.findItemById(2L);
assertNotNull(item);
composedRepository.findThenDelete(1L);
Optional<ItemType> sameItemType = composedRepository.findById(1L);
assertFalse(sameItemType.isPresent());
Item sameItem = composedRepository.findItemById(2L);
assertNotNull(sameItem);
}
6. Conclusion
In this article, we took a look at the different ways through which we can use Spring Data JPA repositories. We saw that Spring makes it simple to perform database operations on our domain objects without writing much code or even SQL queries.
This support is considerably customizable through the use of composable repositories.
The code snippets from this article are available as a Maven project here on GitHub.