Spring Data: Repository Definition
Implement custom repository interfaces with @RepositoryDefinition annotation.
Background
Spring Data provides a consistent programming model for data access while still retaining the special traits of the underlying data store. It makes it easy to use data access technologies, relational and non-relational databases, map-reduce frameworks, and cloud-based data services.
When working with Spring Data, we typically create repository interfaces by extending one of the provided base interfaces such as CrudRepository
, JpaRepository
, or MongoRepository
. However, sometimes we may want to define a repository with only specific methods, without inheriting all the methods from these base interfaces.
This is where the @RepositoryDefinition
annotation comes in. It allows us to define a repository interface with only the methods we need, providing more control over the repository’s API.
Domain Class
We have a simple domain class, Note, which is a Java record with three fields: id
, title
, and content
.
record Note(@Id Long id, String title, String content) {
}
The @Id
annotation from Spring Data marks the id
field as the primary key.
Repository Definition
Instead of extending a base repository interface, we use the @RepositoryDefinition
annotation to define our repository interface, NoteRepository.
@RepositoryDefinition(domainClass = Note.class, idClass = Long.class)
interface NoteRepository {
List<Note> findByTitleContainingIgnoreCase(String title);
}
The @RepositoryDefinition
annotation takes two parameters:
- domainClass
: The entity class that this repository manages (in this case, Note.class
)
- idClass
: The type of the entity’s ID field (in this case, Long.class
)
With this annotation, Spring Data will create a repository implementation for us, just like it would for a repository that extends a base interface. The difference is that our repository only has the methods we explicitly define, in this case, just findByTitleContainingIgnoreCase
.
Benefits of @RepositoryDefinition
Using @RepositoryDefinition
offers several benefits:
-
Minimalist API: You only expose the methods you need, making the API cleaner and more focused.
-
Explicit Contract: The repository interface clearly shows what operations are supported.
-
Reduced Surface Area: By not inheriting methods from base interfaces, you reduce the risk of unintended operations being performed.
-
Flexibility: You can define repositories for any domain class without being tied to a specific persistence technology’s base interface.
Testing
We can test our repository using Spring Boot’s testing support with Testcontainers for PostgreSQL.
@Import(TestcontainersConfiguration.class)
@DataJdbcTest
@SqlMergeMode(MERGE)
@Sql(statements = "CREATE TABLE note (id BIGINT PRIMARY KEY, title VARCHAR(50), content TEXT);", executionPhase = BEFORE_TEST_CLASS)
class NoteRepositoryTests {
@Autowired
private NoteRepository notes;
@Test
@Sql(statements = {
"INSERT INTO note (id, title, content) VALUES ('1', 'Right Turn', 'Step forward. Step forward and turn right. Collect.')",
"INSERT INTO note (id, title, content) VALUES ('2', 'Left Turn', 'Step forward. Reverse and turn left. Collect.')",
"INSERT INTO note (id, title, content) VALUES ('3', 'Double Spin', 'Syncopated. Double spin. Collect.')"
})
@DisplayName("Given there are two entries with the word 'turn' in the title When I search by 'turn' in title Then Right Turn And Left Turn should be returned")
void findByTitleContainingIgnoreCase() {
var turns = notes.findByTitleContainingIgnoreCase("turn");
assertThat(turns)
.extracting("title")
.containsOnly("Right Turn", "Left Turn");
}
}
The test verifies that our repository method findByTitleContainingIgnoreCase
correctly finds notes with titles containing the word "turn", ignoring case.
Conclusion
The @RepositoryDefinition
annotation provides a way to create custom repository interfaces with only the methods you need, without inheriting all the methods from base interfaces. This gives you more control over your repository’s API and makes your code more explicit about what operations are supported.