-
Notifications
You must be signed in to change notification settings - Fork 4
Add example for Spring Data RepositoryDefinition #204
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Closed
Closed
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
9 changes: 6 additions & 3 deletions
9
...vents/src/main/java/zin/rashidi/boot/data/de/availability/BookAvailabilityRepository.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,14 +1,17 @@ | ||
| package zin.rashidi.boot.data.de.availability; | ||
|
|
||
| import java.util.Optional; | ||
| import org.springframework.data.repository.RepositoryDefinition; | ||
|
|
||
| import org.springframework.data.jpa.repository.JpaRepository; | ||
| import java.util.Optional; | ||
|
|
||
| /** | ||
| * @author Rashidi Zin | ||
| */ | ||
| interface BookAvailabilityRepository extends JpaRepository<BookAvailability, Long> { | ||
| @RepositoryDefinition(domainClass = BookAvailability.class, idClass = Long.class) | ||
| interface BookAvailabilityRepository { | ||
|
|
||
| Optional<BookAvailability> findByIsbn(Long isbn); | ||
|
|
||
| BookAvailability save(BookAvailability entity); | ||
|
|
||
| } | ||
13 changes: 11 additions & 2 deletions
13
data-domain-events/src/main/java/zin/rashidi/boot/data/de/book/BookRepository.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,9 +1,18 @@ | ||
| package zin.rashidi.boot.data.de.book; | ||
|
|
||
| import org.springframework.data.jpa.repository.JpaRepository; | ||
| import org.springframework.data.repository.RepositoryDefinition; | ||
|
|
||
| import java.util.Optional; | ||
|
|
||
| /** | ||
| * @author Rashidi Zin | ||
| */ | ||
| public interface BookRepository extends JpaRepository<Book, Long> { | ||
| @RepositoryDefinition(domainClass = Book.class, idClass = Long.class) | ||
| public interface BookRepository { | ||
|
Comment on lines
+10
to
+11
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Similar to the /**
* This repository uses @RepositoryDefinition to define a custom interface
* with only the required methods, instead of extending JpaRepository.
*/
@RepositoryDefinition(domainClass = Book.class, idClass = Long.class) |
||
|
|
||
| Optional<Book> findById(Long id); | ||
|
|
||
| Book save(Book entity); | ||
|
|
||
| void delete(Book entity); | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| /gradlew text eol=lf | ||
| *.bat text eol=crlf | ||
| *.jar binary |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,37 @@ | ||
| HELP.md | ||
| .gradle | ||
| build/ | ||
| !gradle/wrapper/gradle-wrapper.jar | ||
| !**/src/main/**/build/ | ||
| !**/src/test/**/build/ | ||
|
|
||
| ### STS ### | ||
| .apt_generated | ||
| .classpath | ||
| .factorypath | ||
| .project | ||
| .settings | ||
| .springBeans | ||
| .sts4-cache | ||
| bin/ | ||
| !**/src/main/**/bin/ | ||
| !**/src/test/**/bin/ | ||
|
|
||
| ### IntelliJ IDEA ### | ||
| .idea | ||
| *.iws | ||
| *.iml | ||
| *.ipr | ||
| out/ | ||
| !**/src/main/**/out/ | ||
| !**/src/test/**/out/ | ||
|
|
||
| ### NetBeans ### | ||
| /nbproject/private/ | ||
| /nbbuild/ | ||
| /dist/ | ||
| /nbdist/ | ||
| /.nb-gradle/ | ||
|
|
||
| ### VS Code ### | ||
| .vscode/ |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,101 @@ | ||
| = Spring Data: Repository Definition | ||
| :source-highlighter: highlight.js | ||
| Rashidi Zin <rashidi@zin.my> | ||
| 1.0, March 22, 2025 | ||
| :toc: | ||
| :nofooter: | ||
| :icons: font | ||
| :url-quickref: https://github.com/rashidi/spring-boot-tutorials/tree/master/data-repository-definition | ||
|
|
||
| Implement custom repository interfaces with @RepositoryDefinition annotation. | ||
|
|
||
| include::../docs/badges.adoc[] | ||
|
|
||
| == Background | ||
|
|
||
| link:https://spring.io/projects/spring-data[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, link:{url-quickref}/src/main/java/zin/rashidi/data/repositorydefinition/note/Note.java[Note], which is a Java record with three fields: `id`, `title`, and `content`. | ||
|
|
||
| [source,java] | ||
| ---- | ||
| 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, link:{url-quickref}/src/main/java/zin/rashidi/data/repositorydefinition/note/NoteRepository.java[NoteRepository]. | ||
|
|
||
| [source,java] | ||
| ---- | ||
| @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: | ||
|
|
||
| 1. **Minimalist API**: You only expose the methods you need, making the API cleaner and more focused. | ||
| 2. **Explicit Contract**: The repository interface clearly shows what operations are supported. | ||
| 3. **Reduced Surface Area**: By not inheriting methods from base interfaces, you reduce the risk of unintended operations being performed. | ||
| 4. **Flexibility**: You can define repositories for any domain class without being tied to a specific persistence technology's base interface. | ||
|
|
||
| == Testing | ||
|
|
||
| We can link:{url-quickref}/src/test/java/zin/rashidi/data/repositorydefinition/note/NoteRepositoryTests.java[test our repository] using Spring Boot's testing support with Testcontainers for PostgreSQL. | ||
|
|
||
| [source,java] | ||
| ---- | ||
| @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. | ||
|
|
||
| While extending base interfaces like `CrudRepository` or `JpaRepository` is convenient for most cases, using `@RepositoryDefinition` can be a good choice when you want to limit the operations that can be performed on your entities or when you want to create a more focused and explicit API. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| plugins { | ||
| id 'java' | ||
| id 'org.springframework.boot' version '3.4.4' | ||
| id 'io.spring.dependency-management' version '1.1.7' | ||
| } | ||
|
|
||
| group = 'zin.rashidi' | ||
| version = '0.0.1-SNAPSHOT' | ||
|
|
||
| java { | ||
| toolchain { | ||
| languageVersion = JavaLanguageVersion.of(21) | ||
| } | ||
| } | ||
|
|
||
| repositories { | ||
| mavenCentral() | ||
| } | ||
|
|
||
| dependencies { | ||
| implementation 'org.springframework.boot:spring-boot-starter-data-jdbc' | ||
| runtimeOnly 'org.postgresql:postgresql' | ||
| testImplementation 'org.springframework.boot:spring-boot-starter-test' | ||
| testImplementation 'org.springframework.boot:spring-boot-testcontainers' | ||
| testImplementation 'org.testcontainers:junit-jupiter' | ||
| testImplementation 'org.testcontainers:postgresql' | ||
| testRuntimeOnly 'org.junit.platform:junit-platform-launcher' | ||
| } | ||
|
|
||
| tasks.named('test') { | ||
| useJUnitPlatform() | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| rootProject.name = 'data-repository-definition' |
13 changes: 13 additions & 0 deletions
13
.../main/java/zin/rashidi/data/repositorydefinition/DataRepositoryDefinitionApplication.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| package zin.rashidi.data.repositorydefinition; | ||
|
|
||
| import org.springframework.boot.SpringApplication; | ||
| import org.springframework.boot.autoconfigure.SpringBootApplication; | ||
|
|
||
| @SpringBootApplication | ||
| public class DataRepositoryDefinitionApplication { | ||
|
|
||
| public static void main(String[] args) { | ||
| SpringApplication.run(DataRepositoryDefinitionApplication.class, args); | ||
| } | ||
|
|
||
| } |
9 changes: 9 additions & 0 deletions
9
...-repository-definition/src/main/java/zin/rashidi/data/repositorydefinition/note/Note.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| package zin.rashidi.data.repositorydefinition.note; | ||
|
|
||
| import org.springframework.data.annotation.Id; | ||
|
|
||
| /** | ||
| * @author Rashidi Zin | ||
| */ | ||
| record Note(@Id Long id, String title, String content) { | ||
| } |
15 changes: 15 additions & 0 deletions
15
...y-definition/src/main/java/zin/rashidi/data/repositorydefinition/note/NoteRepository.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| package zin.rashidi.data.repositorydefinition.note; | ||
|
|
||
| import org.springframework.data.repository.RepositoryDefinition; | ||
|
|
||
| import java.util.List; | ||
|
|
||
| /** | ||
| * @author Rashidi Zin | ||
| */ | ||
| @RepositoryDefinition(domainClass = Note.class, idClass = Long.class) | ||
| interface NoteRepository { | ||
|
|
||
| List<Note> findByTitleContainingIgnoreCase(String title); | ||
|
|
||
| } |
1 change: 1 addition & 0 deletions
1
data-repository-definition/src/main/resources/application.properties
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| spring.application.name=data-repository-definition |
11 changes: 11 additions & 0 deletions
11
...t/java/zin/rashidi/data/repositorydefinition/TestDataRepositoryDefinitionApplication.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| package zin.rashidi.data.repositorydefinition; | ||
|
|
||
| import org.springframework.boot.SpringApplication; | ||
|
|
||
| public class TestDataRepositoryDefinitionApplication { | ||
|
|
||
| public static void main(String[] args) { | ||
| SpringApplication.from(DataRepositoryDefinitionApplication::main).with(TestcontainersConfiguration.class).run(args); | ||
| } | ||
|
|
||
| } |
18 changes: 18 additions & 0 deletions
18
...tion/src/test/java/zin/rashidi/data/repositorydefinition/TestcontainersConfiguration.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| package zin.rashidi.data.repositorydefinition; | ||
|
|
||
| import org.springframework.boot.test.context.TestConfiguration; | ||
| import org.springframework.boot.testcontainers.service.connection.ServiceConnection; | ||
| import org.springframework.context.annotation.Bean; | ||
| import org.testcontainers.containers.PostgreSQLContainer; | ||
| import org.testcontainers.utility.DockerImageName; | ||
|
|
||
| @TestConfiguration(proxyBeanMethods = false) | ||
| public class TestcontainersConfiguration { | ||
|
|
||
| @Bean | ||
| @ServiceConnection | ||
| PostgreSQLContainer<?> postgresContainer() { | ||
| return new PostgreSQLContainer<>(DockerImageName.parse("postgres:latest")); | ||
| } | ||
|
|
||
| } |
43 changes: 43 additions & 0 deletions
43
...inition/src/test/java/zin/rashidi/data/repositorydefinition/note/NoteRepositoryTests.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,43 @@ | ||
| package zin.rashidi.data.repositorydefinition.note; | ||
|
|
||
| import org.junit.jupiter.api.DisplayName; | ||
| import org.junit.jupiter.api.Test; | ||
| import org.springframework.beans.factory.annotation.Autowired; | ||
| import org.springframework.boot.test.autoconfigure.data.jdbc.DataJdbcTest; | ||
| import org.springframework.context.annotation.Import; | ||
| import org.springframework.test.context.jdbc.Sql; | ||
| import org.springframework.test.context.jdbc.SqlMergeMode; | ||
| import zin.rashidi.data.repositorydefinition.TestcontainersConfiguration; | ||
|
|
||
| import static org.assertj.core.api.Assertions.assertThat; | ||
| import static org.springframework.test.context.jdbc.Sql.ExecutionPhase.BEFORE_TEST_CLASS; | ||
| import static org.springframework.test.context.jdbc.SqlMergeMode.MergeMode.MERGE; | ||
|
|
||
| /** | ||
| * @author Rashidi Zin | ||
| */ | ||
| @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"); | ||
| } | ||
|
|
||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consider adding a JavaDoc to explain why we are using
@RepositoryDefinitioninstead of extendingJpaRepository. This will help future developers understand the intent and benefits of this approach.