├── .github ├── PULL_REQUEST_TEMPLATE.md ├── dco.yml └── workflows │ └── project.yml ├── .gitignore ├── .mvn ├── extensions.xml ├── jvm.config └── wrapper │ ├── maven-wrapper.jar │ └── maven-wrapper.properties ├── CI.adoc ├── CONTRIBUTING.adoc ├── Jenkinsfile ├── LICENSE.txt ├── README.adoc ├── SECURITY.adoc ├── ci └── pipeline.properties ├── mvnw ├── mvnw.cmd ├── package.json ├── pom.xml ├── settings.xml └── src ├── main ├── antora │ ├── antora-playbook.yml │ ├── antora.yml │ ├── modules │ │ └── ROOT │ │ │ ├── nav.adoc │ │ │ └── pages │ │ │ ├── commons │ │ │ └── upgrade.adoc │ │ │ ├── index.adoc │ │ │ ├── keyvalue.adoc │ │ │ ├── keyvalue │ │ │ ├── repository │ │ │ │ └── map-repositories.adoc │ │ │ ├── template.adoc │ │ │ └── value-expressions.adoc │ │ │ ├── repositories.adoc │ │ │ └── repositories │ │ │ ├── core-concepts.adoc │ │ │ ├── core-domain-events.adoc │ │ │ ├── core-extensions.adoc │ │ │ ├── create-instances.adoc │ │ │ ├── custom-implementations.adoc │ │ │ ├── definition.adoc │ │ │ ├── null-handling.adoc │ │ │ ├── object-mapping.adoc │ │ │ ├── projections.adoc │ │ │ ├── query-keywords-reference.adoc │ │ │ ├── query-methods-details.adoc │ │ │ └── query-return-types-reference.adoc │ └── resources │ │ └── antora-resources │ │ └── antora.yml ├── asciidoc │ └── images │ │ ├── epub-cover.png │ │ └── epub-cover.svg ├── java │ └── org │ │ └── springframework │ │ └── data │ │ ├── keyvalue │ │ ├── annotation │ │ │ ├── KeySpace.java │ │ │ └── package-info.java │ │ ├── aot │ │ │ ├── KeyValueRuntimeHints.java │ │ │ └── package-info.java │ │ ├── core │ │ │ ├── AbstractKeyValueAdapter.java │ │ │ ├── CriteriaAccessor.java │ │ │ ├── DefaultIdentifierGenerator.java │ │ │ ├── ForwardingCloseableIterator.java │ │ │ ├── GeneratingIdAccessor.java │ │ │ ├── IdentifierGenerator.java │ │ │ ├── IterableConverter.java │ │ │ ├── KeyValueAdapter.java │ │ │ ├── KeyValueCallback.java │ │ │ ├── KeyValueOperations.java │ │ │ ├── KeyValuePersistenceExceptionTranslator.java │ │ │ ├── KeyValueTemplate.java │ │ │ ├── PathSortAccessor.java │ │ │ ├── PredicateQueryEngine.java │ │ │ ├── PropertyPathComparator.java │ │ │ ├── QueryEngine.java │ │ │ ├── QueryEngineFactory.java │ │ │ ├── SimplePropertyPathAccessor.java │ │ │ ├── SortAccessor.java │ │ │ ├── SpelCriteria.java │ │ │ ├── SpelCriteriaAccessor.java │ │ │ ├── SpelPropertyComparator.java │ │ │ ├── SpelQueryEngine.java │ │ │ ├── SpelSortAccessor.java │ │ │ ├── UncategorizedKeyValueException.java │ │ │ ├── event │ │ │ │ ├── KeyValueEvent.java │ │ │ │ └── package-info.java │ │ │ ├── mapping │ │ │ │ ├── AnnotationBasedKeySpaceResolver.java │ │ │ │ ├── BasicKeyValuePersistentEntity.java │ │ │ │ ├── ClassNameKeySpaceResolver.java │ │ │ │ ├── KeySpaceResolver.java │ │ │ │ ├── KeyValuePersistentEntity.java │ │ │ │ ├── KeyValuePersistentProperty.java │ │ │ │ ├── PrefixKeyspaceResolver.java │ │ │ │ ├── context │ │ │ │ │ ├── KeyValueMappingContext.java │ │ │ │ │ └── package-info.java │ │ │ │ └── package-info.java │ │ │ ├── package-info.java │ │ │ └── query │ │ │ │ ├── KeyValueQuery.java │ │ │ │ └── package-info.java │ │ └── repository │ │ │ ├── KeyValueRepository.java │ │ │ ├── config │ │ │ ├── KeyValueRepositoryConfigurationExtension.java │ │ │ ├── QueryCreatorType.java │ │ │ └── package-info.java │ │ │ ├── package-info.java │ │ │ ├── query │ │ │ ├── CachingKeyValuePartTreeQuery.java │ │ │ ├── KeyValuePartTreeQuery.java │ │ │ ├── PredicateQueryCreator.java │ │ │ ├── SpelQueryCreator.java │ │ │ └── package-info.java │ │ │ └── support │ │ │ ├── KeyValueQuerydslUtils.java │ │ │ ├── KeyValueRepositoryFactory.java │ │ │ ├── KeyValueRepositoryFactoryBean.java │ │ │ ├── QuerydslKeyValuePredicateExecutor.java │ │ │ ├── QuerydslKeyValueRepository.java │ │ │ ├── SimpleKeyValueRepository.java │ │ │ └── package-info.java │ │ └── map │ │ ├── MapKeyValueAdapter.java │ │ ├── package-info.java │ │ └── repository │ │ └── config │ │ ├── EnableMapRepositories.java │ │ ├── MapRepositoriesRegistrar.java │ │ ├── MapRepositoryConfigurationExtension.java │ │ └── package-info.java └── resources │ ├── META-INF │ ├── spring.factories │ └── spring │ │ └── aot.factories │ ├── license.txt │ └── notice.txt └── test ├── java └── org │ └── springframework │ └── data │ ├── keyvalue │ ├── CustomKeySpaceAnnotationWithAliasFor.java │ ├── Person.java │ ├── SubclassOfTypeWithCustomComposedKeySpaceAnnotation.java │ ├── TypeWithCustomComposedKeySpaceAnnotationUsingAliasFor.java │ ├── TypeWithDirectKeySpaceAnnotation.java │ ├── TypeWithInhteritedPersistentAnnotationNotHavingKeySpace.java │ ├── TypeWithPersistentAnnotationNotHavingKeySpace.java │ ├── core │ │ ├── DefaultIdentifierGeneratorUnitTests.java │ │ ├── ForwardingCloseableIteratorUnitTests.java │ │ ├── IterableConverterUnitTests.java │ │ ├── KeyValuePersistenceExceptionTranslatorUnitTests.java │ │ ├── KeyValueTemplateTests.java │ │ ├── KeyValueTemplateUnitTests.java │ │ ├── PredicateQueryEngineUnitTests.java │ │ ├── PropertyPathComparatorUnitTests.java │ │ ├── SpelPropertyComparatorUnitTests.java │ │ ├── SpelQueryEngineUnitTests.java │ │ └── mapping │ │ │ ├── AnnotationBasedKeySpaceResolverUnitTests.java │ │ │ ├── BasicKeyValuePersistentEntityUnitTests.java │ │ │ ├── PrefixKeyspaceResolverUnitTests.java │ │ │ └── context │ │ │ └── KeyValueMappingContextUnitTests.java │ └── repository │ │ ├── MapRepositoriesRegistrarUnitTests.java │ │ ├── SimpleKeyValueRepositoryUnitTests.java │ │ ├── query │ │ ├── AbstractQueryCreatorTestBase.java │ │ ├── CachingKeyValuePartTreeQueryUnitTests.java │ │ ├── KeyValuePartTreeQueryUnitTests.java │ │ ├── PredicateQueryCreatorUnitTests.java │ │ └── SpelQueryCreatorUnitTests.java │ │ └── support │ │ ├── KeyValueQuerydslUtilsUnitTests.java │ │ └── KeyValueRepositoryFactoryBeanUnitTests.java │ └── map │ ├── AbstractRepositoryUnitTests.java │ ├── CachingQuerySimpleKeyValueRepositoryUnitTests.java │ ├── MapKeyValueAdapterUnitTests.java │ ├── QuerydslKeyValuePredicateExecutorUnitTests.java │ ├── SimpleKeyValueRepositoryUnitTests.java │ └── repository │ └── config │ ├── MapRepositoriesConfigurationExtensionIntegrationTests.java │ ├── MapRepositoryRegistrarWithFullDefaultingIntegrationTests.java │ └── MapRepositoryRegistrarWithTemplateDefinitionIntegrationTests.java └── resources └── logback.xml /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 7 | 8 | - [ ] You have read the [Spring Data contribution guidelines](https://github.com/spring-projects/spring-data-build/blob/master/CONTRIBUTING.adoc). 9 | - [ ] You use the code formatters provided [here](https://github.com/spring-projects/spring-data-build/tree/master/etc/ide) and have them applied to your changes. Don’t submit any formatting related changes. 10 | - [ ] You submit test cases (unit or integration tests) that back your changes. 11 | - [ ] You added yourself as author in the headers of the classes you touched. Amend the date range in the Apache license header if needed. For new types, add the license header (copy from another file and set the current year only). 12 | -------------------------------------------------------------------------------- /.github/dco.yml: -------------------------------------------------------------------------------- 1 | require: 2 | members: false 3 | -------------------------------------------------------------------------------- /.github/workflows/project.yml: -------------------------------------------------------------------------------- 1 | # GitHub Actions to automate GitHub issues for Spring Data Project Management 2 | 3 | name: Spring Data GitHub Issues 4 | 5 | on: 6 | issues: 7 | types: [opened, edited, reopened] 8 | issue_comment: 9 | types: [created] 10 | pull_request_target: 11 | types: [opened, edited, reopened] 12 | 13 | jobs: 14 | Inbox: 15 | runs-on: ubuntu-latest 16 | if: github.repository_owner == 'spring-projects' && (github.event.action == 'opened' || github.event.action == 'reopened') && github.event.pull_request == null && !contains(join(github.event.issue.labels.*.name, ', '), 'dependency-upgrade') && !contains(github.event.issue.title, 'Release ') 17 | steps: 18 | - name: Create or Update Issue Card 19 | uses: actions/add-to-project@v1.0.2 20 | with: 21 | project-url: https://github.com/orgs/spring-projects/projects/25 22 | github-token: ${{ secrets.GH_ISSUES_TOKEN_SPRING_DATA }} 23 | Pull-Request: 24 | runs-on: ubuntu-latest 25 | if: github.repository_owner == 'spring-projects' && (github.event.action == 'opened' || github.event.action == 'reopened') && github.event.pull_request != null 26 | steps: 27 | - name: Create or Update Pull Request Card 28 | uses: actions/add-to-project@v1.0.2 29 | with: 30 | project-url: https://github.com/orgs/spring-projects/projects/25 31 | github-token: ${{ secrets.GH_ISSUES_TOKEN_SPRING_DATA }} 32 | Feedback-Provided: 33 | runs-on: ubuntu-latest 34 | if: github.repository_owner == 'spring-projects' && github.event_name == 'issue_comment' && github.event.action == 'created' && github.actor != 'spring-projects-issues' && github.event.pull_request == null && github.event.issue.state == 'open' && contains(toJSON(github.event.issue.labels), 'waiting-for-feedback') 35 | steps: 36 | - name: Update Project Card 37 | uses: actions/add-to-project@v1.0.2 38 | with: 39 | project-url: https://github.com/orgs/spring-projects/projects/25 40 | github-token: ${{ secrets.GH_ISSUES_TOKEN_SPRING_DATA }} 41 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | .settings/ 3 | .project 4 | .classpath 5 | .springBeans 6 | .DS_Store 7 | *.iml 8 | *.ipr 9 | *.iws 10 | /.idea/ 11 | node_modules 12 | node 13 | package-lock.json 14 | build/ 15 | .mvn/.develocity 16 | -------------------------------------------------------------------------------- /.mvn/extensions.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | io.spring.develocity.conventions 5 | develocity-conventions-maven-extension 6 | 0.0.22 7 | 8 | 9 | -------------------------------------------------------------------------------- /.mvn/jvm.config: -------------------------------------------------------------------------------- 1 | --add-exports jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED 2 | --add-exports jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED 3 | --add-exports jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED 4 | --add-exports jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED 5 | --add-exports jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED 6 | --add-exports jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED 7 | --add-exports jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED 8 | --add-exports jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED 9 | --add-opens jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED 10 | --add-opens jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED 11 | --add-opens=java.base/java.util=ALL-UNNAMED 12 | --add-opens=java.base/java.lang.reflect=ALL-UNNAMED 13 | --add-opens=java.base/java.text=ALL-UNNAMED 14 | --add-opens=java.desktop/java.awt.font=ALL-UNNAMED 15 | -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spring-projects/spring-data-keyvalue/8d926d53937e31bf082e1193c0eba032d8941d4f/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Thu Nov 07 09:47:19 CET 2024 2 | distributionUrl=https\://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.9/apache-maven-3.9.9-bin.zip 3 | -------------------------------------------------------------------------------- /CI.adoc: -------------------------------------------------------------------------------- 1 | = Continuous Integration 2 | 3 | image:https://jenkins.spring.io/buildStatus/icon?job=spring-data-keyvalue%2Fmain&subject=Moore%20(main)[link=https://jenkins.spring.io/view/SpringData/job/spring-data-keyvalue/] 4 | image:https://jenkins.spring.io/buildStatus/icon?job=spring-data-keyvalue%2F2.1.x&subject=Lovelace%20(2.1.x)[link=https://jenkins.spring.io/view/SpringData/job/spring-data-keyvalue/] 5 | image:https://jenkins.spring.io/buildStatus/icon?job=spring-data-keyvalue%2F1.2.x&subject=Ingalls%20(1.2.x)[link=https://jenkins.spring.io/view/SpringData/job/spring-data-keyvalue/] 6 | 7 | == Running CI tasks locally 8 | 9 | Since this pipeline is purely Docker-based, it's easy to: 10 | 11 | * Debug what went wrong on your local machine. 12 | * Test out a a tweak to your `test.sh` script before sending it out. 13 | * Experiment against a new image before submitting your pull request. 14 | 15 | All of these use cases are great reasons to essentially run what the CI server does on your local machine. 16 | 17 | IMPORTANT: To do this you must have Docker installed on your machine. 18 | 19 | 1. `docker run -it --mount type=bind,source="$(pwd)",target=/spring-data-keyvalue-github adoptopenjdk/openjdk8:latest /bin/bash` 20 | + 21 | This will launch the Docker image and mount your source code at `spring-data-keyvalue-github`. 22 | + 23 | 2. `cd spring-data-keyvalue-github` 24 | + 25 | Next, run your tests from inside the container: 26 | + 27 | 3. `./mvnw clean dependency:list test -Dsort` (or whatever profile you need to test out) 28 | 29 | Since the container is binding to your source, you can make edits from your IDE and continue to run build jobs. 30 | 31 | If you test building the artifact, do this: 32 | 33 | 1. `docker run -it --mount type=bind,source="$(pwd)",target=/spring-data-keyvalue-github adoptopenjdk/openjdk8:latest /bin/bash` 34 | + 35 | This will launch the Docker image and mount your source code at `spring-data-keyvalue-github`. 36 | + 37 | 2. `cd spring-data-keyvalue-github` 38 | + 39 | Next, try to package everything up from inside the container: 40 | + 41 | 3. `./mvnw -Pci,snapshot -Dmaven.test.skip=true clean package` 42 | 43 | NOTE: Docker containers can eat up disk space fast! From time to time, run `docker system prune` to clean out old images. 44 | -------------------------------------------------------------------------------- /CONTRIBUTING.adoc: -------------------------------------------------------------------------------- 1 | = Spring Data contribution guidelines 2 | 3 | You find the contribution guidelines for Spring Data projects https://github.com/spring-projects/spring-data-build/blob/main/CONTRIBUTING.adoc[here]. 4 | -------------------------------------------------------------------------------- /Jenkinsfile: -------------------------------------------------------------------------------- 1 | def p = [:] 2 | node { 3 | checkout scm 4 | p = readProperties interpolate: true, file: 'ci/pipeline.properties' 5 | } 6 | 7 | pipeline { 8 | agent none 9 | 10 | triggers { 11 | pollSCM 'H/10 * * * *' 12 | upstream(upstreamProjects: "spring-data-commons/main", threshold: hudson.model.Result.SUCCESS) 13 | } 14 | 15 | options { 16 | disableConcurrentBuilds() 17 | buildDiscarder(logRotator(numToKeepStr: '14')) 18 | } 19 | 20 | stages { 21 | stage("test: baseline (main)") { 22 | when { 23 | beforeAgent(true) 24 | anyOf { 25 | branch(pattern: "main|(\\d\\.\\d\\.x)", comparator: "REGEXP") 26 | not { triggeredBy 'UpstreamCause' } 27 | } 28 | } 29 | agent { 30 | label 'data' 31 | } 32 | options { timeout(time: 30, unit: 'MINUTES') } 33 | environment { 34 | ARTIFACTORY = credentials("${p['artifactory.credentials']}") 35 | DEVELOCITY_ACCESS_KEY = credentials("${p['develocity.access-key']}") 36 | } 37 | steps { 38 | script { 39 | docker.withRegistry(p['docker.proxy.registry'], p['docker.proxy.credentials']) { 40 | docker.image(p['docker.java.main.image']).inside(p['docker.java.inside.docker']) { 41 | sh 'MAVEN_OPTS="-Duser.name=' + "${p['jenkins.user.name']}" + ' -Duser.home=/tmp/jenkins-home" ' + 42 | './mvnw -s settings.xml -Ddevelocity.storage.directory=/tmp/jenkins-home/.develocity-root -Dmaven.repo.local=/tmp/jenkins-home/.m2/spring-data-keyvalue clean dependency:list test -Dsort -U -B' 43 | } 44 | } 45 | } 46 | } 47 | } 48 | 49 | stage("Test other configurations") { 50 | when { 51 | beforeAgent(true) 52 | allOf { 53 | branch(pattern: "main|(\\d\\.\\d\\.x)", comparator: "REGEXP") 54 | not { triggeredBy 'UpstreamCause' } 55 | } 56 | } 57 | parallel { 58 | stage("test: baseline (next)") { 59 | agent { 60 | label 'data' 61 | } 62 | options { timeout(time: 30, unit: 'MINUTES') } 63 | environment { 64 | ARTIFACTORY = credentials("${p['artifactory.credentials']}") 65 | DEVELOCITY_ACCESS_KEY = credentials("${p['develocity.access-key']}") 66 | } 67 | steps { 68 | script { 69 | docker.withRegistry(p['docker.proxy.registry'], p['docker.proxy.credentials']) { 70 | docker.image(p['docker.java.next.image']).inside(p['docker.java.inside.docker']) { 71 | sh 'MAVEN_OPTS="-Duser.name=' + "${p['jenkins.user.name']}" + ' -Duser.home=/tmp/jenkins-home" ' + 72 | './mvnw -s settings.xml -Ddevelocity.storage.directory=/tmp/jenkins-home/.develocity-root -Dmaven.repo.local=/tmp/jenkins-home/.m2/spring-data-keyvalue clean dependency:list test -Dsort -U -B' 73 | } 74 | } 75 | } 76 | } 77 | } 78 | } 79 | } 80 | 81 | stage('Release to artifactory') { 82 | when { 83 | beforeAgent(true) 84 | anyOf { 85 | branch(pattern: "main|(\\d\\.\\d\\.x)", comparator: "REGEXP") 86 | not { triggeredBy 'UpstreamCause' } 87 | } 88 | } 89 | agent { 90 | label 'data' 91 | } 92 | options { timeout(time: 20, unit: 'MINUTES') } 93 | environment { 94 | ARTIFACTORY = credentials("${p['artifactory.credentials']}") 95 | DEVELOCITY_ACCESS_KEY = credentials("${p['develocity.access-key']}") 96 | } 97 | steps { 98 | script { 99 | docker.withRegistry(p['docker.proxy.registry'], p['docker.proxy.credentials']) { 100 | docker.image(p['docker.java.main.image']).inside(p['docker.java.inside.docker']) { 101 | sh 'MAVEN_OPTS="-Duser.name=' + "${p['jenkins.user.name']}" + ' -Duser.home=/tmp/jenkins-home" ' + 102 | "./mvnw -s settings.xml -Pci,artifactory " + 103 | "-Ddevelocity.storage.directory=/tmp/jenkins-home/.develocity-root " + 104 | "-Dartifactory.server=${p['artifactory.url']} " + 105 | "-Dartifactory.username=${ARTIFACTORY_USR} " + 106 | "-Dartifactory.password=${ARTIFACTORY_PSW} " + 107 | "-Dartifactory.staging-repository=${p['artifactory.repository.snapshot']} " + 108 | "-Dartifactory.build-name=spring-data-keyvalue " + 109 | "-Dartifactory.build-number=spring-data-keyvalue-${BRANCH_NAME}-build-${BUILD_NUMBER} " + 110 | "-Dmaven.repo.local=/tmp/jenkins-home/.m2/spring-data-keyvalue " + 111 | '-Dmaven.test.skip=true clean deploy -U -B' 112 | } 113 | } 114 | } 115 | } 116 | } 117 | } 118 | 119 | post { 120 | changed { 121 | script { 122 | emailext( 123 | subject: "[${currentBuild.fullDisplayName}] ${currentBuild.currentResult}", 124 | mimeType: 'text/html', 125 | recipientProviders: [[$class: 'CulpritsRecipientProvider'], [$class: 'RequesterRecipientProvider']], 126 | body: "${currentBuild.fullDisplayName} is reported as ${currentBuild.currentResult}") 127 | } 128 | } 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /SECURITY.adoc: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Supported Versions 4 | 5 | Please see the https://spring.io/projects/spring-data[Spring Data] project page for supported versions. 6 | 7 | ## Reporting a Vulnerability 8 | 9 | Please don't raise security vulnerabilities here. Head over to https://pivotal.io/security to learn how to disclose them responsibly. 10 | -------------------------------------------------------------------------------- /ci/pipeline.properties: -------------------------------------------------------------------------------- 1 | # Java versions 2 | java.main.tag=24.0.1_9-jdk-noble 3 | java.next.tag=24.0.1_9-jdk-noble 4 | 5 | # Docker container images - standard 6 | docker.java.main.image=library/eclipse-temurin:${java.main.tag} 7 | docker.java.next.image=library/eclipse-temurin:${java.next.tag} 8 | 9 | # Supported versions of MongoDB 10 | docker.mongodb.6.0.version=6.0.23 11 | docker.mongodb.7.0.version=7.0.20 12 | docker.mongodb.8.0.version=8.0.9 13 | 14 | # Supported versions of Redis 15 | docker.redis.6.version=6.2.13 16 | docker.redis.7.version=7.2.4 17 | 18 | # Docker environment settings 19 | docker.java.inside.basic=-v $HOME:/tmp/jenkins-home 20 | docker.java.inside.docker=-u root -v /var/run/docker.sock:/var/run/docker.sock -v /usr/bin/docker:/usr/bin/docker -v $HOME:/tmp/jenkins-home 21 | 22 | # Credentials 23 | docker.registry= 24 | docker.credentials=hub.docker.com-springbuildmaster 25 | docker.proxy.registry=https://docker-hub.usw1.packages.broadcom.com 26 | docker.proxy.credentials=usw1_packages_broadcom_com-jenkins-token 27 | artifactory.credentials=02bd1690-b54f-4c9f-819d-a77cb7a9822c 28 | artifactory.url=https://repo.spring.io 29 | artifactory.repository.snapshot=libs-snapshot-local 30 | develocity.access-key=gradle_enterprise_secret_access_key 31 | jenkins.user.name=spring-builds+jenkins 32 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "antora": "3.2.0-alpha.6", 4 | "@antora/atlas-extension": "1.0.0-alpha.2", 5 | "@antora/collector-extension": "1.0.0-alpha.7", 6 | "@asciidoctor/tabs": "1.0.0-beta.6", 7 | "@springio/antora-extensions": "1.13.0", 8 | "@springio/asciidoctor-extensions": "1.0.0-alpha.11" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 4.0.0 5 | 6 | org.springframework.data 7 | spring-data-keyvalue 8 | 4.0.0-SNAPSHOT 9 | 10 | Spring Data KeyValue 11 | 12 | 13 | org.springframework.data.build 14 | spring-data-parent 15 | 4.0.0-SNAPSHOT 16 | 17 | 18 | 19 | 20 | 4.0.0-SNAPSHOT 21 | spring.data.keyvalue 22 | 23 | 24 | 25 | 26 | 27 | org.springframework.data 28 | spring-data-commons 29 | ${springdata.commons} 30 | 31 | 32 | 33 | org.springframework 34 | spring-context 35 | 36 | 37 | 38 | org.springframework 39 | spring-tx 40 | 41 | 42 | 43 | com.querydsl 44 | querydsl-collections 45 | ${querydsl} 46 | true 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | com.mysema.maven 55 | apt-maven-plugin 56 | ${apt} 57 | 58 | 59 | generate-test-sources 60 | 61 | test-process 62 | 63 | 64 | target/generated-test-sources 65 | 66 | com.querydsl.apt.QuerydslAnnotationProcessor 67 | 68 | 69 | 70 | 71 | 72 | 73 | org.apache.maven.plugins 74 | maven-assembly-plugin 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | none 84 | 85 | 86 | antora-process-resources 87 | 88 | 89 | 90 | src/main/antora/resources/antora-resources 91 | true 92 | 93 | 94 | 95 | 96 | 97 | antora 98 | 99 | 100 | 101 | org.antora 102 | antora-maven-plugin 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | spring-snapshot 112 | https://repo.spring.io/snapshot 113 | 114 | true 115 | 116 | 117 | false 118 | 119 | 120 | 121 | spring-milestone 122 | https://repo.spring.io/milestone 123 | 124 | 125 | 126 | 127 | -------------------------------------------------------------------------------- /settings.xml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | 8 | spring-plugins-release 9 | ${env.ARTIFACTORY_USR} 10 | ${env.ARTIFACTORY_PSW} 11 | 12 | 13 | spring-libs-snapshot 14 | ${env.ARTIFACTORY_USR} 15 | ${env.ARTIFACTORY_PSW} 16 | 17 | 18 | spring-libs-milestone 19 | ${env.ARTIFACTORY_USR} 20 | ${env.ARTIFACTORY_PSW} 21 | 22 | 23 | spring-libs-release 24 | ${env.ARTIFACTORY_USR} 25 | ${env.ARTIFACTORY_PSW} 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /src/main/antora/antora-playbook.yml: -------------------------------------------------------------------------------- 1 | # PACKAGES antora@3.2.0-alpha.2 @antora/atlas-extension:1.0.0-alpha.1 @antora/collector-extension@1.0.0-alpha.3 @springio/antora-extensions@1.1.0-alpha.2 @asciidoctor/tabs@1.0.0-alpha.12 @opendevise/antora-release-line-extension@1.0.0-alpha.2 2 | # 3 | # The purpose of this Antora playbook is to build the docs in the current branch. 4 | antora: 5 | extensions: 6 | - require: '@springio/antora-extensions' 7 | root_component_name: 'data-keyvalue' 8 | site: 9 | title: Spring Data KeyValue 10 | url: https://docs.spring.io/spring-data/keyvalue/reference/ 11 | content: 12 | sources: 13 | - url: ./../../.. 14 | branches: HEAD 15 | start_path: src/main/antora 16 | worktrees: true 17 | - url: https://github.com/spring-projects/spring-data-commons 18 | # Refname matching: 19 | # https://docs.antora.org/antora/latest/playbook/content-refname-matching/ 20 | branches: [ main, 3.2.x ] 21 | start_path: src/main/antora 22 | asciidoc: 23 | attributes: 24 | hide-uri-scheme: '@' 25 | tabs-sync-option: '@' 26 | extensions: 27 | - '@asciidoctor/tabs' 28 | - '@springio/asciidoctor-extensions' 29 | - '@springio/asciidoctor-extensions/javadoc-extension' 30 | sourcemap: true 31 | urls: 32 | latest_version_segment: '' 33 | runtime: 34 | log: 35 | failure_level: warn 36 | format: pretty 37 | ui: 38 | bundle: 39 | url: https://github.com/spring-io/antora-ui-spring/releases/download/v0.4.16/ui-bundle.zip 40 | snapshot: true 41 | -------------------------------------------------------------------------------- /src/main/antora/antora.yml: -------------------------------------------------------------------------------- 1 | name: data-keyvalue 2 | version: true 3 | title: Spring Data KeyValue 4 | nav: 5 | - modules/ROOT/nav.adoc 6 | ext: 7 | collector: 8 | - run: 9 | command: ./mvnw validate process-resources -Pantora-process-resources 10 | local: true 11 | scan: 12 | dir: target/classes/ 13 | - run: 14 | command: ./mvnw package -Pdistribute 15 | local: true 16 | scan: 17 | dir: target/antora 18 | -------------------------------------------------------------------------------- /src/main/antora/modules/ROOT/nav.adoc: -------------------------------------------------------------------------------- 1 | * xref:index.adoc[Overview] 2 | * xref:keyvalue.adoc[] 3 | * xref:keyvalue/template.adoc[] 4 | 5 | * xref:repositories.adoc[] 6 | ** xref:repositories/core-concepts.adoc[] 7 | ** xref:repositories/definition.adoc[] 8 | ** xref:repositories/create-instances.adoc[] 9 | ** xref:keyvalue/repository/map-repositories.adoc[] 10 | ** xref:keyvalue/value-expressions.adoc[] 11 | ** xref:repositories/query-keywords-reference.adoc[] 12 | ** xref:repositories/query-return-types-reference.adoc[] 13 | 14 | * xref:attachment$api/java/index.html[Javadoc,role=link-external,window=_blank] 15 | * https://github.com/spring-projects/spring-data-commons/wiki[Wiki,role=link-external,window=_blank] 16 | 17 | -------------------------------------------------------------------------------- /src/main/antora/modules/ROOT/pages/commons/upgrade.adoc: -------------------------------------------------------------------------------- 1 | include::{commons}@data-commons::page$upgrade.adoc[] 2 | 3 | Once you’ve decided to upgrade your application, you can find detailed information regarding specific features in the rest of the document. 4 | 5 | Spring Data's documentation is specific to that version, so any information that you find in here will contain the most up-to-date changes that are in that version. 6 | -------------------------------------------------------------------------------- /src/main/antora/modules/ROOT/pages/index.adoc: -------------------------------------------------------------------------------- 1 | [[spring-data-key-value-reference-guide]] 2 | = Spring Data Key-Value 3 | :revnumber: {version} 4 | :revdate: {localdate} 5 | :feature-scroll: true 6 | 7 | Spring Data KeyValue provides connectivity and repository support for the in memory map structures. 8 | It eases development of applications with a consistent programming model that need to access key based storage and servers as foundation for custom adapters._ 9 | 10 | [horizontal] 11 | xref:keyvalue.adoc[Key/Value Storage] :: Support for built in key-value structures 12 | xref:repositories.adoc[Repositories] :: KeyValue Repositories 13 | https://github.com/spring-projects/spring-data-commons/wiki[Wiki] :: What's New, Upgrade Notes, Supported Versions, additional cross-version information. 14 | 15 | Oliver Gierke; Thomas Darimont; Christoph Strobl; Jay Bryant; Mark Paluch 16 | 17 | (C) 2008-{copyright-year} VMware, Inc. 18 | 19 | Copies of this document may be made for your own use and for distribution to others, provided that you do not charge any fee for such copies and further provided that each copy contains this Copyright Notice, whether distributed in print or electronically. 20 | -------------------------------------------------------------------------------- /src/main/antora/modules/ROOT/pages/keyvalue.adoc: -------------------------------------------------------------------------------- 1 | [[key-value]] 2 | = KeyValue 3 | 4 | Spring Data KeyValue provides easy configuration and access to `Map` like structures that associate values with unique keys. 5 | It offers both low-level and high-level abstractions for interacting with the underlying data structure, freeing the user from infrastructural concerns. 6 | 7 | The key-value abstraction within Spring Data Key Value requires an `Adapter` that shields the native store implementation, freeing up `KeyValueTemplate` to work on top of any key-value pair-like structure. 8 | Keys are distributed across <>. 9 | Unless otherwise specified, the class name is used as the default keyspace for an entity. 10 | The following interface definition shows the `KeyValueOperations` interface, which is the heart of Spring Data Key-Value: 11 | 12 | ==== 13 | [source, java] 14 | ---- 15 | interface KeyValueOperations { 16 | 17 | T insert(T objectToInsert); <1> 18 | 19 | void update(Object objectToUpdate); <2> 20 | 21 | void delete(Class type); <3> 22 | 23 | T findById(Object id, Class type); <4> 24 | 25 | Iterable findAllOf(Class type); <5> 26 | 27 | Iterable find(KeyValueQuery query, Class type); <6> 28 | 29 | //... more functionality omitted. 30 | 31 | } 32 | ---- 33 | <1> Inserts the given entity and assigns an ID (if required). 34 | <2> Updates the given entity. 35 | <3> Removes all entities of the matching type. 36 | <4> Returns the entity of the given type with its matching ID. 37 | <5> Returns all entities of the matching type. 38 | <6> Returns a `List` of all entities of the given type that match the criteria of the query. 39 | ==== 40 | 41 | [[key-value.keyspaces]] 42 | == Keyspaces 43 | 44 | Keyspaces define the part of the data structure in which the entity should be kept. 45 | This concept is similar to collections in MongoDB and Elasticsearch, cores in Solr, and tables in JPA. 46 | By default, the keyspace of an entity is extracted from its type, but you can also store entities of different types within one keyspace. 47 | -------------------------------------------------------------------------------- /src/main/antora/modules/ROOT/pages/keyvalue/repository/map-repositories.adoc: -------------------------------------------------------------------------------- 1 | [[key-value.repositories.map]] 2 | = Map Repositories 3 | 4 | Map repositories reside on top of the `KeyValueTemplate`. 5 | Using the default `PredicateQueryCreator` allows deriving query and sort expressions from the given method name, as the following example shows: 6 | 7 | [source, java] 8 | ---- 9 | @Configuration 10 | @EnableMapRepositories 11 | class KeyValueConfig { 12 | 13 | } 14 | 15 | interface PersonRepository implements CrudRepository { 16 | List findByLastname(String lastname); 17 | } 18 | ---- 19 | 20 | == Configuring the QueryEngine 21 | 22 | It is possible to change the `QueryEngine` and use a custom one instead of the default. 23 | The `EnableMapRepositories` annotation allows to configure the by supplying a `QueryEngineFactory` as well as the `QueryCreator` via according attributes. 24 | Please mind that the `QueryEngine` needs to be able to process queries created by the configured `QueryCreator`. 25 | 26 | -------------------------------------------------------------------------------- /src/main/antora/modules/ROOT/pages/keyvalue/template.adoc: -------------------------------------------------------------------------------- 1 | [[key-value.template]] 2 | = KeyValueTemplate 3 | 4 | In its very basic shape, the `KeyValueTemplate` uses a `MapAdapter` that wraps a `ConcurrentHashMap` and that uses link:{spring-framework-docs}core.html#expressions[Spring Expression Language] to run queries and sorting. 5 | 6 | NOTE: The used `KeyValueAdapter` does the heavy lifting when it comes to storing and retrieving data. 7 | The data structure influences performance and multi-threading behavior. 8 | 9 | You can use a different type or pre-initialize the adapter with some values, and you can do so by using various constructors on `MapKeyValueAdapter`, as the following example shows: 10 | 11 | ==== 12 | [source, java] 13 | ---- 14 | @Configuration 15 | class MyConfiguration { 16 | 17 | @Bean 18 | public KeyValueOperations mapKeyValueTemplate() { <1> 19 | return new KeyValueTemplate(keyValueAdapter()); 20 | } 21 | 22 | @Bean 23 | public KeyValueAdapter keyValueAdapter() { 24 | return new MapKeyValueAdapter(ConcurrentHashMap.class); <2> 25 | } 26 | } 27 | ---- 28 | <1> Defines a custom `KeyValueOperations` bean using the default bean name. See documentation and properties of `@EnableMapRepositories` for further customization. 29 | <2> Defines a custom `KeyValueAdapter` bean using a `ConcurrentHashMap` as storage that is used by `KeyValueTemplate`. 30 | ==== 31 | 32 | [[key-value.keyspaces]] 33 | == Keyspaces 34 | 35 | The following example shows a keyspace for a repository of `Person` objects: 36 | 37 | ==== 38 | [source, java] 39 | ---- 40 | @KeySpace("persons") 41 | class Person { 42 | 43 | @Id String id; 44 | String firstname; 45 | String lastname; 46 | } 47 | 48 | class User extends Person { 49 | String username; 50 | } 51 | 52 | template.findAllOf(Person.class); <1> 53 | template.findAllOf(User.class); <2> 54 | ---- 55 | <1> Returns all entities for the `persons` keyspace. 56 | <2> Returns only elements of type `User` stored in `persons` keyspace. 57 | ==== 58 | 59 | TIP: `@KeySpace` supports xref:keyvalue/value-expressions.adoc[Value Expressions] allowing dynamic keyspace configuration. 60 | 61 | [[key-value.keyspaces-custom]] 62 | === Custom KeySpace Annotation 63 | 64 | You can compose your own `KeySpace` annotations for a more domain-centric usage by annotating one of the attributes with `@AliasFor`. 65 | 66 | IMPORTANT: The composed annotation must inherit `@Persistent`. 67 | 68 | The following example shows a custom `@KeySpace` annotation: 69 | 70 | ==== 71 | [source, java] 72 | ---- 73 | @KeySpace 74 | @Persistent 75 | @Retention(RetentionPolicy.RUNTIME) 76 | @Target({ ElementType.TYPE }) 77 | static @interface CacheCentricAnnotation { 78 | 79 | @AliasFor(annotation = KeySpace.class, attribute = "value") 80 | String cacheRegion() default ""; 81 | } 82 | 83 | @CacheCentricAnnotation(cacheRegion = "customers") 84 | class Customer { 85 | //... 86 | } 87 | ---- 88 | ==== 89 | 90 | [[key-value.template-query]] 91 | == Querying 92 | 93 | Running queries is managed by a `QueryEngine`. 94 | As mentioned earlier, you can instruct the `KeyValueAdapter` to use an implementation-specific `QueryEngine` that allows access to native functionality. 95 | When used without further customization, queries can be run by using `SpELQueryEngine`. 96 | 97 | NOTE: For performance reasons, we highly recommend to have at least Spring Framework 4.1.2 or better to make use of link:{spring-framework-docs}core.html#expressions-spel-compilation[compiled SpEL Expressions]. 98 | ("`SpEL`" is short for "`Spring Expression Language`".) You can use the `-Dspring.expression.compiler.mode=IMMEDIATE` switch to enable it. 99 | 100 | The following example shows a query that uses the SpEL: 101 | 102 | ==== 103 | [source,java] 104 | ---- 105 | KeyValueQuery query = new KeyValueQuery("lastname == 'targaryen'"); 106 | List targaryens = template.find(query, Person.class); 107 | ---- 108 | ==== 109 | 110 | IMPORTANT: You must have getters and setters present to query properties when you use SpEL. 111 | 112 | [[key-value.template-sort]] 113 | == Sorting 114 | 115 | Depending on the store implementation provided by the adapter, entities might already be stored in some sorted way but do not necessarily have to be.Again, the underlying `QueryEngine` is capable of performing sort operations. 116 | When used without further customization, sorting is done by using a `SpelPropertyComparator` extracted from the `Sort` clause.The following example shows a query with a `Sort` clause: 117 | 118 | ==== 119 | [source, java] 120 | ---- 121 | KeyValueQuery query = new KeyValueQuery("lastname == 'baratheon'"); 122 | query.setSort(Sort.by(DESC, "age")); 123 | List targaryens = template.find(query, Person.class); 124 | ---- 125 | ==== 126 | 127 | IMPORTANT: Please note that you need to have getters and setters present to sort using SpEL. 128 | -------------------------------------------------------------------------------- /src/main/antora/modules/ROOT/pages/keyvalue/value-expressions.adoc: -------------------------------------------------------------------------------- 1 | include::{commons}@data-commons::page$value-expressions.adoc[] 2 | -------------------------------------------------------------------------------- /src/main/antora/modules/ROOT/pages/repositories.adoc: -------------------------------------------------------------------------------- 1 | [[keyvalyue.repositories]] 2 | = KeyValue Repositories 3 | 4 | This chapter explains the basic foundations of Spring Data repositories and KeyValue specifics. 5 | Before continuing to the specifics, make sure you have a sound understanding of the basic concepts. 6 | 7 | The goal of the Spring Data repository abstraction is to significantly reduce the amount of boilerplate code required to implement data access layers for various persistence stores. 8 | -------------------------------------------------------------------------------- /src/main/antora/modules/ROOT/pages/repositories/core-concepts.adoc: -------------------------------------------------------------------------------- 1 | include::{commons}@data-commons::page$repositories/core-concepts.adoc[] 2 | 3 | [[redis.entity-persistence.state-detection-strategies]] 4 | include::{commons}@data-commons::page$is-new-state-detection.adoc[leveloffset=+1] 5 | -------------------------------------------------------------------------------- /src/main/antora/modules/ROOT/pages/repositories/core-domain-events.adoc: -------------------------------------------------------------------------------- 1 | include::{commons}@data-commons::page$repositories/core-domain-events.adoc[] 2 | -------------------------------------------------------------------------------- /src/main/antora/modules/ROOT/pages/repositories/core-extensions.adoc: -------------------------------------------------------------------------------- 1 | [[core.extensions.querydsl]] 2 | = Querydsl 3 | 4 | Spring Data Redis does not support Querydsl. 5 | -------------------------------------------------------------------------------- /src/main/antora/modules/ROOT/pages/repositories/create-instances.adoc: -------------------------------------------------------------------------------- 1 | include::{commons}@data-commons::page$repositories/create-instances.adoc[] 2 | -------------------------------------------------------------------------------- /src/main/antora/modules/ROOT/pages/repositories/custom-implementations.adoc: -------------------------------------------------------------------------------- 1 | include::{commons}@data-commons::page$repositories/custom-implementations.adoc[] 2 | -------------------------------------------------------------------------------- /src/main/antora/modules/ROOT/pages/repositories/definition.adoc: -------------------------------------------------------------------------------- 1 | include::{commons}@data-commons::page$repositories/definition.adoc[] 2 | -------------------------------------------------------------------------------- /src/main/antora/modules/ROOT/pages/repositories/null-handling.adoc: -------------------------------------------------------------------------------- 1 | include::{commons}@data-commons::page$repositories/null-handling.adoc[] 2 | -------------------------------------------------------------------------------- /src/main/antora/modules/ROOT/pages/repositories/object-mapping.adoc: -------------------------------------------------------------------------------- 1 | include::{commons}@data-commons::page$object-mapping.adoc[] 2 | -------------------------------------------------------------------------------- /src/main/antora/modules/ROOT/pages/repositories/projections.adoc: -------------------------------------------------------------------------------- 1 | [[cassandra.projections]] 2 | = Projections 3 | 4 | include::{commons}@data-commons::page$repositories/projections.adoc[leveloffset=+1] 5 | -------------------------------------------------------------------------------- /src/main/antora/modules/ROOT/pages/repositories/query-keywords-reference.adoc: -------------------------------------------------------------------------------- 1 | include::{commons}@data-commons::page$repositories/query-keywords-reference.adoc[] 2 | -------------------------------------------------------------------------------- /src/main/antora/modules/ROOT/pages/repositories/query-methods-details.adoc: -------------------------------------------------------------------------------- 1 | include::{commons}@data-commons::page$repositories/query-methods-details.adoc[] 2 | -------------------------------------------------------------------------------- /src/main/antora/modules/ROOT/pages/repositories/query-return-types-reference.adoc: -------------------------------------------------------------------------------- 1 | include::{commons}@data-commons::page$repositories/query-return-types-reference.adoc[] 2 | -------------------------------------------------------------------------------- /src/main/antora/resources/antora-resources/antora.yml: -------------------------------------------------------------------------------- 1 | version: ${antora-component.version} 2 | prerelease: ${antora-component.prerelease} 3 | 4 | asciidoc: 5 | attributes: 6 | copyright-year: ${current.year} 7 | version: ${project.version} 8 | springversionshort: ${spring.short} 9 | springversion: ${spring} 10 | attribute-missing: 'warn' 11 | commons: ${springdata.commons.docs} 12 | include-xml-namespaces: false 13 | spring-data-commons-docs-url: https://docs.spring.io/spring-data/commons/reference 14 | spring-data-commons-javadoc-base: https://docs.spring.io/spring-data/commons/docs/${springdata.commons}/api/ 15 | springdocsurl: https://docs.spring.io/spring-framework/reference/{springversionshort} 16 | springjavadocurl: https://docs.spring.io/spring-framework/docs/${spring}/javadoc-api 17 | spring-framework-docs: '{springdocsurl}' 18 | spring-framework-javadoc: '{springjavadocurl}' 19 | springhateoasversion: ${spring-hateoas} 20 | releasetrainversion: ${releasetrain} 21 | store: KeyValue 22 | -------------------------------------------------------------------------------- /src/main/asciidoc/images/epub-cover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spring-projects/spring-data-keyvalue/8d926d53937e31bf082e1193c0eba032d8941d4f/src/main/asciidoc/images/epub-cover.png -------------------------------------------------------------------------------- /src/main/java/org/springframework/data/keyvalue/annotation/KeySpace.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014-2025 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.data.keyvalue.annotation; 17 | 18 | import static java.lang.annotation.ElementType.*; 19 | 20 | import java.lang.annotation.Documented; 21 | import java.lang.annotation.Retention; 22 | import java.lang.annotation.RetentionPolicy; 23 | import java.lang.annotation.Target; 24 | 25 | import org.springframework.data.annotation.Persistent; 26 | 27 | /** 28 | * Marker interface for methods with {@link Persistent} annotations indicating the presence of a dedicated keyspace the 29 | * entity should reside in. If present the value will be picked up for resolving the keyspace. The {@link #value()} 30 | * attribute supports Value Expressions to dynamically resolve the keyspace based on a per-operation basis. 31 | * 32 | *
33 |  * @Persistent
34 |  * @Retention(RetentionPolicy.RUNTIME)
35 |  * @Target({ ElementType.TYPE })
36 |  * static @interface CacheCentricAnnotation {
37 |  *
38 |  * 	@AliasFor(annotation = KeySpace.class, attribute = "value")
39 |  * 	String cacheRegion() default "";
40 |  * }
41 |  *
42 |  * @CacheCentricAnnotation(cacheRegion = "customers")
43 |  * class Customer {
44 |  * 	// ...
45 |  * }
46 |  * 
47 | * 48 | * Can also be directly used on types to indicate the keyspace. 49 | * 50 | *
51 |  * @KeySpace("persons")
52 |  * public class Foo {
53 |  *
54 |  * }
55 |  * 
56 | * 57 | * @author Christoph Strobl 58 | * @author Mark Paluch 59 | */ 60 | @Documented 61 | @Retention(RetentionPolicy.RUNTIME) 62 | @Target(value = { METHOD, TYPE }) 63 | public @interface KeySpace { 64 | 65 | /** 66 | * @return dedicated keyspace the entity should reside in. 67 | */ 68 | String value() default ""; 69 | } 70 | -------------------------------------------------------------------------------- /src/main/java/org/springframework/data/keyvalue/annotation/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Key-Value annotations for declarative keyspace configuration. 3 | */ 4 | @org.jspecify.annotations.NullMarked 5 | package org.springframework.data.keyvalue.annotation; 6 | -------------------------------------------------------------------------------- /src/main/java/org/springframework/data/keyvalue/aot/KeyValueRuntimeHints.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022-2025 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.data.keyvalue.aot; 17 | 18 | import java.util.Arrays; 19 | import java.util.List; 20 | 21 | import org.jspecify.annotations.Nullable; 22 | import org.springframework.aot.hint.ExecutableMode; 23 | import org.springframework.aot.hint.MemberCategory; 24 | import org.springframework.aot.hint.RuntimeHints; 25 | import org.springframework.aot.hint.RuntimeHintsRegistrar; 26 | import org.springframework.aot.hint.TypeReference; 27 | import org.springframework.data.keyvalue.repository.query.KeyValuePartTreeQuery; 28 | 29 | /** 30 | * {@link RuntimeHintsRegistrar} for KeyValue. 31 | * 32 | * @author Christoph Strobl 33 | * @since 3.0 34 | */ 35 | class KeyValueRuntimeHints implements RuntimeHintsRegistrar { 36 | 37 | @Override 38 | public void registerHints(RuntimeHints hints, @Nullable ClassLoader classLoader) { 39 | 40 | // REFLECTION 41 | hints.reflection().registerTypes( 42 | Arrays.asList( 43 | TypeReference.of(org.springframework.data.keyvalue.repository.support.SimpleKeyValueRepository.class), 44 | TypeReference.of(KeyValuePartTreeQuery.class)), 45 | hint -> hint.withMembers(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS, MemberCategory.INVOKE_DECLARED_METHODS)); 46 | 47 | hints.reflection().registerType(TypeReference.of("java.util.Comparators.NaturalOrderComparator"), 48 | builder -> builder.withMethod("compare", 49 | List.of(TypeReference.of(Object.class), TypeReference.of(Object.class)), ExecutableMode.INVOKE)); 50 | 51 | hints.reflection().registerType(TypeReference.of("java.util.Comparators.NullComparator"), 52 | builder -> builder.withMethod("compare", 53 | List.of(TypeReference.of(Object.class), TypeReference.of(Object.class)), ExecutableMode.INVOKE)); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/main/java/org/springframework/data/keyvalue/aot/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Support classes for key-value ahead of time computation 3 | */ 4 | @org.jspecify.annotations.NullMarked 5 | package org.springframework.data.keyvalue.aot; 6 | -------------------------------------------------------------------------------- /src/main/java/org/springframework/data/keyvalue/core/AbstractKeyValueAdapter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014-2025 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.data.keyvalue.core; 17 | 18 | import java.util.Collection; 19 | import java.util.Comparator; 20 | 21 | import org.jspecify.annotations.Nullable; 22 | import org.springframework.data.keyvalue.core.query.KeyValueQuery; 23 | 24 | /** 25 | * Base implementation of {@link KeyValueAdapter} holds {@link QueryEngine} to delegate {@literal find} and 26 | * {@literal count} execution to. 27 | * 28 | * @author Christoph Strobl 29 | * @author Mark Paluch 30 | */ 31 | public abstract class AbstractKeyValueAdapter implements KeyValueAdapter { 32 | 33 | private final QueryEngine engine; 34 | 35 | /** 36 | * Creates new {@link AbstractKeyValueAdapter} with using the default query engine. 37 | */ 38 | protected AbstractKeyValueAdapter() { 39 | this((QueryEngine) null); 40 | } 41 | 42 | /** 43 | * Creates new {@link AbstractKeyValueAdapter} with using the default query engine and provided comparator for sorting. 44 | * 45 | * @param sortAccessor must not be {@literal null}. 46 | * @since 3.1.10 47 | */ 48 | protected AbstractKeyValueAdapter(SortAccessor> sortAccessor) { 49 | this(new PredicateQueryEngine(sortAccessor)); 50 | } 51 | 52 | /** 53 | * Creates new {@link AbstractKeyValueAdapter} with using the default query engine. 54 | * 55 | * @param engine will be defaulted to {@link SpelQueryEngine} if {@literal null}. 56 | */ 57 | protected AbstractKeyValueAdapter(@Nullable QueryEngine engine) { 58 | 59 | this.engine = engine != null ? engine : new PredicateQueryEngine(); 60 | this.engine.registerAdapter(this); 61 | } 62 | 63 | /** 64 | * Get the {@link QueryEngine} used. 65 | * 66 | * @return 67 | */ 68 | protected QueryEngine getQueryEngine() { 69 | return engine; 70 | } 71 | 72 | @Override 73 | public @Nullable T get(Object id, String keyspace, Class type) { 74 | return type.cast(get(id, keyspace)); 75 | } 76 | 77 | @Override 78 | public @Nullable T delete(Object id, String keyspace, Class type) { 79 | return type.cast(delete(id, keyspace)); 80 | } 81 | 82 | @Override 83 | public Iterable find(KeyValueQuery query, String keyspace, Class type) { 84 | return engine.execute(query, keyspace, type); 85 | } 86 | 87 | @Override 88 | public Collection find(KeyValueQuery query, String keyspace) { 89 | return engine.execute(query, keyspace); 90 | } 91 | 92 | @Override 93 | public long count(KeyValueQuery query, String keyspace) { 94 | return engine.count(query, keyspace); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/main/java/org/springframework/data/keyvalue/core/CriteriaAccessor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014-2025 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.data.keyvalue.core; 17 | 18 | import org.jspecify.annotations.Nullable; 19 | import org.springframework.data.keyvalue.core.query.KeyValueQuery; 20 | 21 | /** 22 | * Resolves the criteria object from given {@link KeyValueQuery}. 23 | * 24 | * @author Christoph Strobl 25 | * @author Mark Paluch 26 | * @param 27 | */ 28 | public interface CriteriaAccessor { 29 | 30 | /** 31 | * Checks and reads {@link KeyValueQuery#getCriteria()} of given {@link KeyValueQuery}. Might also apply additional 32 | * transformation to match the desired type. 33 | * 34 | * @param query must not be {@literal null}. 35 | * @return the criteria extracted from the query. Can be {@literal null}. 36 | * @throws IllegalArgumentException in case the criteria is not valid for usage with specific 37 | * {@link CriteriaAccessor}. 38 | */ 39 | @Nullable 40 | T resolve(KeyValueQuery query); 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/org/springframework/data/keyvalue/core/DefaultIdentifierGenerator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014-2025 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.data.keyvalue.core; 17 | 18 | import java.security.NoSuchAlgorithmException; 19 | import java.security.SecureRandom; 20 | import java.util.Arrays; 21 | import java.util.List; 22 | import java.util.UUID; 23 | import java.util.concurrent.atomic.AtomicReference; 24 | 25 | import org.springframework.dao.InvalidDataAccessApiUsageException; 26 | import org.springframework.data.util.TypeInformation; 27 | import org.springframework.util.ClassUtils; 28 | import org.springframework.util.StringUtils; 29 | 30 | /** 31 | * Default implementation of {@link IdentifierGenerator} to generate identifiers of types {@link UUID}, String, 32 | * 33 | * @author Christoph Strobl 34 | * @author Oliver Gierke 35 | */ 36 | enum DefaultIdentifierGenerator implements IdentifierGenerator { 37 | 38 | INSTANCE; 39 | 40 | private final AtomicReference secureRandom = new AtomicReference<>(null); 41 | 42 | @Override 43 | @SuppressWarnings("unchecked") 44 | public T generateIdentifierOfType(TypeInformation identifierType) { 45 | 46 | Class type = identifierType.getType(); 47 | 48 | if (ClassUtils.isAssignable(UUID.class, type)) { 49 | return (T) UUID.randomUUID(); 50 | } else if (ClassUtils.isAssignable(String.class, type)) { 51 | return (T) UUID.randomUUID().toString(); 52 | } else if (ClassUtils.isAssignable(Integer.class, type)) { 53 | return (T) Integer.valueOf(getSecureRandom().nextInt()); 54 | } else if (ClassUtils.isAssignable(Long.class, type)) { 55 | return (T) Long.valueOf(getSecureRandom().nextLong()); 56 | } 57 | 58 | throw new InvalidDataAccessApiUsageException( 59 | String.format("Identifier cannot be generated for %s; Supported types are: UUID, String, Integer, and Long", 60 | identifierType.getType().getName())); 61 | } 62 | 63 | private SecureRandom getSecureRandom() { 64 | 65 | SecureRandom secureRandom = this.secureRandom.get(); 66 | if (secureRandom != null) { 67 | return secureRandom; 68 | } 69 | 70 | for (String algorithm : OsTools.secureRandomAlgorithmNames()) { 71 | try { 72 | secureRandom = SecureRandom.getInstance(algorithm); 73 | } catch (NoSuchAlgorithmException e) { 74 | // ignore and try next. 75 | } 76 | } 77 | 78 | if (secureRandom == null) { 79 | throw new InvalidDataAccessApiUsageException( 80 | String.format("Could not create SecureRandom instance for one of the algorithms '%s'", 81 | StringUtils.collectionToCommaDelimitedString(OsTools.secureRandomAlgorithmNames()))); 82 | } 83 | 84 | this.secureRandom.compareAndSet(null, secureRandom); 85 | 86 | return secureRandom; 87 | } 88 | 89 | /** 90 | * @author Christoph Strobl 91 | * @since 1.1.2 92 | */ 93 | private static class OsTools { 94 | 95 | private static final String OPERATING_SYSTEM_NAME = System.getProperty("os.name").toLowerCase(); 96 | 97 | private static final List SECURE_RANDOM_ALGORITHMS_LINUX_OSX_SOLARIS = Arrays.asList("NativePRNGBlocking", 98 | "NativePRNGNonBlocking", "NativePRNG", "SHA1PRNG"); 99 | private static final List SECURE_RANDOM_ALGORITHMS_WINDOWS = Arrays.asList("SHA1PRNG", "Windows-PRNG"); 100 | 101 | static List secureRandomAlgorithmNames() { 102 | return OPERATING_SYSTEM_NAME.contains("win") ? SECURE_RANDOM_ALGORITHMS_WINDOWS 103 | : SECURE_RANDOM_ALGORITHMS_LINUX_OSX_SOLARIS; 104 | } 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/main/java/org/springframework/data/keyvalue/core/ForwardingCloseableIterator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015-2025 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.data.keyvalue.core; 17 | 18 | import java.util.Iterator; 19 | 20 | import org.jspecify.annotations.Nullable; 21 | import org.springframework.data.util.CloseableIterator; 22 | import org.springframework.util.Assert; 23 | 24 | /** 25 | * Forwards {@link CloseableIterator} invocations to the configured {@link Iterator} delegate. 26 | * 27 | * @author Christoph Strobl 28 | * @author Thomas Darimont 29 | * @author Oliver Gierke 30 | * @author Mark Paluch 31 | */ 32 | public class ForwardingCloseableIterator implements CloseableIterator { 33 | 34 | private final Iterator delegate; 35 | private final @Nullable Runnable closeHandler; 36 | 37 | /** 38 | * Creates a new {@link ForwardingCloseableIterator}. 39 | * 40 | * @param delegate must not be {@literal null}. 41 | */ 42 | public ForwardingCloseableIterator(Iterator delegate) { 43 | this(delegate, null); 44 | } 45 | 46 | /** 47 | * Creates a new {@link ForwardingCloseableIterator} that invokes the configured {@code closeHandler} on 48 | * {@link #close()}. 49 | * 50 | * @param delegate must not be {@literal null}. 51 | * @param closeHandler may be {@literal null}. 52 | */ 53 | public ForwardingCloseableIterator(Iterator delegate, @Nullable Runnable closeHandler) { 54 | 55 | Assert.notNull(delegate, "Delegate iterator must not be null"); 56 | 57 | this.delegate = delegate; 58 | this.closeHandler = closeHandler; 59 | } 60 | 61 | @Override 62 | public boolean hasNext() { 63 | return delegate.hasNext(); 64 | } 65 | 66 | @Override 67 | public T next() { 68 | return delegate.next(); 69 | } 70 | 71 | @Override 72 | public void close() { 73 | if (closeHandler != null) { 74 | closeHandler.run(); 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/main/java/org/springframework/data/keyvalue/core/GeneratingIdAccessor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014-2025 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.data.keyvalue.core; 17 | 18 | import org.jspecify.annotations.Nullable; 19 | import org.springframework.data.mapping.IdentifierAccessor; 20 | import org.springframework.data.mapping.PersistentProperty; 21 | import org.springframework.data.mapping.PersistentPropertyAccessor; 22 | import org.springframework.util.Assert; 23 | 24 | /** 25 | * {@link IdentifierAccessor} adding a {@link #getOrGenerateIdentifier()} to automatically generate an identifier and 26 | * set it on the underling bean instance. 27 | * 28 | * @author Oliver Gierke 29 | * @author Mark Paluch 30 | * @see #getOrGenerateIdentifier() 31 | */ 32 | class GeneratingIdAccessor implements IdentifierAccessor { 33 | 34 | private final PersistentPropertyAccessor accessor; 35 | private final PersistentProperty identifierProperty; 36 | private final IdentifierGenerator generator; 37 | 38 | /** 39 | * Creates a new {@link GeneratingIdAccessor} using the given {@link PersistentPropertyAccessor}, identifier property 40 | * and {@link IdentifierGenerator}. 41 | * 42 | * @param accessor must not be {@literal null}. 43 | * @param identifierProperty must not be {@literal null}. 44 | * @param generator must not be {@literal null}. 45 | */ 46 | GeneratingIdAccessor(PersistentPropertyAccessor accessor, PersistentProperty identifierProperty, 47 | IdentifierGenerator generator) { 48 | 49 | Assert.notNull(accessor, "PersistentPropertyAccessor must not be null"); 50 | Assert.notNull(identifierProperty, "Identifier property must not be null"); 51 | Assert.notNull(generator, "IdentifierGenerator must not be null"); 52 | 53 | this.accessor = accessor; 54 | this.identifierProperty = identifierProperty; 55 | this.generator = generator; 56 | } 57 | 58 | @Override 59 | public @Nullable Object getIdentifier() { 60 | return accessor.getProperty(identifierProperty); 61 | } 62 | 63 | /** 64 | * Returns the identifier value of the backing bean or generates a new one using the configured 65 | * {@link IdentifierGenerator}. 66 | * 67 | * @return 68 | */ 69 | Object getOrGenerateIdentifier() { 70 | 71 | Object existingIdentifier = getIdentifier(); 72 | 73 | if (existingIdentifier != null) { 74 | return existingIdentifier; 75 | } 76 | 77 | Object generatedIdentifier = generator.generateIdentifierOfType(identifierProperty.getTypeInformation()); 78 | accessor.setProperty(identifierProperty, generatedIdentifier); 79 | 80 | return generatedIdentifier; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/main/java/org/springframework/data/keyvalue/core/IdentifierGenerator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014-2025 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.data.keyvalue.core; 17 | 18 | import org.springframework.data.util.TypeInformation; 19 | 20 | /** 21 | * API for components generating identifiers. 22 | * 23 | * @author Christoph Strobl 24 | * @author Oliver Gierke 25 | */ 26 | public interface IdentifierGenerator { 27 | 28 | /** 29 | * Creates an identifier of the given type. 30 | * 31 | * @param type must not be {@literal null}. 32 | * @return an identifier of the given type. 33 | */ 34 | T generateIdentifierOfType(TypeInformation type); 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/org/springframework/data/keyvalue/core/IterableConverter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015-2025 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.data.keyvalue.core; 17 | 18 | import java.util.ArrayList; 19 | import java.util.Collection; 20 | import java.util.Collections; 21 | import java.util.List; 22 | 23 | import org.jspecify.annotations.Nullable; 24 | import org.springframework.lang.Contract; 25 | 26 | /** 27 | * Converter capable of transforming a given {@link Iterable} into a collection type. 28 | * 29 | * @author Christoph Strobl 30 | * @author Mark Paluch 31 | */ 32 | public final class IterableConverter { 33 | 34 | private IterableConverter() {} 35 | 36 | /** 37 | * Converts a given {@link Iterable} into a {@link List} 38 | * 39 | * @param source 40 | * @return {@link Collections#emptyList()} when source is {@literal null}. 41 | */ 42 | @Contract("_ -> !null") 43 | public static List toList(@Nullable Iterable source) { 44 | 45 | if(source == null) { 46 | return Collections.emptyList(); 47 | } 48 | 49 | if (source instanceof List) { 50 | return (List) source; 51 | } 52 | 53 | if (source instanceof Collection) { 54 | return new ArrayList<>((Collection) source); 55 | } 56 | 57 | List result = new ArrayList<>(); 58 | for (T value : source) { 59 | result.add(value); 60 | } 61 | return result; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/main/java/org/springframework/data/keyvalue/core/KeyValueCallback.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014-2025 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.data.keyvalue.core; 17 | 18 | import org.jspecify.annotations.Nullable; 19 | 20 | /** 21 | * Generic callback interface for code that operates on a {@link KeyValueAdapter}. This is particularly useful for 22 | * delegating code that needs to work closely on the underlying key/value store implementation. 23 | * 24 | * @author Christoph Strobl 25 | * @author Mark Paluch 26 | * @param 27 | */ 28 | public interface KeyValueCallback { 29 | 30 | /** 31 | * Gets called by {@code KeyValueTemplate#execute(KeyValueCallback)}. Allows for returning a result object created 32 | * within the callback, i.e. a domain object or a collection of domain objects. 33 | * 34 | * @param adapter 35 | * @return 36 | */ 37 | @Nullable 38 | T doInKeyValue(KeyValueAdapter adapter); 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/org/springframework/data/keyvalue/core/KeyValuePersistenceExceptionTranslator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014-2025 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.data.keyvalue.core; 17 | 18 | import java.util.NoSuchElementException; 19 | 20 | import org.jspecify.annotations.Nullable; 21 | import org.springframework.dao.DataAccessException; 22 | import org.springframework.dao.DataRetrievalFailureException; 23 | import org.springframework.dao.support.PersistenceExceptionTranslator; 24 | import org.springframework.util.Assert; 25 | 26 | /** 27 | * Simple {@link PersistenceExceptionTranslator} implementation for key/value stores that converts the given runtime 28 | * exception to an appropriate exception from the {@code org.springframework.dao} hierarchy. 29 | * 30 | * @author Christoph Strobl 31 | * @author Mark Paluch 32 | */ 33 | public class KeyValuePersistenceExceptionTranslator implements PersistenceExceptionTranslator { 34 | 35 | @Override 36 | @SuppressWarnings("NullAway") 37 | public @Nullable DataAccessException translateExceptionIfPossible(RuntimeException exception) { 38 | 39 | Assert.notNull(exception, "Exception must not be null"); 40 | 41 | if (exception instanceof DataAccessException) { 42 | return (DataAccessException) exception; 43 | } 44 | 45 | if (exception instanceof NoSuchElementException || exception instanceof IndexOutOfBoundsException 46 | || exception instanceof IllegalStateException) { 47 | return new DataRetrievalFailureException(exception.getMessage(), exception); 48 | } 49 | 50 | if (exception.getClass().getName().startsWith("java")) { 51 | return new UncategorizedKeyValueException(exception.getMessage(), exception); 52 | } 53 | 54 | return null; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/main/java/org/springframework/data/keyvalue/core/PathSortAccessor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024-2025 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.data.keyvalue.core; 17 | 18 | import java.util.Comparator; 19 | import java.util.Optional; 20 | 21 | import org.jspecify.annotations.Nullable; 22 | import org.springframework.data.domain.Sort.Direction; 23 | import org.springframework.data.domain.Sort.NullHandling; 24 | import org.springframework.data.domain.Sort.Order; 25 | import org.springframework.data.keyvalue.core.query.KeyValueQuery; 26 | 27 | /** 28 | * @author Christoph Strobl 29 | * @since 3.1.10 30 | */ 31 | public class PathSortAccessor implements SortAccessor> { 32 | 33 | @Override 34 | public @Nullable Comparator resolve(KeyValueQuery query) { 35 | 36 | if (query.getSort().isUnsorted()) { 37 | return null; 38 | } 39 | 40 | Optional> comparator = Optional.empty(); 41 | for (Order order : query.getSort()) { 42 | 43 | PropertyPathComparator pathSort = new PropertyPathComparator<>(order.getProperty()); 44 | 45 | if (Direction.DESC.equals(order.getDirection())) { 46 | 47 | pathSort.desc(); 48 | 49 | if (!NullHandling.NATIVE.equals(order.getNullHandling())) { 50 | pathSort = NullHandling.NULLS_FIRST.equals(order.getNullHandling()) ? pathSort.nullsFirst() 51 | : pathSort.nullsLast(); 52 | } 53 | } 54 | 55 | if (!comparator.isPresent()) { 56 | comparator = Optional.of(pathSort); 57 | } else { 58 | 59 | PropertyPathComparator pathSortToUse = pathSort; 60 | comparator = comparator.map(it -> it.thenComparing(pathSortToUse)); 61 | } 62 | } 63 | 64 | return comparator.orElseThrow( 65 | () -> new IllegalStateException("No sort definitions have been added to this CompoundComparator to compare")); 66 | 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/main/java/org/springframework/data/keyvalue/core/PredicateQueryEngine.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024-2025 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.data.keyvalue.core; 17 | 18 | import java.util.Collection; 19 | import java.util.Comparator; 20 | import java.util.List; 21 | import java.util.function.Predicate; 22 | import java.util.stream.Collectors; 23 | import java.util.stream.Stream; 24 | 25 | import org.jspecify.annotations.Nullable; 26 | import org.springframework.data.keyvalue.core.query.KeyValueQuery; 27 | import org.springframework.lang.Contract; 28 | 29 | /** 30 | * {@link QueryEngine} implementation specific for executing {@link Predicate} based {@link KeyValueQuery} against 31 | * {@link KeyValueAdapter}. 32 | * 33 | * @author Christoph Strobl 34 | * @since 3.3 35 | */ 36 | public class PredicateQueryEngine extends QueryEngine, Comparator> { 37 | 38 | /** 39 | * Creates a new {@link PredicateQueryEngine}. 40 | */ 41 | public PredicateQueryEngine() { 42 | this(new PathSortAccessor()); 43 | } 44 | 45 | /** 46 | * Creates a new query engine using provided {@link SortAccessor accessor} for sorting results. 47 | */ 48 | public PredicateQueryEngine(SortAccessor> sortAccessor) { 49 | super(new CriteriaAccessor<>() { 50 | 51 | @Override 52 | public @Nullable Predicate resolve(KeyValueQuery query) { 53 | return (Predicate) query.getCriteria(); 54 | } 55 | }, sortAccessor); 56 | } 57 | 58 | @Override 59 | public Collection execute(@Nullable Predicate criteria, @Nullable Comparator sort, long offset, int rows, 60 | String keyspace) { 61 | return sortAndFilterMatchingRange(getRequiredAdapter().getAllOf(keyspace), criteria, sort, offset, rows); 62 | } 63 | 64 | @Override 65 | public long count(@Nullable Predicate criteria, String keyspace) { 66 | return filterMatchingRange(IterableConverter.toList(getRequiredAdapter().getAllOf(keyspace)), criteria, -1, -1) 67 | .size(); 68 | } 69 | 70 | @SuppressWarnings({ "unchecked", "rawtypes" }) 71 | private List sortAndFilterMatchingRange(Iterable source, @Nullable Predicate criteria, 72 | @Nullable Comparator sort, long offset, int rows) { 73 | 74 | List tmp = IterableConverter.toList(source); 75 | if (sort != null) { 76 | tmp.sort(sort); 77 | } 78 | 79 | return filterMatchingRange(tmp, criteria, offset, rows); 80 | } 81 | 82 | @Contract("!null, _, _, _ -> !null") 83 | private static List filterMatchingRange(List source, @Nullable Predicate criteria, long offset, int rows) { 84 | 85 | Stream stream = source.stream(); 86 | 87 | if (criteria != null) { 88 | stream = stream.filter((Predicate) criteria); 89 | } 90 | if (offset > 0) { 91 | stream = stream.skip(offset); 92 | } 93 | if (rows > 0) { 94 | stream = stream.limit(rows); 95 | } 96 | 97 | return stream.collect(Collectors.toList()); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/main/java/org/springframework/data/keyvalue/core/PropertyPathComparator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024-2025 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.data.keyvalue.core; 17 | 18 | import java.util.Comparator; 19 | import java.util.HashMap; 20 | import java.util.Map; 21 | 22 | import org.jspecify.annotations.Nullable; 23 | import org.springframework.data.mapping.PropertyPath; 24 | import org.springframework.lang.Contract; 25 | 26 | /** 27 | * {@link Comparator} implementation to compare objects based on a {@link PropertyPath}. This comparator obtains the 28 | * value at {@link PropertyPath} from the {@link #compare(Object, Object) given comparison objects} and then performs 29 | * the comparison. 30 | * 31 | * @author Christoph Strobl 32 | * @author Mark Paluch 33 | * @since 3.1.10 34 | */ 35 | public class PropertyPathComparator implements Comparator { 36 | 37 | private static final Comparator NULLS_FIRST = Comparator.nullsFirst(Comparator.naturalOrder()); 38 | private static final Comparator NULLS_LAST = Comparator.nullsLast(Comparator.naturalOrder()); 39 | 40 | private final String path; 41 | 42 | private boolean asc = true; 43 | private boolean nullsFirst = true; 44 | 45 | private final Map, PropertyPath> pathCache = new HashMap<>(2); 46 | 47 | public PropertyPathComparator(String path) { 48 | this.path = path; 49 | } 50 | 51 | @Override 52 | public int compare(@Nullable T o1, @Nullable T o2) { 53 | 54 | if (o1 == null && o2 == null) { 55 | return 0; 56 | } 57 | if (o1 == null) { 58 | return nullsFirst ? 1 : -1; 59 | } 60 | if (o2 == null) { 61 | return nullsFirst ? 1 : -1; 62 | } 63 | 64 | PropertyPath propertyPath = pathCache.computeIfAbsent(o1.getClass(), it -> PropertyPath.from(path, it)); 65 | Object value1 = getCompareValue(o1, propertyPath); 66 | Object value2 = getCompareValue(o2, propertyPath); 67 | 68 | return getComparator().compare(value1, value2) * (asc ? 1 : -1); 69 | } 70 | 71 | protected @Nullable Object getCompareValue(S object, PropertyPath propertyPath) { 72 | return new SimplePropertyPathAccessor<>(object).getValue(propertyPath); 73 | } 74 | 75 | @SuppressWarnings("unchecked") 76 | private Comparator<@Nullable Object> getComparator() { 77 | return (Comparator) (nullsFirst ? NULLS_FIRST : NULLS_LAST); 78 | } 79 | 80 | /** 81 | * Sort {@literal ascending}. 82 | * 83 | * @return 84 | */ 85 | @Contract("-> this") 86 | public PropertyPathComparator<@Nullable T> asc() { 87 | this.asc = true; 88 | return this; 89 | } 90 | 91 | /** 92 | * Sort {@literal descending}. 93 | * 94 | * @return 95 | */ 96 | @Contract("-> this") 97 | public PropertyPathComparator<@Nullable T> desc() { 98 | this.asc = false; 99 | return this; 100 | } 101 | 102 | /** 103 | * Sort {@literal null} values first. 104 | * 105 | * @return 106 | */ 107 | @Contract("-> this") 108 | public PropertyPathComparator<@Nullable T> nullsFirst() { 109 | this.nullsFirst = true; 110 | return this; 111 | } 112 | 113 | /** 114 | * Sort {@literal null} values last. 115 | * 116 | * @return 117 | */ 118 | @Contract("-> this") 119 | public PropertyPathComparator<@Nullable T> nullsLast() { 120 | this.nullsFirst = false; 121 | return this; 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /src/main/java/org/springframework/data/keyvalue/core/QueryEngine.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014-2025 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.data.keyvalue.core; 17 | 18 | import java.util.Collection; 19 | import java.util.Optional; 20 | 21 | import org.jspecify.annotations.Nullable; 22 | import org.springframework.data.keyvalue.core.query.KeyValueQuery; 23 | 24 | /** 25 | * Base implementation for accessing and executing {@link KeyValueQuery} against a {@link KeyValueAdapter}. 26 | * 27 | * @author Christoph Strobl 28 | * @author Mark Paluch 29 | * @param 30 | * @param 31 | * @param 32 | */ 33 | public abstract class QueryEngine { 34 | 35 | private final Optional> criteriaAccessor; 36 | private final Optional> sortAccessor; 37 | 38 | private @Nullable ADAPTER adapter; 39 | 40 | public QueryEngine(@Nullable CriteriaAccessor criteriaAccessor, @Nullable SortAccessor sortAccessor) { 41 | 42 | this.criteriaAccessor = Optional.ofNullable(criteriaAccessor); 43 | this.sortAccessor = Optional.ofNullable(sortAccessor); 44 | } 45 | 46 | /** 47 | * Extract query attributes and delegate to concrete execution. 48 | * 49 | * @param query 50 | * @param keyspace 51 | * @return 52 | */ 53 | public Collection execute(KeyValueQuery query, String keyspace) { 54 | 55 | CRITERIA criteria = this.criteriaAccessor.map(it -> it.resolve(query)).orElse(null); 56 | SORT sort = this.sortAccessor.map(it -> it.resolve(query)).orElse(null); 57 | 58 | return execute(criteria, sort, query.getOffset(), query.getRows(), keyspace); 59 | } 60 | 61 | /** 62 | * Extract query attributes and delegate to concrete execution. 63 | * 64 | * @param query 65 | * @param keyspace 66 | * @return 67 | */ 68 | public Collection execute(KeyValueQuery query, String keyspace, Class type) { 69 | 70 | CRITERIA criteria = this.criteriaAccessor.map(it -> it.resolve(query)).orElse(null); 71 | SORT sort = this.sortAccessor.map(it -> it.resolve(query)).orElse(null); 72 | 73 | return execute(criteria, sort, query.getOffset(), query.getRows(), keyspace, type); 74 | } 75 | 76 | /** 77 | * Extract query attributes and delegate to concrete execution. 78 | * 79 | * @param query 80 | * @param keyspace 81 | * @return 82 | */ 83 | public long count(KeyValueQuery query, String keyspace) { 84 | 85 | CRITERIA criteria = this.criteriaAccessor.map(it -> it.resolve(query)).orElse(null); 86 | return count(criteria, keyspace); 87 | } 88 | 89 | /** 90 | * @param criteria 91 | * @param sort 92 | * @param offset 93 | * @param rows 94 | * @param keyspace 95 | * @return 96 | */ 97 | public abstract Collection execute(@Nullable CRITERIA criteria, @Nullable SORT sort, long offset, int rows, 98 | String keyspace); 99 | 100 | /** 101 | * @param criteria 102 | * @param sort 103 | * @param offset 104 | * @param rows 105 | * @param keyspace 106 | * @param type 107 | * @return 108 | * @since 1.1 109 | */ 110 | @SuppressWarnings("unchecked") 111 | public Collection execute(@Nullable CRITERIA criteria, @Nullable SORT sort, long offset, int rows, 112 | String keyspace, Class type) { 113 | return (Collection) execute(criteria, sort, offset, rows, keyspace); 114 | } 115 | 116 | /** 117 | * @param criteria 118 | * @param keyspace 119 | * @return 120 | */ 121 | public abstract long count(@Nullable CRITERIA criteria, String keyspace); 122 | 123 | /** 124 | * Get the {@link KeyValueAdapter} used. 125 | * 126 | * @return 127 | */ 128 | protected @Nullable ADAPTER getAdapter() { 129 | return this.adapter; 130 | } 131 | 132 | /** 133 | * Get the required {@link KeyValueAdapter} used or throw {@link IllegalStateException} if the adapter is not set. 134 | * 135 | * @return the required {@link KeyValueAdapter}. 136 | * @throws IllegalStateException if the adapter is not set. 137 | */ 138 | protected ADAPTER getRequiredAdapter() { 139 | 140 | ADAPTER adapter = getAdapter(); 141 | 142 | if (adapter != null) { 143 | return adapter; 144 | } 145 | 146 | throw new IllegalStateException("Required KeyValueAdapter is not set"); 147 | } 148 | 149 | /** 150 | * @param adapter 151 | */ 152 | @SuppressWarnings("unchecked") 153 | public void registerAdapter(KeyValueAdapter adapter) { 154 | 155 | if (this.adapter == null) { 156 | this.adapter = (ADAPTER) adapter; 157 | } else { 158 | throw new IllegalArgumentException("Cannot register more than one adapter for this QueryEngine"); 159 | } 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /src/main/java/org/springframework/data/keyvalue/core/QueryEngineFactory.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024-2025 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.data.keyvalue.core; 17 | 18 | /** 19 | * Interface for {@code QueryEngineFactory} implementations that provide a {@link QueryEngine} object as part of the 20 | * configuration. 21 | *

22 | * The factory is used during configuration to supply the query engine to be used. When configured, a 23 | * {@code QueryEngineFactory} can be instantiated by accepting a {@link SortAccessor} in its constructor. Otherwise, 24 | * implementations are expected to declare a no-args constructor. 25 | * 26 | * @author Mark Paluch 27 | * @since 3.3.1 28 | */ 29 | public interface QueryEngineFactory { 30 | 31 | /** 32 | * Factory method for creating a {@link QueryEngine}. 33 | * 34 | * @return the query engine. 35 | */ 36 | QueryEngine create(); 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/org/springframework/data/keyvalue/core/SimplePropertyPathAccessor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024-2025 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.data.keyvalue.core; 17 | 18 | import org.jspecify.annotations.Nullable; 19 | import org.springframework.beans.BeanWrapper; 20 | import org.springframework.data.mapping.PropertyPath; 21 | import org.springframework.data.util.DirectFieldAccessFallbackBeanWrapper; 22 | 23 | /** 24 | * @author Christoph Strobl 25 | * @since 3.1.10 26 | */ 27 | public class SimplePropertyPathAccessor { 28 | 29 | private final Object root; 30 | 31 | public SimplePropertyPathAccessor(Object source) { 32 | this.root = source; 33 | } 34 | 35 | public @Nullable Object getValue(PropertyPath path) { 36 | 37 | Object currentValue = root; 38 | for (PropertyPath current : path) { 39 | currentValue = wrap(currentValue).getPropertyValue(current.getSegment()); 40 | if (currentValue == null) { 41 | break; 42 | } 43 | } 44 | return currentValue; 45 | } 46 | 47 | BeanWrapper wrap(Object o) { 48 | return new DirectFieldAccessFallbackBeanWrapper(o); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/org/springframework/data/keyvalue/core/SortAccessor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014-2025 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.data.keyvalue.core; 17 | 18 | import org.jspecify.annotations.Nullable; 19 | import org.springframework.data.domain.Sort; 20 | import org.springframework.data.keyvalue.core.query.KeyValueQuery; 21 | 22 | /** 23 | * Resolves the {@link Sort} object from given {@link KeyValueQuery} and potentially converts it into a store specific 24 | * representation that can be used by the {@link QueryEngine} implementation. 25 | * 26 | * @author Christoph Strobl 27 | * @author Mark Paluch 28 | * @param 29 | */ 30 | public interface SortAccessor { 31 | 32 | /** 33 | * Reads {@link KeyValueQuery#getSort()} of given {@link KeyValueQuery} and applies required transformation to match 34 | * the desired type. 35 | * 36 | * @param query must not be {@literal null}. 37 | * @return {@literal null} in case {@link Sort} has not been defined on {@link KeyValueQuery}. 38 | */ 39 | @Nullable 40 | T resolve(KeyValueQuery query); 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/org/springframework/data/keyvalue/core/SpelCriteria.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016-2025 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.data.keyvalue.core; 17 | 18 | import org.springframework.expression.EvaluationContext; 19 | import org.springframework.expression.spel.standard.SpelExpression; 20 | import org.springframework.expression.spel.support.SimpleEvaluationContext; 21 | import org.springframework.util.Assert; 22 | 23 | /** 24 | * {@link SpelCriteria} allows to pass on a {@link SpelExpression} and {@link EvaluationContext} to the actual query 25 | * processor. This decouples the {@link SpelExpression} from the context it is used in. 26 | * 27 | * @author Christoph Strobl 28 | * @author Oliver Gierke 29 | */ 30 | public class SpelCriteria { 31 | 32 | private final SpelExpression expression; 33 | private final EvaluationContext context; 34 | 35 | /** 36 | * Creates a new {@link SpelCriteria} for the given {@link SpelExpression}. 37 | * 38 | * @param expression must not be {@literal null}. 39 | */ 40 | public SpelCriteria(SpelExpression expression) { 41 | this(expression, SimpleEvaluationContext.forReadOnlyDataBinding().withInstanceMethods().build()); 42 | } 43 | 44 | /** 45 | * Creates new {@link SpelCriteria}. 46 | * 47 | * @param expression must not be {@literal null}. 48 | * @param context must not be {@literal null}. 49 | */ 50 | public SpelCriteria(SpelExpression expression, EvaluationContext context) { 51 | 52 | Assert.notNull(expression, "SpEL expression must not be null"); 53 | Assert.notNull(context, "EvaluationContext must not be null"); 54 | 55 | this.expression = expression; 56 | this.context = context; 57 | } 58 | 59 | /** 60 | * @return will never be {@literal null}. 61 | */ 62 | public EvaluationContext getContext() { 63 | return context; 64 | } 65 | 66 | /** 67 | * @return will never be {@literal null}. 68 | */ 69 | public SpelExpression getExpression() { 70 | return expression; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/main/java/org/springframework/data/keyvalue/core/SpelCriteriaAccessor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014-2025 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.data.keyvalue.core; 17 | 18 | import org.jspecify.annotations.Nullable; 19 | import org.springframework.data.keyvalue.core.query.KeyValueQuery; 20 | import org.springframework.expression.spel.standard.SpelExpression; 21 | import org.springframework.expression.spel.standard.SpelExpressionParser; 22 | import org.springframework.util.Assert; 23 | 24 | /** 25 | * {@link CriteriaAccessor} implementation capable of {@link SpelExpression}s. 26 | * 27 | * @author Christoph Strobl 28 | * @author Oliver Gierke 29 | */ 30 | class SpelCriteriaAccessor implements CriteriaAccessor { 31 | 32 | private final SpelExpressionParser parser; 33 | 34 | /** 35 | * Creates a new {@link SpelCriteriaAccessor} using the given {@link SpelExpressionParser}. 36 | * 37 | * @param parser must not be {@literal null}. 38 | */ 39 | public SpelCriteriaAccessor(SpelExpressionParser parser) { 40 | 41 | Assert.notNull(parser, "SpelExpressionParser must not be null"); 42 | 43 | this.parser = parser; 44 | } 45 | 46 | @Override 47 | public @Nullable SpelCriteria resolve(KeyValueQuery query) { 48 | 49 | if (query.getCriteria() == null) { 50 | return null; 51 | } 52 | 53 | if (query.getCriteria() instanceof SpelExpression) { 54 | return new SpelCriteria((SpelExpression) query.getCriteria()); 55 | } 56 | 57 | if (query.getCriteria() instanceof String) { 58 | return new SpelCriteria(parser.parseRaw((String) query.getCriteria())); 59 | } 60 | 61 | if (query.getCriteria() instanceof SpelCriteria) { 62 | return (SpelCriteria) query.getCriteria(); 63 | } 64 | 65 | throw new IllegalArgumentException("Cannot create SpelCriteria for " + query.getCriteria()); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/main/java/org/springframework/data/keyvalue/core/SpelPropertyComparator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014-2025 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.data.keyvalue.core; 17 | 18 | import java.util.Comparator; 19 | 20 | import org.jspecify.annotations.Nullable; 21 | import org.springframework.expression.spel.standard.SpelExpression; 22 | import org.springframework.expression.spel.standard.SpelExpressionParser; 23 | import org.springframework.expression.spel.support.SimpleEvaluationContext; 24 | import org.springframework.lang.Contract; 25 | import org.springframework.util.Assert; 26 | 27 | /** 28 | * {@link Comparator} implementation using {@link SpelExpression}. 29 | * 30 | * @author Christoph Strobl 31 | * @author Oliver Gierke 32 | * @author Mark Paluch 33 | * @param 34 | */ 35 | public class SpelPropertyComparator implements Comparator { 36 | 37 | private static final Comparator NULLS_FIRST = Comparator.nullsFirst(Comparator.naturalOrder()); 38 | private static final Comparator NULLS_LAST = Comparator.nullsLast(Comparator.naturalOrder()); 39 | 40 | private final String path; 41 | private final SpelExpressionParser parser; 42 | 43 | private boolean asc = true; 44 | private boolean nullsFirst = true; 45 | private @Nullable SpelExpression expression; 46 | 47 | /** 48 | * Create new {@link SpelPropertyComparator} for the given property path an {@link SpelExpressionParser}. 49 | * 50 | * @param path must not be {@literal null} or empty. 51 | * @param parser must not be {@literal null}. 52 | */ 53 | public SpelPropertyComparator(String path, SpelExpressionParser parser) { 54 | 55 | Assert.hasText(path, "Path must not be null or empty"); 56 | Assert.notNull(parser, "SpelExpressionParser must not be null"); 57 | 58 | this.path = path; 59 | this.parser = parser; 60 | } 61 | 62 | /** 63 | * Sort {@literal ascending}. 64 | * 65 | * @return 66 | */ 67 | @Contract("-> this") 68 | public SpelPropertyComparator<@Nullable T> asc() { 69 | this.asc = true; 70 | return this; 71 | } 72 | 73 | /** 74 | * Sort {@literal descending}. 75 | * 76 | * @return 77 | */ 78 | @Contract("-> this") 79 | public SpelPropertyComparator<@Nullable T> desc() { 80 | this.asc = false; 81 | return this; 82 | } 83 | 84 | /** 85 | * Sort {@literal null} values first. 86 | * 87 | * @return 88 | */ 89 | @Contract("-> this") 90 | public SpelPropertyComparator<@Nullable T> nullsFirst() { 91 | this.nullsFirst = true; 92 | return this; 93 | } 94 | 95 | /** 96 | * Sort {@literal null} values last. 97 | * 98 | * @return 99 | */ 100 | @Contract("-> this") 101 | public SpelPropertyComparator<@Nullable T> nullsLast() { 102 | this.nullsFirst = false; 103 | return this; 104 | } 105 | 106 | /** 107 | * Parse values to {@link SpelExpression} 108 | * 109 | * @return 110 | */ 111 | protected SpelExpression getExpression() { 112 | 113 | if (this.expression == null) { 114 | this.expression = parser.parseRaw(buildExpressionForPath()); 115 | } 116 | 117 | return this.expression; 118 | } 119 | 120 | /** 121 | * Create the expression raw value. 122 | * 123 | * @return 124 | */ 125 | protected String buildExpressionForPath() { 126 | 127 | return String.format("#comparator.compare(#arg1?.%s,#arg2?.%s)", path.replace(".", "?."), 128 | path.replace(".", "?.")); 129 | } 130 | 131 | @Override 132 | public int compare(@Nullable T arg1, @Nullable T arg2) { 133 | 134 | SpelExpression expressionToUse = getExpression(); 135 | 136 | SimpleEvaluationContext ctx = SimpleEvaluationContext.forReadOnlyDataBinding().withInstanceMethods().build(); 137 | ctx.setVariable("comparator", nullsFirst ? NULLS_FIRST : NULLS_LAST); 138 | ctx.setVariable("arg1", arg1); 139 | ctx.setVariable("arg2", arg2); 140 | 141 | expressionToUse.setEvaluationContext(ctx); 142 | 143 | Integer value = expressionToUse.getValue(Integer.class); 144 | return (value != null ? value : 0) * (asc ? 1 : -1); 145 | } 146 | 147 | /** 148 | * Get dot path to property. 149 | * 150 | * @return 151 | */ 152 | public String getPath() { 153 | return path; 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /src/main/java/org/springframework/data/keyvalue/core/SpelQueryEngine.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014-2025 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.data.keyvalue.core; 17 | 18 | import java.util.Collection; 19 | import java.util.Comparator; 20 | import java.util.List; 21 | import java.util.stream.Collectors; 22 | import java.util.stream.Stream; 23 | 24 | import org.jspecify.annotations.Nullable; 25 | import org.springframework.data.keyvalue.core.query.KeyValueQuery; 26 | import org.springframework.expression.spel.SpelEvaluationException; 27 | import org.springframework.expression.spel.standard.SpelExpression; 28 | import org.springframework.expression.spel.standard.SpelExpressionParser; 29 | 30 | /** 31 | * {@link QueryEngine} implementation specific for executing {@link SpelExpression} based {@link KeyValueQuery} against 32 | * {@link KeyValueAdapter}. 33 | * 34 | * @author Christoph Strobl 35 | * @author Oliver Gierke 36 | * @author Mark Paluch 37 | */ 38 | public class SpelQueryEngine extends QueryEngine> { 39 | 40 | private static final SpelExpressionParser PARSER = new SpelExpressionParser(); 41 | 42 | /** 43 | * Creates a new {@link SpelQueryEngine}. 44 | */ 45 | public SpelQueryEngine() { 46 | this(new SpelSortAccessor(PARSER)); 47 | } 48 | 49 | /** 50 | * Creates a new query engine using provided {@link SortAccessor accessor} for sorting results. 51 | * 52 | * @since 3.1.10 53 | */ 54 | public SpelQueryEngine(SortAccessor> sortAccessor) { 55 | super(new SpelCriteriaAccessor(PARSER), sortAccessor); 56 | } 57 | 58 | @Override 59 | public Collection execute(@Nullable SpelCriteria criteria, @Nullable Comparator sort, long offset, int rows, 60 | String keyspace) { 61 | return sortAndFilterMatchingRange(getRequiredAdapter().getAllOf(keyspace), criteria, sort, offset, rows); 62 | } 63 | 64 | @Override 65 | public long count(@Nullable SpelCriteria criteria, String keyspace) { 66 | return filterMatchingRange(IterableConverter.toList(getRequiredAdapter().getAllOf(keyspace)), criteria, -1, -1) 67 | .size(); 68 | } 69 | 70 | @SuppressWarnings({ "unchecked", "rawtypes" }) 71 | private List sortAndFilterMatchingRange(Iterable source, @Nullable SpelCriteria criteria, 72 | @Nullable Comparator sort, long offset, int rows) { 73 | 74 | List tmp = IterableConverter.toList(source); 75 | if (sort != null) { 76 | tmp.sort(sort); 77 | } 78 | 79 | return filterMatchingRange(tmp, criteria, offset, rows); 80 | } 81 | 82 | private static List filterMatchingRange(List source, @Nullable SpelCriteria criteria, long offset, 83 | int rows) { 84 | 85 | Stream stream = source.stream(); 86 | 87 | if (criteria != null) { 88 | stream = stream.filter(it -> evaluateExpression(criteria, it)); 89 | } 90 | if (offset > 0) { 91 | stream = stream.skip(offset); 92 | } 93 | if (rows > 0) { 94 | stream = stream.limit(rows); 95 | } 96 | 97 | return stream.collect(Collectors.toList()); 98 | } 99 | 100 | @SuppressWarnings("NullAway") 101 | private static boolean evaluateExpression(SpelCriteria criteria, Object candidate) { 102 | 103 | try { 104 | return criteria.getExpression().getValue(criteria.getContext(), candidate, Boolean.class); 105 | } catch (SpelEvaluationException e) { 106 | criteria.getContext().setVariable("it", candidate); 107 | return criteria.getExpression().getValue(criteria.getContext()) == null ? false 108 | : criteria.getExpression().getValue(criteria.getContext(), Boolean.class); 109 | } 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /src/main/java/org/springframework/data/keyvalue/core/SpelSortAccessor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014-2025 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.data.keyvalue.core; 17 | 18 | import java.util.Comparator; 19 | import java.util.Optional; 20 | 21 | import org.jspecify.annotations.Nullable; 22 | import org.springframework.data.domain.Sort.Direction; 23 | import org.springframework.data.domain.Sort.NullHandling; 24 | import org.springframework.data.domain.Sort.Order; 25 | import org.springframework.data.keyvalue.core.query.KeyValueQuery; 26 | import org.springframework.expression.spel.standard.SpelExpressionParser; 27 | import org.springframework.util.Assert; 28 | 29 | /** 30 | * {@link SortAccessor} implementation capable of creating {@link SpelPropertyComparator}. 31 | * 32 | * @author Christoph Strobl 33 | * @author Oliver Gierke 34 | * @author Mark Paluch 35 | */ 36 | public class SpelSortAccessor implements SortAccessor> { 37 | 38 | private final SpelExpressionParser parser; 39 | 40 | /** 41 | * Creates a new {@link SpelSortAccessor} given {@link SpelExpressionParser}. 42 | * 43 | * @param parser must not be {@literal null}. 44 | */ 45 | public SpelSortAccessor(SpelExpressionParser parser) { 46 | 47 | Assert.notNull(parser, "SpelExpressionParser must not be null"); 48 | this.parser = parser; 49 | } 50 | 51 | @Override 52 | public @Nullable Comparator resolve(KeyValueQuery query) { 53 | 54 | if (query.getSort().isUnsorted()) { 55 | return null; 56 | } 57 | 58 | Optional> comparator = Optional.empty(); 59 | for (Order order : query.getSort()) { 60 | 61 | SpelPropertyComparator spelSort = new SpelPropertyComparator<>(order.getProperty(), parser); 62 | 63 | if (Direction.DESC.equals(order.getDirection())) { 64 | 65 | spelSort.desc(); 66 | 67 | if (!NullHandling.NATIVE.equals(order.getNullHandling())) { 68 | spelSort = NullHandling.NULLS_FIRST.equals(order.getNullHandling()) ? spelSort.nullsFirst() 69 | : spelSort.nullsLast(); 70 | } 71 | } 72 | 73 | if (!comparator.isPresent()) { 74 | comparator = Optional.of(spelSort); 75 | } else { 76 | 77 | SpelPropertyComparator spelSortToUse = spelSort; 78 | comparator = comparator.map(it -> it.thenComparing(spelSortToUse)); 79 | } 80 | } 81 | 82 | return comparator.orElseThrow( 83 | () -> new IllegalStateException("No sort definitions have been added to this CompoundComparator to compare")); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/main/java/org/springframework/data/keyvalue/core/UncategorizedKeyValueException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014-2025 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.data.keyvalue.core; 17 | 18 | import org.springframework.dao.UncategorizedDataAccessException; 19 | 20 | /** 21 | * Normal superclass when we can't distinguish anything more specific than "something went wrong with the underlying 22 | * resource". 23 | * 24 | * @author Christoph Strobl 25 | * @author Mark Paluch 26 | */ 27 | public class UncategorizedKeyValueException extends UncategorizedDataAccessException { 28 | 29 | private static final long serialVersionUID = -8087116071859122297L; 30 | 31 | /** 32 | * Creates a new {@link UncategorizedKeyValueException}. 33 | * 34 | * @param msg the detail message. 35 | * @param cause the root cause (usually from using a underlying data access API). 36 | */ 37 | public UncategorizedKeyValueException(String msg, Throwable cause) { 38 | super(msg, cause); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/org/springframework/data/keyvalue/core/event/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Support classes for key-value events, like standard persistence lifecycle events. 3 | */ 4 | @org.jspecify.annotations.NullMarked 5 | package org.springframework.data.keyvalue.core.event; 6 | -------------------------------------------------------------------------------- /src/main/java/org/springframework/data/keyvalue/core/mapping/AnnotationBasedKeySpaceResolver.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015-2025 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.data.keyvalue.core.mapping; 17 | 18 | import org.jspecify.annotations.Nullable; 19 | import org.springframework.core.annotation.MergedAnnotation; 20 | import org.springframework.core.annotation.MergedAnnotations; 21 | import org.springframework.data.annotation.Persistent; 22 | import org.springframework.data.keyvalue.annotation.KeySpace; 23 | import org.springframework.util.Assert; 24 | import org.springframework.util.ClassUtils; 25 | 26 | /** 27 | * {@link AnnotationBasedKeySpaceResolver} looks up {@link Persistent} and checks for presence of either meta or direct 28 | * usage of {@link KeySpace}. If non found it will default the keyspace to {@link Class#getName()}. 29 | * 30 | * @author Christoph Strobl 31 | * @author Oliver Gierke 32 | * @author Mark Paluch 33 | */ 34 | public enum AnnotationBasedKeySpaceResolver implements KeySpaceResolver { 35 | 36 | INSTANCE; 37 | 38 | @Override 39 | public @Nullable String resolveKeySpace(Class type) { 40 | 41 | Assert.notNull(type, "Type for keyspace for null"); 42 | 43 | Class userClass = ClassUtils.getUserClass(type); 44 | Object keySpace = getKeySpace(userClass); 45 | 46 | return keySpace != null ? keySpace.toString() : null; 47 | } 48 | 49 | 50 | private static @Nullable Object getKeySpace(Class type) { 51 | 52 | MergedAnnotation annotation = MergedAnnotations 53 | .from(type, MergedAnnotations.SearchStrategy.TYPE_HIERARCHY).get(KeySpace.class); 54 | 55 | if (annotation.isPresent()) { 56 | return annotation.getValue("value").orElse(null); 57 | } 58 | 59 | return null; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/main/java/org/springframework/data/keyvalue/core/mapping/BasicKeyValuePersistentEntity.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015-2025 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.data.keyvalue.core.mapping; 17 | 18 | import org.jspecify.annotations.Nullable; 19 | import org.springframework.data.expression.ValueExpression; 20 | import org.springframework.data.expression.ValueExpressionParser; 21 | import org.springframework.data.mapping.model.BasicPersistentEntity; 22 | import org.springframework.data.util.TypeInformation; 23 | import org.springframework.expression.Expression; 24 | import org.springframework.expression.common.LiteralExpression; 25 | import org.springframework.expression.spel.standard.SpelExpressionParser; 26 | import org.springframework.util.ObjectUtils; 27 | import org.springframework.util.StringUtils; 28 | 29 | /** 30 | * {@link KeyValuePersistentEntity} implementation that adds specific meta-data such as the {@literal keySpace}. 31 | * 32 | * @author Christoph Strobl 33 | * @author Oliver Gierke 34 | * @author Mark Paluch 35 | * @param 36 | */ 37 | public class BasicKeyValuePersistentEntity> 38 | extends BasicPersistentEntity implements KeyValuePersistentEntity { 39 | 40 | private static final ValueExpressionParser PARSER = ValueExpressionParser.create(SpelExpressionParser::new); 41 | 42 | private final @Nullable ValueExpression keyspaceExpression; 43 | private final String keyspace; 44 | 45 | /** 46 | * @param information must not be {@literal null}. 47 | * @since 3.1 48 | */ 49 | public BasicKeyValuePersistentEntity(TypeInformation information) { 50 | this(information, (String) null); 51 | } 52 | 53 | /** 54 | * @param information must not be {@literal null}. 55 | * @param keySpaceResolver can be {@literal null}. 56 | */ 57 | public BasicKeyValuePersistentEntity(TypeInformation information, @Nullable KeySpaceResolver keySpaceResolver) { 58 | this(information, keySpaceResolver != null ? keySpaceResolver.resolveKeySpace(information.getType()) : null); 59 | } 60 | 61 | private BasicKeyValuePersistentEntity(TypeInformation information, @Nullable String keyspace) { 62 | 63 | super(information); 64 | 65 | if (StringUtils.hasText(keyspace)) { 66 | 67 | this.keyspace = keyspace; 68 | this.keyspaceExpression = null; 69 | } else { 70 | 71 | Class type = information.getType(); 72 | String detectedKeyspace = AnnotationBasedKeySpaceResolver.INSTANCE.resolveKeySpace(type); 73 | 74 | if (StringUtils.hasText(detectedKeyspace)) { 75 | 76 | this.keyspace = detectedKeyspace; 77 | this.keyspaceExpression = detectExpression(detectedKeyspace); 78 | } else { 79 | 80 | this.keyspace = ClassNameKeySpaceResolver.INSTANCE.resolveKeySpace(type); 81 | this.keyspaceExpression = null; 82 | } 83 | } 84 | } 85 | 86 | /** 87 | * Returns a SpEL {@link Expression} if the given {@link String} is actually an expression that does not evaluate to a 88 | * {@link LiteralExpression} (indicating that no subsequent evaluation is necessary). 89 | * 90 | * @param potentialExpression must not be {@literal null} 91 | * @return the parsed {@link Expression} or {@literal null}. 92 | */ 93 | 94 | private static @Nullable ValueExpression detectExpression(String potentialExpression) { 95 | 96 | ValueExpression expression = PARSER.parse(potentialExpression); 97 | return expression.isLiteral() ? null : expression; 98 | } 99 | 100 | @Override 101 | public String getKeySpace() { 102 | return keyspaceExpression == null // 103 | ? keyspace // 104 | : ObjectUtils.nullSafeToString(keyspaceExpression.evaluate(getValueEvaluationContext(null))); 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/main/java/org/springframework/data/keyvalue/core/mapping/ClassNameKeySpaceResolver.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015-2025 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.data.keyvalue.core.mapping; 17 | 18 | import org.springframework.util.Assert; 19 | import org.springframework.util.ClassUtils; 20 | 21 | /** 22 | * Most trivial implementation of {@link KeySpaceResolver} returning the {@link Class#getName()}. 23 | * 24 | * @author Christoph Strobl 25 | * @author Oliver Gierke 26 | */ 27 | public enum ClassNameKeySpaceResolver implements KeySpaceResolver { 28 | 29 | INSTANCE; 30 | 31 | @Override 32 | public String resolveKeySpace(Class type) { 33 | 34 | Assert.notNull(type, "Type must not be null"); 35 | return ClassUtils.getUserClass(type).getName(); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/org/springframework/data/keyvalue/core/mapping/KeySpaceResolver.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015-2025 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.data.keyvalue.core.mapping; 17 | 18 | import org.jspecify.annotations.Nullable; 19 | 20 | /** 21 | * {@link KeySpaceResolver} determines the {@literal keyspace} a given type is assigned to. A keyspace in this context 22 | * is a specific region/collection/grouping of elements sharing a common keyrange. 23 | * 24 | * @author Christoph Strobl 25 | * @author Mark Paluch 26 | */ 27 | public interface KeySpaceResolver { 28 | 29 | /** 30 | * Determine the {@literal keySpace} to use for a given type. 31 | * 32 | * @param type must not be {@literal null}. 33 | * @return 34 | */ 35 | @Nullable 36 | String resolveKeySpace(Class type); 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/org/springframework/data/keyvalue/core/mapping/KeyValuePersistentEntity.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015-2025 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.data.keyvalue.core.mapping; 17 | 18 | import org.jspecify.annotations.Nullable; 19 | import org.springframework.data.mapping.model.MutablePersistentEntity; 20 | 21 | /** 22 | * @author Christoph Strobl 23 | * @param 24 | */ 25 | public interface KeyValuePersistentEntity> 26 | extends MutablePersistentEntity { 27 | 28 | /** 29 | * Get the {@literal keySpace} a given entity assigns to. 30 | * 31 | * @return can be {@literal null}. 32 | */ 33 | @Nullable 34 | String getKeySpace(); 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/org/springframework/data/keyvalue/core/mapping/KeyValuePersistentProperty.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014-2025 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.data.keyvalue.core.mapping; 17 | 18 | import org.springframework.data.mapping.Association; 19 | import org.springframework.data.mapping.PersistentEntity; 20 | import org.springframework.data.mapping.PersistentProperty; 21 | import org.springframework.data.mapping.model.AnnotationBasedPersistentProperty; 22 | import org.springframework.data.mapping.model.Property; 23 | import org.springframework.data.mapping.model.SimpleTypeHolder; 24 | 25 | /** 26 | * Most trivial implementation of {@link PersistentProperty}. 27 | * 28 | * @author Christoph Strobl 29 | * @author Mark Paluch 30 | */ 31 | public class KeyValuePersistentProperty

> 32 | extends AnnotationBasedPersistentProperty

{ 33 | 34 | public KeyValuePersistentProperty(Property property, PersistentEntity owner, 35 | SimpleTypeHolder simpleTypeHolder) { 36 | super(property, owner, simpleTypeHolder); 37 | } 38 | 39 | @Override 40 | @SuppressWarnings("unchecked") 41 | protected Association

createAssociation() { 42 | return new Association<>((P) this, null); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/org/springframework/data/keyvalue/core/mapping/PrefixKeyspaceResolver.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022-2025 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.data.keyvalue.core.mapping; 17 | 18 | import org.springframework.util.Assert; 19 | 20 | /** 21 | * {@link KeySpaceResolver} prefixing the {@literal keyspace} with a static prefix after determining the keyspace from a 22 | * delegate {@link KeySpaceResolver}. 23 | * 24 | * @author Mark Paluch 25 | * @since 3.1 26 | */ 27 | public class PrefixKeyspaceResolver implements KeySpaceResolver { 28 | 29 | private final String prefix; 30 | private final KeySpaceResolver delegate; 31 | 32 | public PrefixKeyspaceResolver(String prefix, KeySpaceResolver delegate) { 33 | 34 | Assert.notNull(prefix, "Prefix must not be null"); 35 | Assert.notNull(delegate, "Delegate KeySpaceResolver must not be null"); 36 | 37 | this.prefix = prefix; 38 | this.delegate = delegate; 39 | } 40 | 41 | @Override 42 | public String resolveKeySpace(Class type) { 43 | return prefix + delegate.resolveKeySpace(type); 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/org/springframework/data/keyvalue/core/mapping/context/KeyValueMappingContext.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014-2025 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.data.keyvalue.core.mapping.context; 17 | 18 | import java.util.Collections; 19 | 20 | import org.jspecify.annotations.Nullable; 21 | import org.springframework.data.keyvalue.core.mapping.BasicKeyValuePersistentEntity; 22 | import org.springframework.data.keyvalue.core.mapping.KeySpaceResolver; 23 | import org.springframework.data.keyvalue.core.mapping.KeyValuePersistentEntity; 24 | import org.springframework.data.keyvalue.core.mapping.KeyValuePersistentProperty; 25 | import org.springframework.data.mapping.context.AbstractMappingContext; 26 | import org.springframework.data.mapping.context.MappingContext; 27 | import org.springframework.data.mapping.model.Property; 28 | import org.springframework.data.mapping.model.SimpleTypeHolder; 29 | import org.springframework.data.util.TypeInformation; 30 | 31 | /** 32 | * Default implementation of a {@link MappingContext} using {@link KeyValuePersistentEntity} and 33 | * {@link KeyValuePersistentProperty} as primary abstractions. 34 | * 35 | * @author Christoph Strobl 36 | * @author Oliver Gierke 37 | * @author Mark Paluch 38 | */ 39 | public class KeyValueMappingContext, P extends KeyValuePersistentProperty

> 40 | extends AbstractMappingContext { 41 | 42 | private @Nullable KeySpaceResolver keySpaceResolver; 43 | 44 | public KeyValueMappingContext() { 45 | setSimpleTypeHolder(new KeyValueSimpleTypeHolder()); 46 | } 47 | 48 | /** 49 | * Configures the {@link KeySpaceResolver} to be used if not explicit key space is annotated to the domain type. 50 | * 51 | * @param fallbackKeySpaceResolver can be {@literal null}. 52 | * @deprecated since 3.1, use {@link KeySpaceResolver} instead. 53 | */ 54 | @Deprecated(since = "3.1") 55 | public void setFallbackKeySpaceResolver(KeySpaceResolver fallbackKeySpaceResolver) { 56 | setKeySpaceResolver(fallbackKeySpaceResolver); 57 | } 58 | 59 | /** 60 | * Configures the {@link KeySpaceResolver} to be used. Configuring a {@link KeySpaceResolver} disables SpEL evaluation 61 | * abilities. 62 | * 63 | * @param keySpaceResolver can be {@literal null}. 64 | * @since 3.1 65 | */ 66 | public void setKeySpaceResolver(KeySpaceResolver keySpaceResolver) { 67 | this.keySpaceResolver = keySpaceResolver; 68 | } 69 | 70 | /** 71 | * @return the current {@link KeySpaceResolver}. Can be {@literal null}. 72 | * @since 3.1 73 | */ 74 | public @Nullable KeySpaceResolver getKeySpaceResolver() { 75 | return keySpaceResolver; 76 | } 77 | 78 | @Override 79 | @SuppressWarnings("unchecked") 80 | protected E createPersistentEntity(TypeInformation typeInformation) { 81 | return (E) new BasicKeyValuePersistentEntity(typeInformation, getKeySpaceResolver()); 82 | } 83 | 84 | @Override 85 | @SuppressWarnings("unchecked") 86 | protected P createPersistentProperty(Property property, E owner, SimpleTypeHolder simpleTypeHolder) { 87 | return (P) new KeyValuePersistentProperty<>(property, owner, simpleTypeHolder); 88 | } 89 | 90 | /** 91 | * @since 2.5.1 92 | */ 93 | private static class KeyValueSimpleTypeHolder extends SimpleTypeHolder { 94 | 95 | public KeyValueSimpleTypeHolder() { 96 | super(Collections.emptySet(), true); 97 | } 98 | 99 | @Override 100 | public boolean isSimpleType(Class type) { 101 | 102 | if (type.getName().startsWith("java.math.") || type.getName().startsWith("java.util.")) { 103 | return true; 104 | } 105 | 106 | return super.isSimpleType(type); 107 | } 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /src/main/java/org/springframework/data/keyvalue/core/mapping/context/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Infrastructure for the Key-Value mapping context. 3 | */ 4 | @org.jspecify.annotations.NullMarked 5 | package org.springframework.data.keyvalue.core.mapping.context; 6 | -------------------------------------------------------------------------------- /src/main/java/org/springframework/data/keyvalue/core/mapping/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Infrastructure for the Key-Value mapping subsystem and keyspace resolution. 3 | */ 4 | @org.jspecify.annotations.NullMarked 5 | package org.springframework.data.keyvalue.core.mapping; 6 | -------------------------------------------------------------------------------- /src/main/java/org/springframework/data/keyvalue/core/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Core key/value implementation. 3 | */ 4 | @org.jspecify.annotations.NullMarked 5 | package org.springframework.data.keyvalue.core; 6 | -------------------------------------------------------------------------------- /src/main/java/org/springframework/data/keyvalue/core/query/KeyValueQuery.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014-2025 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.data.keyvalue.core.query; 17 | 18 | import org.jspecify.annotations.Nullable; 19 | import org.springframework.data.domain.Sort; 20 | import org.springframework.lang.Contract; 21 | import org.springframework.util.Assert; 22 | 23 | /** 24 | * @author Christoph Strobl 25 | * @author Mark Paluch 26 | * @author Marcel Overdijk 27 | * @param Criteria type 28 | */ 29 | public class KeyValueQuery { 30 | 31 | private Sort sort = Sort.unsorted(); 32 | private long offset = -1; 33 | private int rows = -1; 34 | private final @Nullable T criteria; 35 | 36 | /** 37 | * Creates new instance of {@link KeyValueQuery}. 38 | */ 39 | public KeyValueQuery() { 40 | this((T) null); 41 | } 42 | 43 | /** 44 | * Creates new instance of {@link KeyValueQuery} with given criteria. 45 | * 46 | * @param criteria can be {@literal null}. 47 | */ 48 | public KeyValueQuery(@Nullable T criteria) { 49 | this.criteria = criteria; 50 | } 51 | 52 | /** 53 | * Creates new instance of {@link KeyValueQuery} with given criteria and {@link Sort}. 54 | * 55 | * @param criteria can be {@literal null}. 56 | * @param sort must not be {@literal null}. 57 | * @since 2.4 58 | */ 59 | public KeyValueQuery(@Nullable T criteria, Sort sort) { 60 | this.criteria = criteria; 61 | setSort(sort); 62 | } 63 | 64 | /** 65 | * Creates new instance of {@link KeyValueQuery} with given {@link Sort}. 66 | * 67 | * @param sort must not be {@literal null}. 68 | */ 69 | public KeyValueQuery(Sort sort) { 70 | this(); 71 | setSort(sort); 72 | } 73 | 74 | /** 75 | * Get the criteria object. 76 | * 77 | * @return 78 | * @since 2.0 79 | */ 80 | public @Nullable T getCriteria() { 81 | return criteria; 82 | } 83 | 84 | /** 85 | * Get {@link Sort}. 86 | * 87 | * @return 88 | */ 89 | public Sort getSort() { 90 | return sort; 91 | } 92 | 93 | /** 94 | * Number of elements to skip. 95 | * 96 | * @return negative value if not set. 97 | */ 98 | public long getOffset() { 99 | return this.offset; 100 | } 101 | 102 | /** 103 | * Number of elements to read. 104 | * 105 | * @return negative value if not set. 106 | */ 107 | public int getRows() { 108 | return this.rows; 109 | } 110 | 111 | /** 112 | * Set the number of elements to skip. 113 | * 114 | * @param offset use negative value for none. 115 | */ 116 | public void setOffset(long offset) { 117 | this.offset = offset; 118 | } 119 | 120 | /** 121 | * Set the number of elements to read. 122 | * 123 | * @param rows use negative value for all. 124 | */ 125 | public void setRows(int rows) { 126 | this.rows = rows; 127 | } 128 | 129 | /** 130 | * Set {@link Sort} to be applied. 131 | * 132 | * @param sort 133 | */ 134 | public void setSort(Sort sort) { 135 | 136 | Assert.notNull(sort, "Sort must not be null"); 137 | 138 | this.sort = sort; 139 | } 140 | 141 | /** 142 | * Add given {@link Sort}. 143 | * 144 | * @param sort must not be {@literal null}. 145 | * @return 146 | */ 147 | @Contract("_ -> this") 148 | public KeyValueQuery orderBy(Sort sort) { 149 | 150 | Assert.notNull(sort, "Sort must not be null"); 151 | 152 | if (this.sort.isSorted()) { 153 | this.sort = this.sort.and(sort); 154 | } else { 155 | this.sort = sort; 156 | } 157 | 158 | return this; 159 | } 160 | 161 | /** 162 | * @see KeyValueQuery#setOffset(long) 163 | * @param offset 164 | * @return 165 | */ 166 | @Contract("_ -> this") 167 | public KeyValueQuery skip(long offset) { 168 | 169 | setOffset(offset); 170 | 171 | return this; 172 | } 173 | 174 | /** 175 | * @see KeyValueQuery#setRows(int) 176 | * @param rows 177 | * @return 178 | */ 179 | @Contract("_ -> this") 180 | public KeyValueQuery limit(int rows) { 181 | setRows(rows); 182 | return this; 183 | } 184 | 185 | } 186 | -------------------------------------------------------------------------------- /src/main/java/org/springframework/data/keyvalue/core/query/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Key/value specific query and abstractions. 3 | */ 4 | @org.jspecify.annotations.NullMarked 5 | package org.springframework.data.keyvalue.core.query; 6 | -------------------------------------------------------------------------------- /src/main/java/org/springframework/data/keyvalue/repository/KeyValueRepository.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014-2025 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.data.keyvalue.repository; 17 | 18 | import org.springframework.data.repository.ListCrudRepository; 19 | import org.springframework.data.repository.ListPagingAndSortingRepository; 20 | 21 | /** 22 | * @author Christoph Strobl 23 | * @param 24 | * @param 25 | */ 26 | public interface KeyValueRepository extends ListCrudRepository, ListPagingAndSortingRepository { 27 | 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/org/springframework/data/keyvalue/repository/config/QueryCreatorType.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014-2025 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.data.keyvalue.repository.config; 17 | 18 | import java.lang.annotation.Documented; 19 | import java.lang.annotation.ElementType; 20 | import java.lang.annotation.Retention; 21 | import java.lang.annotation.RetentionPolicy; 22 | import java.lang.annotation.Target; 23 | 24 | import org.springframework.data.keyvalue.repository.query.KeyValuePartTreeQuery; 25 | import org.springframework.data.repository.query.RepositoryQuery; 26 | import org.springframework.data.repository.query.parser.AbstractQueryCreator; 27 | 28 | /** 29 | * Annotation to customize the query creator type to be used for a specific store. 30 | * 31 | * @author Oliver Gierke 32 | * @author Christoph Strobl 33 | */ 34 | @Documented 35 | @Retention(RetentionPolicy.RUNTIME) 36 | @Target(ElementType.ANNOTATION_TYPE) 37 | public @interface QueryCreatorType { 38 | 39 | Class> value(); 40 | 41 | /** 42 | * The {@link RepositoryQuery} type to be created by the {@link QueryCreatorType#value()}. 43 | * 44 | * @return 45 | * @since 1.1 46 | */ 47 | Class repositoryQueryType() default KeyValuePartTreeQuery.class; 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/org/springframework/data/keyvalue/repository/config/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Support infrastructure for the configuration of key/value specific repositories. 3 | */ 4 | @org.jspecify.annotations.NullMarked 5 | package org.springframework.data.keyvalue.repository.config; 6 | -------------------------------------------------------------------------------- /src/main/java/org/springframework/data/keyvalue/repository/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Key/value specific repository implementation. 3 | */ 4 | @org.jspecify.annotations.NullMarked 5 | package org.springframework.data.keyvalue.repository; 6 | -------------------------------------------------------------------------------- /src/main/java/org/springframework/data/keyvalue/repository/query/CachingKeyValuePartTreeQuery.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016-2025 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.data.keyvalue.repository.query; 17 | 18 | import org.jspecify.annotations.Nullable; 19 | import org.springframework.data.keyvalue.core.KeyValueOperations; 20 | import org.springframework.data.keyvalue.core.query.KeyValueQuery; 21 | import org.springframework.data.repository.query.QueryMethod; 22 | import org.springframework.data.repository.query.ValueExpressionDelegate; 23 | import org.springframework.data.repository.query.parser.AbstractQueryCreator; 24 | import org.springframework.data.repository.query.parser.PartTree; 25 | 26 | /** 27 | * {@link KeyValuePartTreeQuery} implementation deriving queries from {@link PartTree} using a predefined 28 | * {@link AbstractQueryCreator} that caches the once created query. 29 | * 30 | * @author Christoph Strobl 31 | * @author Mark Paluch 32 | * @since 1.1 33 | */ 34 | public class CachingKeyValuePartTreeQuery extends KeyValuePartTreeQuery { 35 | 36 | private @Nullable KeyValueQuery cachedQuery; 37 | 38 | public CachingKeyValuePartTreeQuery(QueryMethod queryMethod, 39 | ValueExpressionDelegate valueExpressionDelegate, KeyValueOperations keyValueOperations, 40 | Class> queryCreator) { 41 | super(queryMethod, valueExpressionDelegate, keyValueOperations, queryCreator); 42 | } 43 | 44 | protected KeyValueQuery prepareQuery(Object[] parameters) { 45 | 46 | if (cachedQuery == null) { 47 | cachedQuery = super.prepareQuery(parameters); 48 | } 49 | 50 | return prepareQuery(cachedQuery, parameters); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/org/springframework/data/keyvalue/repository/query/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Query derivation mechanism for key/value specific repositories providing a generic SpEL based implementation. 3 | */ 4 | @org.jspecify.annotations.NullMarked 5 | package org.springframework.data.keyvalue.repository.query; 6 | -------------------------------------------------------------------------------- /src/main/java/org/springframework/data/keyvalue/repository/support/KeyValueQuerydslUtils.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014-2025 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.data.keyvalue.repository.support; 17 | 18 | import java.util.ArrayList; 19 | import java.util.List; 20 | 21 | import org.springframework.data.domain.Sort; 22 | import org.springframework.data.domain.Sort.Order; 23 | import org.springframework.data.mapping.PropertyPath; 24 | import org.springframework.data.querydsl.QSort; 25 | import org.springframework.lang.Contract; 26 | import org.springframework.util.Assert; 27 | 28 | import com.querydsl.core.types.Expression; 29 | import com.querydsl.core.types.OrderSpecifier; 30 | import com.querydsl.core.types.OrderSpecifier.NullHandling; 31 | import com.querydsl.core.types.Path; 32 | import com.querydsl.core.types.dsl.Expressions; 33 | import com.querydsl.core.types.dsl.PathBuilder; 34 | 35 | /** 36 | * Utilities for Querydsl usage. 37 | * 38 | * @author Christoph Strobl 39 | * @author Thomas Darimont 40 | * @author Oliver Gierke 41 | * @author Mark Paluch 42 | */ 43 | abstract class KeyValueQuerydslUtils { 44 | 45 | private KeyValueQuerydslUtils() { 46 | // prevent instantiation 47 | } 48 | 49 | /** 50 | * Transforms a plain {@link Order} into a QueryDsl specific {@link OrderSpecifier}. 51 | * 52 | * @param sort must not be {@literal null}. 53 | * @param builder must not be {@literal null}. 54 | * @return empty {@code OrderSpecifier[]} when sort is {@literal null}. 55 | */ 56 | @Contract("!null, !null -> new") 57 | static OrderSpecifier[] toOrderSpecifier(Sort sort, PathBuilder builder) { 58 | 59 | Assert.notNull(sort, "Sort must not be null"); 60 | Assert.notNull(builder, "Builder must not be null"); 61 | 62 | List> specifiers = null; 63 | 64 | if (sort instanceof QSort) { 65 | specifiers = ((QSort) sort).getOrderSpecifiers(); 66 | } else { 67 | 68 | specifiers = new ArrayList<>(); 69 | for (Order order : sort) { 70 | specifiers.add(toOrderSpecifier(order, builder)); 71 | } 72 | } 73 | 74 | return specifiers.toArray(new OrderSpecifier[specifiers.size()]); 75 | } 76 | 77 | @SuppressWarnings({ "rawtypes", "unchecked" }) 78 | private static OrderSpecifier toOrderSpecifier(Order order, PathBuilder builder) { 79 | return new OrderSpecifier( 80 | order.isAscending() ? com.querydsl.core.types.Order.ASC : com.querydsl.core.types.Order.DESC, 81 | buildOrderPropertyPathFrom(order, builder), toQueryDslNullHandling(order.getNullHandling())); 82 | } 83 | 84 | /** 85 | * Creates an {@link Expression} for the given {@link Order} property. 86 | * 87 | * @param order must not be {@literal null}. 88 | * @param builder must not be {@literal null}. 89 | * @return 90 | */ 91 | private static Expression buildOrderPropertyPathFrom(Order order, PathBuilder builder) { 92 | 93 | Assert.notNull(order, "Order must not be null"); 94 | Assert.notNull(builder, "Builder must not be null"); 95 | 96 | PropertyPath path = PropertyPath.from(order.getProperty(), builder.getType()); 97 | Expression sortPropertyExpression = builder; 98 | 99 | while (path != null) { 100 | 101 | if (!path.hasNext() && order.isIgnoreCase()) { 102 | // if order is ignore-case we have to treat the last path segment as a String. 103 | sortPropertyExpression = Expressions.stringPath((Path) sortPropertyExpression, path.getSegment()).lower(); 104 | } else { 105 | sortPropertyExpression = Expressions.path(path.getType(), (Path) sortPropertyExpression, path.getSegment()); 106 | } 107 | 108 | path = path.next(); 109 | } 110 | 111 | return sortPropertyExpression; 112 | } 113 | 114 | /** 115 | * Converts the given {@link org.springframework.data.domain.Sort.NullHandling} to the appropriate Querydsl 116 | * {@link NullHandling}. 117 | * 118 | * @param nullHandling must not be {@literal null}. 119 | * @return 120 | */ 121 | private static NullHandling toQueryDslNullHandling(org.springframework.data.domain.Sort.NullHandling nullHandling) { 122 | 123 | Assert.notNull(nullHandling, "NullHandling must not be null"); 124 | 125 | switch (nullHandling) { 126 | 127 | case NULLS_FIRST: 128 | return NullHandling.NullsFirst; 129 | 130 | case NULLS_LAST: 131 | return NullHandling.NullsLast; 132 | 133 | case NATIVE: 134 | default: 135 | return NullHandling.Default; 136 | } 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /src/main/java/org/springframework/data/keyvalue/repository/support/QuerydslKeyValueRepository.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014-2025 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.data.keyvalue.repository.support; 17 | 18 | import java.util.Optional; 19 | import java.util.function.Function; 20 | 21 | import org.springframework.data.domain.Page; 22 | import org.springframework.data.domain.Pageable; 23 | import org.springframework.data.domain.Sort; 24 | import org.springframework.data.keyvalue.core.KeyValueOperations; 25 | import org.springframework.data.keyvalue.repository.KeyValueRepository; 26 | import org.springframework.data.projection.SpelAwareProxyProjectionFactory; 27 | import org.springframework.data.querydsl.EntityPathResolver; 28 | import org.springframework.data.querydsl.QuerydslPredicateExecutor; 29 | import org.springframework.data.querydsl.SimpleEntityPathResolver; 30 | import org.springframework.data.repository.core.EntityInformation; 31 | import org.springframework.data.repository.query.FluentQuery; 32 | import org.springframework.util.Assert; 33 | 34 | import com.querydsl.collections.CollQuery; 35 | import com.querydsl.core.types.OrderSpecifier; 36 | import com.querydsl.core.types.Predicate; 37 | 38 | /** 39 | * {@link KeyValueRepository} implementation capable of executing {@link Predicate}s using {@link CollQuery}. 40 | * 41 | * @author Christoph Strobl 42 | * @author Oliver Gierke 43 | * @author Thomas Darimont 44 | * @author Mark Paluch 45 | * @param the domain type to manage 46 | * @param the identifier type of the domain type 47 | * @deprecated since 2.6, use {@link QuerydslKeyValuePredicateExecutor} instead. 48 | */ 49 | @Deprecated 50 | public class QuerydslKeyValueRepository extends SimpleKeyValueRepository 51 | implements QuerydslPredicateExecutor { 52 | 53 | private final QuerydslKeyValuePredicateExecutor executor; 54 | 55 | /** 56 | * Creates a new {@link QuerydslKeyValueRepository} for the given {@link EntityInformation} and 57 | * {@link KeyValueOperations}. 58 | * 59 | * @param entityInformation must not be {@literal null}. 60 | * @param operations must not be {@literal null}. 61 | */ 62 | public QuerydslKeyValueRepository(EntityInformation entityInformation, KeyValueOperations operations) { 63 | this(entityInformation, operations, SimpleEntityPathResolver.INSTANCE); 64 | } 65 | 66 | /** 67 | * Creates a new {@link QuerydslKeyValueRepository} for the given {@link EntityInformation}, 68 | * {@link KeyValueOperations} and {@link EntityPathResolver}. 69 | * 70 | * @param entityInformation must not be {@literal null}. 71 | * @param operations must not be {@literal null}. 72 | * @param resolver must not be {@literal null}. 73 | */ 74 | public QuerydslKeyValueRepository(EntityInformation entityInformation, KeyValueOperations operations, 75 | EntityPathResolver resolver) { 76 | 77 | super(entityInformation, operations); 78 | 79 | Assert.notNull(resolver, "EntityPathResolver must not be null"); 80 | 81 | this.executor = new QuerydslKeyValuePredicateExecutor<>(entityInformation, new SpelAwareProxyProjectionFactory(), 82 | operations, resolver); 83 | } 84 | 85 | @Override 86 | public Optional findOne(Predicate predicate) { 87 | return executor.findOne(predicate); 88 | } 89 | 90 | @Override 91 | public Iterable findAll(Predicate predicate) { 92 | return executor.findAll(predicate); 93 | } 94 | 95 | @Override 96 | public Iterable findAll(Predicate predicate, OrderSpecifier... orders) { 97 | return executor.findAll(predicate, orders); 98 | } 99 | 100 | @Override 101 | public Iterable findAll(Predicate predicate, Sort sort) { 102 | return executor.findAll(predicate, sort); 103 | } 104 | 105 | @Override 106 | public Page findAll(Predicate predicate, Pageable pageable) { 107 | return executor.findAll(predicate, pageable); 108 | } 109 | 110 | @Override 111 | public Iterable findAll(OrderSpecifier... orders) { 112 | return executor.findAll(orders); 113 | } 114 | 115 | @Override 116 | public long count(Predicate predicate) { 117 | return executor.count(predicate); 118 | } 119 | 120 | @Override 121 | public boolean exists(Predicate predicate) { 122 | return executor.exists(predicate); 123 | } 124 | 125 | @Override 126 | public R findBy(Predicate predicate, 127 | Function, R> queryFunction) { 128 | return executor.findBy(predicate, queryFunction); 129 | } 130 | 131 | } 132 | -------------------------------------------------------------------------------- /src/main/java/org/springframework/data/keyvalue/repository/support/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Support infrastructure for query derivation of key/value specific repositories. 3 | */ 4 | @org.jspecify.annotations.NullMarked 5 | package org.springframework.data.keyvalue.repository.support; 6 | -------------------------------------------------------------------------------- /src/main/java/org/springframework/data/map/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Repository implementation backed by generic {@link java.util.Map} instances. 3 | */ 4 | @org.jspecify.annotations.NullMarked 5 | package org.springframework.data.map; 6 | -------------------------------------------------------------------------------- /src/main/java/org/springframework/data/map/repository/config/MapRepositoriesRegistrar.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014-2025 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.data.map.repository.config; 17 | 18 | import java.lang.annotation.Annotation; 19 | 20 | import org.springframework.data.repository.config.RepositoryBeanDefinitionRegistrarSupport; 21 | import org.springframework.data.repository.config.RepositoryConfigurationExtension; 22 | 23 | /** 24 | * Map specific {@link RepositoryBeanDefinitionRegistrarSupport} implementation. 25 | * 26 | * @author Christoph Strobl 27 | */ 28 | public class MapRepositoriesRegistrar extends RepositoryBeanDefinitionRegistrarSupport { 29 | 30 | @Override 31 | protected Class getAnnotation() { 32 | return EnableMapRepositories.class; 33 | } 34 | 35 | @Override 36 | protected RepositoryConfigurationExtension getExtension() { 37 | return new MapRepositoryConfigurationExtension(); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/org/springframework/data/map/repository/config/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Support infrastructure for the configuration of {@link java.util.Map} repositories. 3 | */ 4 | @org.jspecify.annotations.NullMarked 5 | package org.springframework.data.map.repository.config; 6 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/spring.factories: -------------------------------------------------------------------------------- 1 | org.springframework.data.repository.core.support.RepositoryFactorySupport=org.springframework.data.keyvalue.repository.support.KeyValueRepositoryFactory 2 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/spring/aot.factories: -------------------------------------------------------------------------------- 1 | org.springframework.aot.hint.RuntimeHintsRegistrar=\ 2 | org.springframework.data.keyvalue.aot.KeyValueRuntimeHints 3 | -------------------------------------------------------------------------------- /src/main/resources/notice.txt: -------------------------------------------------------------------------------- 1 | Spring Data KeyValue 4.0 M3 (2025.1.0) 2 | Copyright (c) 2015-2019 Pivotal Software, Inc. 3 | 4 | This product is licensed to you under the Apache License, Version 2.0 5 | (the "License"). You may not use this product except in compliance with 6 | the License. 7 | 8 | This product may include a number of subcomponents with separate 9 | copyright notices and license terms. Your use of the source code for 10 | these subcomponents is subject to the terms and conditions of the 11 | subcomponent's license, as noted in the license.txt file. 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/test/java/org/springframework/data/keyvalue/CustomKeySpaceAnnotationWithAliasFor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016-2025 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.data.keyvalue; 17 | 18 | import java.lang.annotation.ElementType; 19 | import java.lang.annotation.Retention; 20 | import java.lang.annotation.RetentionPolicy; 21 | import java.lang.annotation.Target; 22 | 23 | import org.springframework.core.annotation.AliasFor; 24 | import org.springframework.data.annotation.Persistent; 25 | import org.springframework.data.keyvalue.annotation.KeySpace; 26 | 27 | /** 28 | * Custom composed {@link Persistent} annotation using {@link AliasFor} on name attribute. 29 | * 30 | * @author Christoph Strobl 31 | */ 32 | @Persistent 33 | @Retention(RetentionPolicy.RUNTIME) 34 | @Target({ ElementType.TYPE }) 35 | @KeySpace 36 | public @interface CustomKeySpaceAnnotationWithAliasFor { 37 | 38 | @AliasFor(annotation = KeySpace.class, attribute = "value") 39 | String name() default ""; 40 | } 41 | -------------------------------------------------------------------------------- /src/test/java/org/springframework/data/keyvalue/Person.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014-2025 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.data.keyvalue; 17 | 18 | import org.springframework.data.annotation.Id; 19 | 20 | import com.querydsl.core.annotations.QueryEntity; 21 | 22 | /** 23 | * @author Christoph Strobl 24 | * @author Mark Paluch 25 | */ 26 | @QueryEntity 27 | public class Person { 28 | 29 | private @Id String id; 30 | private String firstname; 31 | private int age; 32 | 33 | public Person(String firstname, int age) { 34 | super(); 35 | this.firstname = firstname; 36 | this.age = age; 37 | } 38 | 39 | public String getId() { 40 | return this.id; 41 | } 42 | 43 | public String getFirstname() { 44 | return this.firstname; 45 | } 46 | 47 | public int getAge() { 48 | return this.age; 49 | } 50 | 51 | public void setId(String id) { 52 | this.id = id; 53 | } 54 | 55 | public void setFirstname(String firstname) { 56 | this.firstname = firstname; 57 | } 58 | 59 | public void setAge(int age) { 60 | this.age = age; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/test/java/org/springframework/data/keyvalue/SubclassOfTypeWithCustomComposedKeySpaceAnnotation.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015-2025 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.data.keyvalue; 17 | 18 | import org.springframework.data.keyvalue.annotation.KeySpace; 19 | 20 | /** 21 | * Class that inherits its {@link KeySpace} from a super class annotated with a custom {@link CustomKeySpaceAnnotation} 22 | * annotation. 23 | * 24 | * @author Christoph Strobl 25 | */ 26 | public class SubclassOfTypeWithCustomComposedKeySpaceAnnotation 27 | extends TypeWithCustomComposedKeySpaceAnnotationUsingAliasFor { 28 | 29 | public SubclassOfTypeWithCustomComposedKeySpaceAnnotation(String name) { 30 | super(name); 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /src/test/java/org/springframework/data/keyvalue/TypeWithCustomComposedKeySpaceAnnotationUsingAliasFor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016-2025 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.data.keyvalue; 17 | 18 | import org.springframework.data.annotation.Id; 19 | import org.springframework.data.annotation.Persistent; 20 | 21 | /** 22 | * A {@link Persistent} type with {@link CustomKeySpaceAnnotationWithAliasFor}. 23 | * 24 | * @author Christoph Strobl 25 | * @author Mark Paluch 26 | */ 27 | @CustomKeySpaceAnnotationWithAliasFor(name = "aliased") 28 | public class TypeWithCustomComposedKeySpaceAnnotationUsingAliasFor { 29 | 30 | @Id String id; 31 | String name; 32 | 33 | public TypeWithCustomComposedKeySpaceAnnotationUsingAliasFor(String name) { 34 | this.name = name; 35 | } 36 | 37 | public String getId() { 38 | return this.id; 39 | } 40 | 41 | public String getName() { 42 | return this.name; 43 | } 44 | 45 | public void setId(String id) { 46 | this.id = id; 47 | } 48 | 49 | public void setName(String name) { 50 | this.name = name; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/test/java/org/springframework/data/keyvalue/TypeWithDirectKeySpaceAnnotation.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015-2025 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.data.keyvalue; 17 | 18 | import org.springframework.data.annotation.Persistent; 19 | import org.springframework.data.keyvalue.annotation.KeySpace; 20 | 21 | /** 22 | * A {@link Persistent} type with explict {@link KeySpace}. 23 | * 24 | * @author Christoph Strobl 25 | */ 26 | @KeySpace("rhaegar") 27 | public class TypeWithDirectKeySpaceAnnotation { 28 | 29 | } 30 | -------------------------------------------------------------------------------- /src/test/java/org/springframework/data/keyvalue/TypeWithInhteritedPersistentAnnotationNotHavingKeySpace.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015-2025 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.data.keyvalue; 17 | 18 | import org.springframework.data.annotation.Persistent; 19 | import org.springframework.data.annotation.TypeAlias; 20 | import org.springframework.data.keyvalue.annotation.KeySpace; 21 | 22 | /** 23 | * A type inheriting {@link Persistent} from {@link TypeAlias} not having a {@link KeySpace} defined. 24 | * 25 | * @author Christoph Strobl 26 | */ 27 | @TypeAlias("foo") 28 | public class TypeWithInhteritedPersistentAnnotationNotHavingKeySpace { 29 | 30 | } 31 | -------------------------------------------------------------------------------- /src/test/java/org/springframework/data/keyvalue/TypeWithPersistentAnnotationNotHavingKeySpace.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015-2025 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.data.keyvalue; 17 | 18 | import org.springframework.data.annotation.Persistent; 19 | import org.springframework.data.keyvalue.annotation.KeySpace; 20 | 21 | /** 22 | * A {@link Persistent} class without a defined {@link KeySpace}. 23 | * 24 | * @author Christoph Strobl 25 | */ 26 | @Persistent 27 | public class TypeWithPersistentAnnotationNotHavingKeySpace { 28 | 29 | } 30 | -------------------------------------------------------------------------------- /src/test/java/org/springframework/data/keyvalue/core/DefaultIdentifierGeneratorUnitTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016-2025 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.data.keyvalue.core; 17 | 18 | import static org.assertj.core.api.Assertions.*; 19 | 20 | import java.util.Date; 21 | import java.util.UUID; 22 | 23 | import org.junit.jupiter.api.Test; 24 | import org.springframework.dao.InvalidDataAccessApiUsageException; 25 | import org.springframework.data.util.TypeInformation; 26 | 27 | /** 28 | * @author Christoph Strobl 29 | */ 30 | class DefaultIdentifierGeneratorUnitTests { 31 | 32 | private DefaultIdentifierGenerator generator = DefaultIdentifierGenerator.INSTANCE; 33 | 34 | @Test 35 | void shouldThrowExceptionForUnsupportedType() { 36 | assertThatExceptionOfType(InvalidDataAccessApiUsageException.class) 37 | .isThrownBy(() -> generator.generateIdentifierOfType(TypeInformation.of(Date.class))); 38 | } 39 | 40 | @Test // DATAKV-136 41 | void shouldGenerateUUIDValueCorrectly() { 42 | 43 | Object value = generator.generateIdentifierOfType(TypeInformation.of(UUID.class)); 44 | 45 | assertThat(value).isNotNull().isInstanceOf(UUID.class); 46 | } 47 | 48 | @Test // DATAKV-136 49 | void shouldGenerateStringValueCorrectly() { 50 | 51 | Object value = generator.generateIdentifierOfType(TypeInformation.of(String.class)); 52 | 53 | assertThat(value).isNotNull().isInstanceOf(String.class); 54 | } 55 | 56 | @Test // DATAKV-136 57 | void shouldGenerateLongValueCorrectly() { 58 | 59 | Object value = generator.generateIdentifierOfType(TypeInformation.of(Long.class)); 60 | 61 | assertThat(value).isNotNull().isInstanceOf(Long.class); 62 | } 63 | 64 | @Test // DATAKV-136 65 | void shouldGenerateIntValueCorrectly() { 66 | 67 | Object value = generator.generateIdentifierOfType(TypeInformation.of(Integer.class)); 68 | 69 | assertThat(value).isNotNull().isInstanceOf(Integer.class); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/test/java/org/springframework/data/keyvalue/core/ForwardingCloseableIteratorUnitTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015-2025 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.data.keyvalue.core; 17 | 18 | import static org.assertj.core.api.Assertions.*; 19 | import static org.mockito.Mockito.*; 20 | 21 | import java.util.Iterator; 22 | import java.util.Map; 23 | import java.util.Map.Entry; 24 | import java.util.NoSuchElementException; 25 | 26 | import org.junit.jupiter.api.Test; 27 | import org.junit.jupiter.api.extension.ExtendWith; 28 | import org.mockito.Mock; 29 | import org.mockito.junit.jupiter.MockitoExtension; 30 | 31 | import org.springframework.data.util.CloseableIterator; 32 | 33 | /** 34 | * @author Christoph Strobl 35 | * @author Thomas Darimont 36 | * @author Oliver Gierke 37 | */ 38 | @ExtendWith(MockitoExtension.class) 39 | class ForwardingCloseableIteratorUnitTests { 40 | 41 | @Mock Iterator> iteratorMock; 42 | @Mock Runnable closeActionMock; 43 | 44 | @Test // DATAKV-99 45 | void hasNextShouldDelegateToWrappedIterator() { 46 | 47 | when(iteratorMock.hasNext()).thenReturn(true); 48 | 49 | CloseableIterator> iterator = new ForwardingCloseableIterator<>(iteratorMock); 50 | 51 | try { 52 | assertThat(iterator.hasNext()).isTrue(); 53 | verify(iteratorMock, times(1)).hasNext(); 54 | } finally { 55 | iterator.close(); 56 | } 57 | } 58 | 59 | @Test // DATAKV-99 60 | @SuppressWarnings("unchecked") 61 | void nextShouldDelegateToWrappedIterator() { 62 | 63 | when(iteratorMock.next()).thenReturn((Entry) mock(Map.Entry.class)); 64 | 65 | CloseableIterator> iterator = new ForwardingCloseableIterator<>(iteratorMock); 66 | 67 | try { 68 | assertThat(iterator.next()).isNotNull(); 69 | verify(iteratorMock, times(1)).next(); 70 | } finally { 71 | iterator.close(); 72 | } 73 | } 74 | 75 | @Test // DATAKV-99 76 | void nextShouldThrowErrorWhenWrappedIteratorHasNoMoreElements() { 77 | 78 | when(iteratorMock.next()).thenThrow(new NoSuchElementException()); 79 | 80 | CloseableIterator> iterator = new ForwardingCloseableIterator<>(iteratorMock); 81 | 82 | try { 83 | assertThatExceptionOfType(NoSuchElementException.class).isThrownBy(iterator::next); 84 | } finally { 85 | iterator.close(); 86 | } 87 | } 88 | 89 | @Test // DATAKV-99 90 | void closeShouldDoNothingByDefault() { 91 | 92 | new ForwardingCloseableIterator<>(iteratorMock).close(); 93 | 94 | verifyNoInteractions(iteratorMock); 95 | } 96 | 97 | @Test // DATAKV-99 98 | void closeShouldInvokeConfiguredCloseAction() { 99 | 100 | new ForwardingCloseableIterator<>(iteratorMock, closeActionMock).close(); 101 | 102 | verify(closeActionMock, times(1)).run(); 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/test/java/org/springframework/data/keyvalue/core/IterableConverterUnitTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015-2025 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.data.keyvalue.core; 17 | 18 | import static org.assertj.core.api.Assertions.*; 19 | import static org.springframework.data.keyvalue.core.IterableConverter.*; 20 | 21 | import java.util.ArrayList; 22 | import java.util.Collections; 23 | import java.util.HashSet; 24 | import java.util.LinkedHashSet; 25 | import java.util.List; 26 | import java.util.Set; 27 | 28 | import org.junit.jupiter.api.Test; 29 | 30 | /** 31 | * @author Christoph Strobl 32 | * @author Mark Paluch 33 | */ 34 | class IterableConverterUnitTests { 35 | 36 | @Test // DATAKV-101 37 | void toListShouldReturnEmptyListWhenSourceEmpty() { 38 | assertThat(toList(Collections.emptySet())).isEmpty(); 39 | } 40 | 41 | @Test // DATAKV-101 42 | void toListShouldReturnSameObjectWhenSourceIsAlreadyListType() { 43 | 44 | List source = new ArrayList<>(); 45 | 46 | assertThat(toList(source)).isSameAs(source); 47 | } 48 | 49 | @Test // DATAKV-101 50 | void toListShouldReturnListWhenSourceIsNonListType() { 51 | 52 | Set source = new HashSet<>(); 53 | source.add("tyrion"); 54 | 55 | assertThat(toList(source)).isInstanceOf(List.class); 56 | } 57 | 58 | @Test // DATAKV-101 59 | void toListShouldHoldValuesInOrderOfSource() { 60 | 61 | Set source = new LinkedHashSet<>(); 62 | source.add("tyrion"); 63 | source.add("jaime"); 64 | 65 | assertThat(toList(source)).containsExactly(source.toArray(new String[2])); 66 | } 67 | 68 | } 69 | -------------------------------------------------------------------------------- /src/test/java/org/springframework/data/keyvalue/core/KeyValuePersistenceExceptionTranslatorUnitTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014-2025 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.data.keyvalue.core; 17 | 18 | import static org.assertj.core.api.Assertions.*; 19 | 20 | import java.util.NoSuchElementException; 21 | 22 | import org.junit.jupiter.api.Test; 23 | 24 | import org.springframework.beans.factory.NoSuchBeanDefinitionException; 25 | import org.springframework.dao.DataRetrievalFailureException; 26 | 27 | /** 28 | * @author Christoph Strobl 29 | */ 30 | class KeyValuePersistenceExceptionTranslatorUnitTests { 31 | 32 | private KeyValuePersistenceExceptionTranslator translator = new KeyValuePersistenceExceptionTranslator(); 33 | 34 | @Test // DATACMNS-525 35 | void translateExeptionShouldReturnDataAccessExceptionWhenGivenOne() { 36 | assertThat(translator.translateExceptionIfPossible(new DataRetrievalFailureException("booh"))) 37 | .isInstanceOf(DataRetrievalFailureException.class); 38 | } 39 | 40 | @Test // DATACMNS-525, DATAKV-192 41 | void translateExeptionShouldReturnNullWhenGivenNull() { 42 | assertThatIllegalArgumentException() 43 | .isThrownBy(() -> assertThat(translator.translateExceptionIfPossible(null)).isNull()); 44 | } 45 | 46 | @Test // DATACMNS-525 47 | void translateExeptionShouldTranslateNoSuchElementExceptionToDataRetrievalFailureException() { 48 | assertThat(translator.translateExceptionIfPossible(new NoSuchElementException(""))) 49 | .isInstanceOf(DataRetrievalFailureException.class); 50 | } 51 | 52 | @Test // DATACMNS-525 53 | void translateExeptionShouldTranslateIndexOutOfBoundsExceptionToDataRetrievalFailureException() { 54 | assertThat(translator.translateExceptionIfPossible(new IndexOutOfBoundsException(""))) 55 | .isInstanceOf(DataRetrievalFailureException.class); 56 | } 57 | 58 | @Test // DATACMNS-525 59 | void translateExeptionShouldTranslateIllegalStateExceptionToDataRetrievalFailureException() { 60 | assertThat(translator.translateExceptionIfPossible(new IllegalStateException(""))) 61 | .isInstanceOf(DataRetrievalFailureException.class); 62 | } 63 | 64 | @Test // DATACMNS-525 65 | void translateExeptionShouldTranslateAnyJavaExceptionToUncategorizedKeyValueException() { 66 | assertThat(translator.translateExceptionIfPossible(new UnsupportedOperationException(""))) 67 | .isInstanceOf(UncategorizedKeyValueException.class); 68 | } 69 | 70 | @Test // DATACMNS-525 71 | void translateExeptionShouldReturnNullForNonJavaExceptions() { 72 | assertThat(translator.translateExceptionIfPossible(new NoSuchBeanDefinitionException(""))).isNull(); 73 | } 74 | 75 | } 76 | -------------------------------------------------------------------------------- /src/test/java/org/springframework/data/keyvalue/core/PredicateQueryEngineUnitTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024-2025 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.data.keyvalue.core; 17 | 18 | import static org.assertj.core.api.Assertions.*; 19 | import static org.mockito.Mockito.*; 20 | 21 | import java.lang.reflect.Method; 22 | import java.util.ArrayList; 23 | import java.util.Arrays; 24 | import java.util.Collection; 25 | import java.util.List; 26 | import java.util.function.Predicate; 27 | 28 | import org.junit.jupiter.api.BeforeEach; 29 | import org.junit.jupiter.api.Test; 30 | import org.junit.jupiter.api.extension.ExtendWith; 31 | import org.mockito.Mock; 32 | import org.mockito.junit.jupiter.MockitoExtension; 33 | import org.springframework.data.annotation.Id; 34 | import org.springframework.data.keyvalue.repository.query.PredicateQueryCreator; 35 | import org.springframework.data.projection.SpelAwareProxyProjectionFactory; 36 | import org.springframework.data.repository.core.RepositoryMetadata; 37 | import org.springframework.data.repository.query.ParametersParameterAccessor; 38 | import org.springframework.data.repository.query.QueryMethod; 39 | import org.springframework.data.repository.query.parser.PartTree; 40 | import org.springframework.data.util.TypeInformation; 41 | 42 | /** 43 | * Unit tests for {@link SpelQueryEngine}. 44 | * 45 | * @author Christoph Strobl 46 | */ 47 | @ExtendWith(MockitoExtension.class) 48 | public class PredicateQueryEngineUnitTests { 49 | 50 | private static final Person BOB_WITH_FIRSTNAME = new Person("bob", 30); 51 | private static final Person MIKE_WITHOUT_FIRSTNAME = new Person(null, 25); 52 | 53 | @Mock KeyValueAdapter adapter; 54 | 55 | private PredicateQueryEngine engine; 56 | 57 | private Iterable people = Arrays.asList(BOB_WITH_FIRSTNAME, MIKE_WITHOUT_FIRSTNAME); 58 | 59 | @BeforeEach 60 | void setUp() { 61 | 62 | engine = new PredicateQueryEngine(); 63 | engine.registerAdapter(adapter); 64 | } 65 | 66 | @Test // DATAKV-114 67 | @SuppressWarnings("unchecked") 68 | void queriesEntitiesWithNullProperty() throws Exception { 69 | 70 | doReturn(people).when(adapter).getAllOf(anyString()); 71 | 72 | Collection result = engine.execute(createQueryForMethodWithArgs("findByFirstname", "bob"), null, -1, -1, 73 | anyString()); 74 | assertThat(result).containsExactly(BOB_WITH_FIRSTNAME); 75 | } 76 | 77 | @Test // DATAKV-114 78 | void countsEntitiesWithNullProperty() throws Exception { 79 | 80 | doReturn(people).when(adapter).getAllOf(anyString()); 81 | 82 | assertThat(engine.count(createQueryForMethodWithArgs("findByFirstname", "bob"), anyString())).isEqualTo(1L); 83 | } 84 | 85 | private static Predicate createQueryForMethodWithArgs(String methodName, Object... args) throws Exception { 86 | 87 | List> types = new ArrayList<>(args.length); 88 | 89 | for (Object arg : args) { 90 | types.add(arg.getClass()); 91 | } 92 | 93 | Method method = PersonRepository.class.getMethod(methodName, types.toArray(new Class[types.size()])); 94 | RepositoryMetadata metadata = mock(RepositoryMetadata.class); 95 | doReturn(method.getReturnType()).when(metadata).getReturnedDomainClass(method); 96 | doReturn(TypeInformation.fromReturnTypeOf(method)).when(metadata).getReturnType(method); 97 | doReturn(TypeInformation.of(Person.class)).when(metadata).getDomainTypeInformation(); 98 | 99 | PartTree partTree = new PartTree(method.getName(), method.getReturnType()); 100 | PredicateQueryCreator creator = new PredicateQueryCreator(partTree, new ParametersParameterAccessor( 101 | new QueryMethod(method, metadata, new SpelAwareProxyProjectionFactory()).getParameters(), args)); 102 | 103 | return creator.createQuery().getCriteria(); 104 | } 105 | 106 | interface PersonRepository { 107 | Person findByFirstname(String firstname); 108 | } 109 | 110 | public static class Person { 111 | 112 | @Id String id; 113 | String firstname; 114 | int age; 115 | 116 | Person(String firstname, int age) { 117 | 118 | this.firstname = firstname; 119 | this.age = age; 120 | } 121 | 122 | public String getFirstname() { 123 | return firstname; 124 | } 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /src/test/java/org/springframework/data/keyvalue/core/SpelQueryEngineUnitTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015-2025 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.data.keyvalue.core; 17 | 18 | import static org.assertj.core.api.Assertions.*; 19 | import static org.mockito.Mockito.*; 20 | 21 | import java.lang.reflect.Method; 22 | import java.util.ArrayList; 23 | import java.util.Arrays; 24 | import java.util.Collection; 25 | import java.util.List; 26 | 27 | import org.junit.jupiter.api.BeforeEach; 28 | import org.junit.jupiter.api.Test; 29 | import org.junit.jupiter.api.extension.ExtendWith; 30 | import org.mockito.Mock; 31 | import org.mockito.junit.jupiter.MockitoExtension; 32 | import org.springframework.data.annotation.Id; 33 | import org.springframework.data.keyvalue.repository.query.SpelQueryCreator; 34 | import org.springframework.data.projection.SpelAwareProxyProjectionFactory; 35 | import org.springframework.data.repository.core.RepositoryMetadata; 36 | import org.springframework.data.repository.query.ParametersParameterAccessor; 37 | import org.springframework.data.repository.query.QueryMethod; 38 | import org.springframework.data.repository.query.parser.PartTree; 39 | import org.springframework.data.util.TypeInformation; 40 | import org.springframework.expression.spel.support.SimpleEvaluationContext; 41 | 42 | /** 43 | * Unit tests for {@link SpelQueryEngine}. 44 | * 45 | * @author Martin Macko 46 | * @author Oliver Gierke 47 | * @author Mark Paluch 48 | */ 49 | @ExtendWith(MockitoExtension.class) 50 | public class SpelQueryEngineUnitTests { 51 | 52 | private static final Person BOB_WITH_FIRSTNAME = new Person("bob", 30); 53 | private static final Person MIKE_WITHOUT_FIRSTNAME = new Person(null, 25); 54 | 55 | @Mock KeyValueAdapter adapter; 56 | 57 | private SpelQueryEngine engine; 58 | 59 | private Iterable people = Arrays.asList(BOB_WITH_FIRSTNAME, MIKE_WITHOUT_FIRSTNAME); 60 | 61 | @BeforeEach 62 | void setUp() { 63 | 64 | engine = new SpelQueryEngine(); 65 | engine.registerAdapter(adapter); 66 | } 67 | 68 | @Test // DATAKV-114 69 | @SuppressWarnings("unchecked") 70 | void queriesEntitiesWithNullProperty() throws Exception { 71 | 72 | doReturn(people).when(adapter).getAllOf(anyString()); 73 | 74 | Collection result = engine.execute(createQueryForMethodWithArgs("findByFirstname", "bob"), null, -1, -1, 75 | anyString()); 76 | assertThat(result).containsExactly(BOB_WITH_FIRSTNAME); 77 | } 78 | 79 | @Test // DATAKV-114 80 | void countsEntitiesWithNullProperty() throws Exception { 81 | 82 | doReturn(people).when(adapter).getAllOf(anyString()); 83 | 84 | assertThat(engine.count(createQueryForMethodWithArgs("findByFirstname", "bob"), anyString())).isEqualTo(1L); 85 | } 86 | 87 | private static SpelCriteria createQueryForMethodWithArgs(String methodName, Object... args) throws Exception { 88 | 89 | List> types = new ArrayList<>(args.length); 90 | 91 | for (Object arg : args) { 92 | types.add(arg.getClass()); 93 | } 94 | 95 | Method method = PersonRepository.class.getMethod(methodName, types.toArray(new Class[types.size()])); 96 | RepositoryMetadata metadata = mock(RepositoryMetadata.class); 97 | doReturn(method.getReturnType()).when(metadata).getReturnedDomainClass(method); 98 | doReturn(TypeInformation.fromReturnTypeOf(method)).when(metadata).getReturnType(method); 99 | doReturn(TypeInformation.of(Person.class)).when(metadata).getDomainTypeInformation(); 100 | 101 | PartTree partTree = new PartTree(method.getName(), method.getReturnType()); 102 | SpelQueryCreator creator = new SpelQueryCreator(partTree, new ParametersParameterAccessor( 103 | new QueryMethod(method, metadata, new SpelAwareProxyProjectionFactory()).getParameters(), args)); 104 | 105 | return new SpelCriteria(creator.createQuery().getCriteria(), 106 | SimpleEvaluationContext.forReadOnlyDataBinding().withInstanceMethods().withRootObject(args).build()); 107 | } 108 | 109 | interface PersonRepository { 110 | Person findByFirstname(String firstname); 111 | } 112 | 113 | public static class Person { 114 | 115 | @Id String id; 116 | String firstname; 117 | int age; 118 | 119 | Person(String firstname, int age) { 120 | 121 | this.firstname = firstname; 122 | this.age = age; 123 | } 124 | 125 | public String getFirstname() { 126 | return firstname; 127 | } 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /src/test/java/org/springframework/data/keyvalue/core/mapping/AnnotationBasedKeySpaceResolverUnitTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014-2025 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.data.keyvalue.core.mapping; 17 | 18 | import static org.assertj.core.api.Assertions.*; 19 | 20 | import java.lang.annotation.ElementType; 21 | import java.lang.annotation.Retention; 22 | import java.lang.annotation.RetentionPolicy; 23 | import java.lang.annotation.Target; 24 | 25 | import org.junit.jupiter.api.BeforeEach; 26 | import org.junit.jupiter.api.Test; 27 | 28 | import org.springframework.core.annotation.AliasFor; 29 | import org.springframework.data.annotation.Persistent; 30 | import org.springframework.data.keyvalue.TypeWithDirectKeySpaceAnnotation; 31 | import org.springframework.data.keyvalue.TypeWithInhteritedPersistentAnnotationNotHavingKeySpace; 32 | import org.springframework.data.keyvalue.TypeWithPersistentAnnotationNotHavingKeySpace; 33 | import org.springframework.data.keyvalue.annotation.KeySpace; 34 | 35 | /** 36 | * Unit tests for {@link AnnotationBasedKeySpaceResolver}. 37 | * 38 | * @author Christoph Strobl 39 | * @author Oliver Gierke 40 | */ 41 | class AnnotationBasedKeySpaceResolverUnitTests { 42 | 43 | private AnnotationBasedKeySpaceResolver resolver; 44 | 45 | @BeforeEach 46 | void setUp() { 47 | resolver = AnnotationBasedKeySpaceResolver.INSTANCE; 48 | } 49 | 50 | @Test // DATACMNS-525 51 | void shouldResolveKeySpaceDefaultValueCorrectly() { 52 | assertThat(resolver.resolveKeySpace(EntityWithDefaultKeySpace.class)).isEqualTo("daenerys"); 53 | } 54 | 55 | @Test // DATAKV-105 56 | void shouldReturnNullWhenNoKeySpaceFoundOnComposedPersistentAnnotation() { 57 | assertThat(resolver.resolveKeySpace(TypeWithInhteritedPersistentAnnotationNotHavingKeySpace.class)).isNull(); 58 | } 59 | 60 | @Test // DATAKV-105 61 | void shouldReturnNullWhenPersistentIsFoundOnNonComposedAnnotation() { 62 | assertThat(resolver.resolveKeySpace(TypeWithPersistentAnnotationNotHavingKeySpace.class)).isNull(); 63 | } 64 | 65 | @Test // DATAKV-105 66 | void shouldReturnNullWhenPersistentIsNotFound() { 67 | assertThat(resolver.resolveKeySpace(TypeWithoutKeySpace.class)).isNull(); 68 | } 69 | 70 | @Test // DATACMNS-525 71 | void shouldResolveInheritedKeySpaceCorrectly() { 72 | assertThat(resolver.resolveKeySpace(EntityWithInheritedKeySpace.class)).isEqualTo("viserys"); 73 | } 74 | 75 | @Test // DATACMNS-525 76 | void shouldResolveDirectKeySpaceAnnotationCorrectly() { 77 | assertThat(resolver.resolveKeySpace(TypeWithDirectKeySpaceAnnotation.class)).isEqualTo("rhaegar"); 78 | } 79 | 80 | @Test // DATAKV-129 81 | void shouldResolveKeySpaceUsingAliasForCorrectly() { 82 | assertThat(resolver.resolveKeySpace(EntityWithSetKeySpaceUsingAliasFor.class)).isEqualTo("viserys"); 83 | } 84 | 85 | @Test // DATAKV-129 86 | void shouldResolveKeySpaceUsingAliasForCorrectlyOnSubClass() { 87 | assertThat(resolver.resolveKeySpace(EntityWithInheritedKeySpaceUsingAliasFor.class)).isEqualTo("viserys"); 88 | } 89 | 90 | @PersistentAnnotationWithExplicitKeySpaceUsingAliasFor 91 | private static class EntityWithDefaultKeySpace { 92 | 93 | } 94 | 95 | @PersistentAnnotationWithExplicitKeySpaceUsingAliasFor(firstname = "viserys") 96 | static class EntityWithSetKeySpaceUsingAliasFor { 97 | 98 | } 99 | 100 | private static class EntityWithInheritedKeySpace extends EntityWithSetKeySpaceUsingAliasFor { 101 | 102 | } 103 | 104 | private static class EntityWithInheritedKeySpaceUsingAliasFor extends EntityWithSetKeySpaceUsingAliasFor { 105 | 106 | } 107 | 108 | @Persistent 109 | @Retention(RetentionPolicy.RUNTIME) 110 | @Target({ ElementType.TYPE }) 111 | @KeySpace 112 | static @interface PersistentAnnotationWithExplicitKeySpaceUsingAliasFor { 113 | 114 | @AliasFor(annotation = KeySpace.class, attribute = "value") 115 | String firstname() default "daenerys"; 116 | 117 | String lastname() default "targaryen"; 118 | 119 | } 120 | 121 | static class TypeWithoutKeySpace { 122 | 123 | String foo; 124 | 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /src/test/java/org/springframework/data/keyvalue/core/mapping/BasicKeyValuePersistentEntityUnitTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019-2025 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.data.keyvalue.core.mapping; 17 | 18 | import static org.assertj.core.api.Assertions.*; 19 | 20 | import java.util.Collections; 21 | import java.util.LinkedHashMap; 22 | import java.util.Map; 23 | 24 | import org.junit.jupiter.api.Test; 25 | import org.springframework.data.keyvalue.annotation.KeySpace; 26 | import org.springframework.data.keyvalue.core.mapping.context.KeyValueMappingContext; 27 | import org.springframework.data.spel.ExtensionAwareEvaluationContextProvider; 28 | import org.springframework.data.spel.spi.EvaluationContextExtension; 29 | import org.springframework.mock.env.MockEnvironment; 30 | 31 | /** 32 | * Unit tests for {@link BasicKeyValuePersistentEntity}. 33 | * 34 | * @author Mark Paluch 35 | */ 36 | class BasicKeyValuePersistentEntityUnitTests { 37 | 38 | private KeyValueMappingContext, ? extends KeyValuePersistentProperty> mappingContext = new KeyValueMappingContext<>(); 39 | 40 | @Test // DATAKV-268 41 | void shouldDeriveKeyspaceFromClassName() { 42 | 43 | assertThat(mappingContext.getPersistentEntity(KeyspaceEntity.class).getKeySpace()) 44 | .isEqualTo(KeyspaceEntity.class.getName()); 45 | } 46 | 47 | @Test // DATAKV-268, GH-613 48 | void shouldEvaluateKeyspaceExpression() { 49 | 50 | MockEnvironment environment = new MockEnvironment().withProperty("my.property", "foo"); 51 | mappingContext.setEnvironment(environment); 52 | 53 | KeyValuePersistentEntity persistentEntity = mappingContext.getPersistentEntity(ExpressionEntity.class); 54 | persistentEntity.setEvaluationContextProvider( 55 | new ExtensionAwareEvaluationContextProvider(Collections.singletonList(new SampleExtension()))); 56 | 57 | assertThat(persistentEntity.getKeySpace()).isEqualTo("some_foo"); 58 | } 59 | 60 | @Test // DATAKV-268 61 | void shouldEvaluateEntityWithoutKeyspace() { 62 | 63 | KeyValuePersistentEntity persistentEntity = mappingContext.getPersistentEntity(NoKeyspaceEntity.class); 64 | persistentEntity.setEvaluationContextProvider( 65 | new ExtensionAwareEvaluationContextProvider(Collections.singletonList(new SampleExtension()))); 66 | 67 | assertThat(persistentEntity.getKeySpace()).isEqualTo(NoKeyspaceEntity.class.getName()); 68 | } 69 | 70 | @Test // GH-461 71 | void shouldApplyKeySpaceResolver() { 72 | 73 | mappingContext.setKeySpaceResolver(new PrefixKeyspaceResolver("foo", ClassNameKeySpaceResolver.INSTANCE)); 74 | KeyValuePersistentEntity persistentEntity = mappingContext.getPersistentEntity(NoKeyspaceEntity.class); 75 | 76 | assertThat(persistentEntity.getKeySpace()).isEqualTo("foo" + NoKeyspaceEntity.class.getName()); 77 | } 78 | 79 | @Test // GH-461 80 | void shouldFallBackToDefaultsIfKeySpaceResolverReturnsNull() { 81 | 82 | mappingContext.setKeySpaceResolver(it -> null); 83 | KeyValuePersistentEntity persistentEntity = mappingContext.getPersistentEntity(NoKeyspaceEntity.class); 84 | 85 | assertThat(persistentEntity.getKeySpace()).isEqualTo(NoKeyspaceEntity.class.getName()); 86 | } 87 | 88 | @KeySpace("#{myProperty}_${my.property}") 89 | private static class ExpressionEntity {} 90 | 91 | @KeySpace 92 | private static class KeyspaceEntity {} 93 | 94 | private static class NoKeyspaceEntity {} 95 | 96 | static class SampleExtension implements EvaluationContextExtension { 97 | 98 | @Override 99 | public String getExtensionId() { 100 | return "sampleExtension"; 101 | } 102 | 103 | @Override 104 | public Map getProperties() { 105 | 106 | Map properties = new LinkedHashMap<>(); 107 | properties.put("myProperty", "some"); 108 | return properties; 109 | } 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /src/test/java/org/springframework/data/keyvalue/core/mapping/PrefixKeyspaceResolverUnitTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022-2025 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.data.keyvalue.core.mapping; 17 | 18 | import static org.assertj.core.api.Assertions.*; 19 | 20 | import org.junit.jupiter.api.Test; 21 | 22 | /** 23 | * Unit tests for {@link PrefixKeyspaceResolver}. 24 | * 25 | * @author Mark Paluch 26 | */ 27 | class PrefixKeyspaceResolverUnitTests { 28 | 29 | @Test // gh-461 30 | void shouldApplyPrefix() { 31 | 32 | var resolver = new PrefixKeyspaceResolver("foo", Class::getSimpleName); 33 | 34 | assertThat(resolver.resolveKeySpace(Object.class)).isEqualTo("fooObject"); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/test/java/org/springframework/data/keyvalue/core/mapping/context/KeyValueMappingContextUnitTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021-2025 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.data.keyvalue.core.mapping.context; 17 | 18 | import static org.assertj.core.api.Assertions.*; 19 | 20 | import java.math.BigDecimal; 21 | import java.math.BigInteger; 22 | import java.util.UUID; 23 | 24 | import org.junit.jupiter.api.Test; 25 | 26 | import org.springframework.data.keyvalue.core.mapping.KeyValuePersistentEntity; 27 | import org.springframework.data.keyvalue.core.mapping.KeyValuePersistentProperty; 28 | 29 | /** 30 | * Unit test for {@link KeyValueMappingContext}. 31 | * 32 | * @author Mark Paluch 33 | */ 34 | class KeyValueMappingContextUnitTests

> { 35 | 36 | @Test 37 | void shouldNotCreateEntitiesForJavaStandardTypes() { 38 | 39 | KeyValueMappingContext, P> mappingContext = new KeyValueMappingContext<>(); 40 | 41 | assertThat(mappingContext.getPersistentEntity(BigInteger.class)).isNull(); 42 | assertThat(mappingContext.getPersistentEntity(BigDecimal.class)).isNull(); 43 | assertThat(mappingContext.getPersistentEntity(UUID.class)).isNull(); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/test/java/org/springframework/data/keyvalue/repository/MapRepositoriesRegistrarUnitTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024-2025 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.data.keyvalue.repository; 17 | 18 | import static org.assertj.core.api.Assertions.assertThat; 19 | 20 | import java.util.Arrays; 21 | import java.util.stream.Stream; 22 | 23 | import org.junit.jupiter.api.BeforeEach; 24 | import org.junit.jupiter.params.ParameterizedTest; 25 | import org.junit.jupiter.params.provider.Arguments; 26 | import org.junit.jupiter.params.provider.MethodSource; 27 | import org.springframework.beans.factory.config.BeanDefinition; 28 | import org.springframework.beans.factory.support.BeanDefinitionRegistry; 29 | import org.springframework.beans.factory.support.DefaultListableBeanFactory; 30 | import org.springframework.context.annotation.AnnotationBeanNameGenerator; 31 | import org.springframework.core.env.StandardEnvironment; 32 | import org.springframework.core.io.DefaultResourceLoader; 33 | import org.springframework.core.type.AnnotationMetadata; 34 | import org.springframework.data.map.repository.config.EnableMapRepositories; 35 | import org.springframework.data.map.repository.config.MapRepositoriesRegistrar; 36 | import org.springframework.data.repository.CrudRepository; 37 | 38 | /** 39 | * @author Christoph Strobl 40 | */ 41 | class MapRepositoriesRegistrarUnitTests { 42 | 43 | private BeanDefinitionRegistry registry; 44 | 45 | @BeforeEach 46 | void setUp() { 47 | registry = new DefaultListableBeanFactory(); 48 | } 49 | 50 | @ParameterizedTest // GH-499, GH-3440 51 | @MethodSource(value = { "args" }) 52 | void configuresRepositoriesCorrectly(AnnotationMetadata metadata, String[] beanNames) { 53 | 54 | MapRepositoriesRegistrar registrar = new MapRepositoriesRegistrar(); 55 | registrar.setResourceLoader(new DefaultResourceLoader()); 56 | registrar.setEnvironment(new StandardEnvironment()); 57 | registrar.registerBeanDefinitions(metadata, registry); 58 | 59 | Iterable names = Arrays.asList(registry.getBeanDefinitionNames()); 60 | assertThat(names).contains(beanNames); 61 | } 62 | 63 | static Stream args() { 64 | return Stream.of( 65 | Arguments.of(AnnotationMetadata.introspect(Config.class), 66 | new String[] { "mapRepositoriesRegistrarUnitTests.PersonRepository" }), 67 | Arguments.of(AnnotationMetadata.introspect(ConfigWithBeanNameGenerator.class), 68 | new String[] { "mapRepositoriesRegistrarUnitTests.PersonREPO" })); 69 | } 70 | 71 | @EnableMapRepositories(basePackageClasses = PersonRepository.class, considerNestedRepositories = true) 72 | private class Config { 73 | 74 | } 75 | 76 | @EnableMapRepositories(basePackageClasses = PersonRepository.class, nameGenerator = MyBeanNameGenerator.class, 77 | considerNestedRepositories = true) 78 | private class ConfigWithBeanNameGenerator { 79 | 80 | } 81 | 82 | static class MyBeanNameGenerator extends AnnotationBeanNameGenerator { 83 | 84 | @Override 85 | public String generateBeanName(BeanDefinition definition, BeanDefinitionRegistry registry) { 86 | return super.generateBeanName(definition, registry).replaceAll("Repository", "REPO"); 87 | } 88 | } 89 | 90 | interface PersonRepository extends CrudRepository { 91 | 92 | } 93 | 94 | static class Person {} 95 | } 96 | -------------------------------------------------------------------------------- /src/test/java/org/springframework/data/keyvalue/repository/query/CachingKeyValuePartTreeQueryUnitTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016-2025 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.data.keyvalue.repository.query; 17 | 18 | import static org.assertj.core.api.Assertions.*; 19 | import static org.mockito.ArgumentMatchers.*; 20 | import static org.mockito.Mockito.*; 21 | 22 | import java.lang.reflect.Method; 23 | import java.util.List; 24 | 25 | import org.junit.jupiter.api.BeforeEach; 26 | import org.junit.jupiter.api.Test; 27 | import org.junit.jupiter.api.extension.ExtendWith; 28 | import org.mockito.Mock; 29 | import org.mockito.junit.jupiter.MockitoExtension; 30 | import org.springframework.data.keyvalue.Person; 31 | import org.springframework.data.keyvalue.core.KeyValueOperations; 32 | import org.springframework.data.keyvalue.core.SpelCriteria; 33 | import org.springframework.data.projection.ProjectionFactory; 34 | import org.springframework.data.repository.core.RepositoryMetadata; 35 | import org.springframework.data.repository.query.QueryMethod; 36 | import org.springframework.data.repository.query.ValueExpressionDelegate; 37 | import org.springframework.data.util.TypeInformation; 38 | 39 | /** 40 | * Unit tests for {@link CachingKeyValuePartTreeQuery}. 41 | * 42 | * @author Mark Paluch 43 | */ 44 | @ExtendWith(MockitoExtension.class) 45 | class CachingKeyValuePartTreeQueryUnitTests { 46 | 47 | @Mock KeyValueOperations kvOpsMock; 48 | @Mock RepositoryMetadata metadataMock; 49 | @Mock ProjectionFactory projectionFactoryMock; 50 | 51 | @BeforeEach 52 | @SuppressWarnings({ "unchecked", "rawtypes" }) 53 | void setUp() throws Exception { 54 | 55 | when(metadataMock.getDomainType()).thenReturn((Class) Person.class); 56 | when(metadataMock.getDomainTypeInformation()).thenReturn((TypeInformation) TypeInformation.of(Person.class)); 57 | when(metadataMock.getReturnedDomainClass(any(Method.class))).thenReturn((Class) Person.class); 58 | when(metadataMock.getReturnType(any(Method.class))).thenReturn(TypeInformation.of((Class) List.class)); 59 | } 60 | 61 | @Test // DATAKV-137 62 | void cachedSpelExpressionShouldBeReusedWithNewContext() throws NoSuchMethodException, SecurityException { 63 | 64 | QueryMethod qm = new QueryMethod(Repo.class.getMethod("findByFirstname", String.class), metadataMock, 65 | projectionFactoryMock); 66 | 67 | KeyValuePartTreeQuery query = new CachingKeyValuePartTreeQuery(qm, ValueExpressionDelegate.create(), kvOpsMock, 68 | SpelQueryCreator.class); 69 | 70 | Object[] args = new Object[] { "foo" }; 71 | 72 | SpelCriteria first = (SpelCriteria) query.prepareQuery(args).getCriteria(); 73 | SpelCriteria second = (SpelCriteria) query.prepareQuery(args).getCriteria(); 74 | 75 | assertThat(first.getExpression()).isSameAs(second.getExpression()); 76 | assertThat(first.getContext()).isNotSameAs(second.getContext()); 77 | } 78 | 79 | static interface Repo { 80 | 81 | List findByFirstname(String firstname); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/test/java/org/springframework/data/keyvalue/repository/query/PredicateQueryCreatorUnitTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024-2025 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.data.keyvalue.repository.query; 17 | 18 | import static org.assertj.core.api.Assertions.*; 19 | 20 | import java.util.function.Predicate; 21 | 22 | import org.junit.jupiter.api.Test; 23 | import org.springframework.dao.InvalidDataAccessApiUsageException; 24 | import org.springframework.data.keyvalue.core.query.KeyValueQuery; 25 | import org.springframework.data.repository.query.ParametersParameterAccessor; 26 | import org.springframework.data.repository.query.parser.PartTree; 27 | 28 | /** 29 | * @author Christoph Strobl 30 | */ 31 | class PredicateQueryCreatorUnitTests extends AbstractQueryCreatorTestBase> { 32 | 33 | @Override 34 | @Test // DATACMNS-525 35 | void startsWithIgnoreCaseReturnsTrueWhenMatching() { 36 | assertThat(evaluate("findByFirstnameIgnoreCase", "RobB").against(ROBB)).isTrue(); 37 | } 38 | 39 | @Override 40 | protected PredicateQueryCreator queryCreator(PartTree partTree, ParametersParameterAccessor accessor) { 41 | return new PredicateQueryCreator(partTree, accessor); 42 | } 43 | 44 | @Override 45 | protected KeyValueQuery> finalizeQuery(KeyValueQuery> query, Object... args) { 46 | return query; 47 | } 48 | 49 | @Override 50 | protected Evaluation createEvaluation(Predicate predicate) { 51 | return new PredicateEvaluation(predicate); 52 | } 53 | 54 | static class PredicateEvaluation implements Evaluation { 55 | 56 | private final Predicate expression; 57 | private Object candidate; 58 | 59 | PredicateEvaluation(Predicate expression) { 60 | this.expression = expression; 61 | } 62 | 63 | public Boolean against(Object candidate) { 64 | this.candidate = candidate; 65 | return evaluate(); 66 | } 67 | 68 | public boolean evaluate() { 69 | return expression.test(candidate); 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/test/java/org/springframework/data/keyvalue/repository/query/SpelQueryCreatorUnitTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014-2025 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.data.keyvalue.repository.query; 17 | 18 | import org.springframework.data.keyvalue.core.query.KeyValueQuery; 19 | import org.springframework.data.repository.query.ParametersParameterAccessor; 20 | import org.springframework.data.repository.query.parser.PartTree; 21 | import org.springframework.expression.spel.standard.SpelExpression; 22 | import org.springframework.expression.spel.support.SimpleEvaluationContext; 23 | 24 | /** 25 | * @author Christoph Strobl 26 | * @author Mark Paluch 27 | */ 28 | public class SpelQueryCreatorUnitTests extends AbstractQueryCreatorTestBase { 29 | 30 | @Override 31 | protected SpelQueryCreator queryCreator(PartTree partTree, ParametersParameterAccessor accessor) { 32 | return new SpelQueryCreator(partTree, accessor); 33 | } 34 | 35 | @Override 36 | protected KeyValueQuery finalizeQuery(KeyValueQuery query, Object... args) { 37 | 38 | query.getCriteria().setEvaluationContext( 39 | SimpleEvaluationContext.forReadOnlyDataBinding().withRootObject(args).withInstanceMethods().build()); 40 | return query; 41 | } 42 | 43 | @Override 44 | protected Evaluation createEvaluation(SpelExpression spelExpression) { 45 | return new SpelEvaluation(spelExpression); 46 | } 47 | 48 | static class SpelEvaluation implements Evaluation { 49 | 50 | SpelExpression expression; 51 | Object candidate; 52 | 53 | SpelEvaluation(SpelExpression expression) { 54 | this.expression = expression; 55 | } 56 | 57 | public Boolean against(Object candidate) { 58 | this.candidate = candidate; 59 | return evaluate(); 60 | } 61 | 62 | public boolean evaluate() { 63 | expression.getEvaluationContext().setVariable("it", candidate); 64 | return expression.getValue(Boolean.class); 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/test/java/org/springframework/data/keyvalue/repository/support/KeyValueQuerydslUtilsUnitTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014-2025 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.data.keyvalue.repository.support; 17 | 18 | import static org.assertj.core.api.Assertions.*; 19 | import static org.springframework.data.keyvalue.repository.support.KeyValueQuerydslUtils.*; 20 | 21 | import org.junit.jupiter.api.BeforeEach; 22 | import org.junit.jupiter.api.Test; 23 | import org.springframework.data.domain.Sort; 24 | import org.springframework.data.domain.Sort.Direction; 25 | import org.springframework.data.domain.Sort.NullHandling; 26 | import org.springframework.data.keyvalue.Person; 27 | import org.springframework.data.keyvalue.QPerson; 28 | import org.springframework.data.querydsl.SimpleEntityPathResolver; 29 | 30 | import com.querydsl.core.types.EntityPath; 31 | import com.querydsl.core.types.OrderSpecifier; 32 | import com.querydsl.core.types.dsl.PathBuilder; 33 | 34 | /** 35 | * Unit tests for {@link KeyValueQuerydslUtils}. 36 | * 37 | * @author Christoph Strobl 38 | * @author Thomas Darimont 39 | * @author Oliver Gierke 40 | * @author Mark Paluch 41 | */ 42 | class KeyValueQuerydslUtilsUnitTests { 43 | 44 | private EntityPath path; 45 | private PathBuilder builder; 46 | 47 | @BeforeEach 48 | void setUp() { 49 | 50 | this.path = SimpleEntityPathResolver.INSTANCE.createPath(Person.class); 51 | this.builder = new PathBuilder<>(path.getType(), path.getMetadata()); 52 | } 53 | 54 | @Test // DATACMNS-525 55 | void toOrderSpecifierThrowsExceptioOnNullPathBuilder() { 56 | assertThatIllegalArgumentException().isThrownBy(() -> toOrderSpecifier(Sort.by("firstname"), null)); 57 | } 58 | 59 | @Test // DATACMNS-525, DATAKV-197 60 | void toOrderSpecifierReturnsEmptyArrayWhenSortIsUnsorted() { 61 | assertThat(toOrderSpecifier(Sort.unsorted(), builder)).hasSize(0); 62 | } 63 | 64 | @Test // DATACMNS-525 65 | void toOrderSpecifierConvertsSimpleAscSortCorrectly() { 66 | 67 | Sort sort = Sort.by(Direction.ASC, "firstname"); 68 | 69 | OrderSpecifier[] specifiers = toOrderSpecifier(sort, builder); 70 | 71 | assertThat(specifiers).containsExactly(QPerson.person.firstname.asc()); 72 | } 73 | 74 | @Test // DATACMNS-525 75 | void toOrderSpecifierConvertsSimpleDescSortCorrectly() { 76 | 77 | Sort sort = Sort.by(Direction.DESC, "firstname"); 78 | 79 | OrderSpecifier[] specifiers = toOrderSpecifier(sort, builder); 80 | 81 | assertThat(specifiers).containsExactly(QPerson.person.firstname.desc()); 82 | } 83 | 84 | @Test // DATACMNS-525 85 | void toOrderSpecifierConvertsSortCorrectlyAndRetainsArgumentOrder() { 86 | 87 | Sort sort = Sort.by(Direction.DESC, "firstname").and(Sort.by(Direction.ASC, "age")); 88 | 89 | OrderSpecifier[] specifiers = toOrderSpecifier(sort, builder); 90 | 91 | assertThat(specifiers).containsExactly(QPerson.person.firstname.desc(), QPerson.person.age.asc()); 92 | } 93 | 94 | @Test // DATACMNS-525 95 | void toOrderSpecifierConvertsSortWithNullHandlingCorrectly() { 96 | 97 | Sort sort = Sort.by(new Sort.Order(Direction.DESC, "firstname", NullHandling.NULLS_LAST)); 98 | 99 | OrderSpecifier[] specifiers = toOrderSpecifier(sort, builder); 100 | 101 | assertThat(specifiers).containsExactly(QPerson.person.firstname.desc().nullsLast()); 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/test/java/org/springframework/data/keyvalue/repository/support/KeyValueRepositoryFactoryBeanUnitTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016-2025 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.data.keyvalue.repository.support; 17 | 18 | import static org.assertj.core.api.Assertions.*; 19 | import static org.mockito.Mockito.*; 20 | 21 | import org.junit.jupiter.api.BeforeEach; 22 | import org.junit.jupiter.api.Test; 23 | 24 | import org.springframework.data.keyvalue.core.KeyValueOperations; 25 | import org.springframework.data.keyvalue.repository.query.KeyValuePartTreeQuery; 26 | import org.springframework.data.repository.Repository; 27 | import org.springframework.data.repository.query.RepositoryQuery; 28 | import org.springframework.data.repository.query.parser.AbstractQueryCreator; 29 | 30 | /** 31 | * Unit tests for {@link KeyValueRepositoryFactoryBean}. 32 | * 33 | * @author Oliver Gierke 34 | * @author Mark Paluch 35 | */ 36 | class KeyValueRepositoryFactoryBeanUnitTests { 37 | 38 | private KeyValueRepositoryFactoryBean factoryBean; 39 | 40 | @BeforeEach 41 | void setUp() { 42 | this.factoryBean = new KeyValueRepositoryFactoryBean, Object, Object>( 43 | SampleRepository.class); 44 | } 45 | 46 | @Test // DATAKV-123 47 | void rejectsNullKeyValueOperations() { 48 | assertThatIllegalArgumentException().isThrownBy(() -> factoryBean.setKeyValueOperations(null)); 49 | } 50 | 51 | @Test // DATAKV-123 52 | void rejectsNullQueryCreator() { 53 | assertThatIllegalArgumentException().isThrownBy(() -> factoryBean.setQueryCreator(null)); 54 | } 55 | 56 | @Test // DATAKV-123 57 | void rejectsUninitializedInstance() { 58 | assertThatIllegalArgumentException().isThrownBy(() -> factoryBean.afterPropertiesSet()); 59 | } 60 | 61 | @SuppressWarnings("unchecked") 62 | @Test // DATAKV-123 63 | void rejectsInstanceWithoutKeyValueOperations() { 64 | 65 | Class> creatorType = (Class>) mock( 66 | AbstractQueryCreator.class).getClass(); 67 | 68 | factoryBean.setQueryCreator(creatorType); 69 | 70 | assertThatIllegalArgumentException().isThrownBy(() -> factoryBean.afterPropertiesSet()); 71 | } 72 | 73 | @Test // DATAKV-123 74 | void rejectsInstanceWithoutQueryCreator() { 75 | 76 | factoryBean.setKeyValueOperations(mock(KeyValueOperations.class)); 77 | assertThatIllegalArgumentException().isThrownBy(() -> factoryBean.afterPropertiesSet()); 78 | } 79 | 80 | @Test // DATAKV-123 81 | @SuppressWarnings("unchecked") 82 | void createsRepositoryFactory() { 83 | 84 | Class> creatorType = (Class>) mock( 85 | AbstractQueryCreator.class).getClass(); 86 | Class queryType = mock(KeyValuePartTreeQuery.class).getClass(); 87 | 88 | factoryBean.setQueryCreator(creatorType); 89 | factoryBean.setKeyValueOperations(mock(KeyValueOperations.class)); 90 | factoryBean.setQueryType(queryType); 91 | 92 | assertThat(factoryBean.createRepositoryFactory()).isNotNull(); 93 | } 94 | 95 | @Test // DATAKV-112 96 | void rejectsNullQueryType() { 97 | assertThatIllegalArgumentException().isThrownBy(() -> factoryBean.setQueryType(null)); 98 | } 99 | 100 | interface SampleRepository extends Repository {} 101 | } 102 | -------------------------------------------------------------------------------- /src/test/java/org/springframework/data/map/CachingQuerySimpleKeyValueRepositoryUnitTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016-2025 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.data.map; 17 | 18 | import org.junit.jupiter.api.Disabled; 19 | import org.springframework.data.keyvalue.core.KeyValueOperations; 20 | import org.springframework.data.keyvalue.repository.query.CachingKeyValuePartTreeQuery; 21 | import org.springframework.data.keyvalue.repository.query.PredicateQueryCreator; 22 | import org.springframework.data.keyvalue.repository.query.SpelQueryCreator; 23 | import org.springframework.data.keyvalue.repository.support.KeyValueRepositoryFactory; 24 | import org.springframework.data.keyvalue.repository.support.SimpleKeyValueRepository; 25 | 26 | /** 27 | * Unit tests for {@link SimpleKeyValueRepository} using {@link CachingKeyValuePartTreeQuery} and 28 | * {@link PredicateQueryCreator}. 29 | * 30 | * @author Mark Paluch 31 | * @author Christoph Strobl 32 | */ 33 | @Disabled 34 | public class CachingQuerySimpleKeyValueRepositoryUnitTests extends SimpleKeyValueRepositoryUnitTests { 35 | 36 | @Override 37 | protected KeyValueRepositoryFactory createKeyValueRepositoryFactory(KeyValueOperations operations) { 38 | return new KeyValueRepositoryFactory(operations, PredicateQueryCreator.class, CachingKeyValuePartTreeQuery.class); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/test/java/org/springframework/data/map/SimpleKeyValueRepositoryUnitTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014-2025 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.data.map; 17 | 18 | import org.springframework.data.keyvalue.repository.support.KeyValueRepositoryFactory; 19 | import org.springframework.data.keyvalue.repository.support.SimpleKeyValueRepository; 20 | import org.springframework.data.map.AbstractRepositoryUnitTests.PersonRepository; 21 | 22 | /** 23 | * Unit tests for {@link SimpleKeyValueRepository}. 24 | * 25 | * @author Christoph Strobl 26 | * @author Oliver Gierke 27 | */ 28 | public class SimpleKeyValueRepositoryUnitTests extends AbstractRepositoryUnitTests { 29 | 30 | protected PersonRepository getRepository(KeyValueRepositoryFactory factory) { 31 | return factory.getRepository(PersonRepository.class); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/test/java/org/springframework/data/map/repository/config/MapRepositoryRegistrarWithFullDefaultingIntegrationTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014-2025 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.data.map.repository.config; 17 | 18 | import static org.assertj.core.api.Assertions.*; 19 | 20 | import java.util.List; 21 | 22 | import org.junit.jupiter.api.Test; 23 | import org.junit.jupiter.api.extension.ExtendWith; 24 | import org.springframework.beans.factory.annotation.Autowired; 25 | import org.springframework.context.annotation.Configuration; 26 | import org.springframework.data.annotation.Id; 27 | import org.springframework.data.repository.CrudRepository; 28 | import org.springframework.test.context.ContextConfiguration; 29 | import org.springframework.test.context.junit.jupiter.SpringExtension; 30 | 31 | /** 32 | * Integration tests for {@link MapRepositoriesRegistrar} with complete defaulting. 33 | * 34 | * @author Christoph Strobl 35 | * @author Mark Paluch 36 | */ 37 | @ExtendWith(SpringExtension.class) 38 | @ContextConfiguration 39 | public class MapRepositoryRegistrarWithFullDefaultingIntegrationTests { 40 | 41 | @Configuration 42 | @EnableMapRepositories(considerNestedRepositories = true) 43 | static class Config { 44 | 45 | } 46 | 47 | @Autowired PersonRepository repo; 48 | 49 | @Test // DATAKV-86 50 | void shouldEnableMapRepositoryCorrectly() { 51 | assertThat(repo).isNotNull(); 52 | } 53 | 54 | static class Person { 55 | 56 | @Id String id; 57 | String firstname; 58 | 59 | public String getId() { 60 | return this.id; 61 | } 62 | 63 | public String getFirstname() { 64 | return this.firstname; 65 | } 66 | 67 | public void setId(String id) { 68 | this.id = id; 69 | } 70 | 71 | public void setFirstname(String firstname) { 72 | this.firstname = firstname; 73 | } 74 | } 75 | 76 | interface PersonRepository extends CrudRepository { 77 | 78 | List findByFirstname(String firstname); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/test/java/org/springframework/data/map/repository/config/MapRepositoryRegistrarWithTemplateDefinitionIntegrationTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014-2025 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.data.map.repository.config; 17 | 18 | import static org.assertj.core.api.Assertions.*; 19 | 20 | import java.util.List; 21 | 22 | import org.junit.jupiter.api.Test; 23 | import org.junit.jupiter.api.extension.ExtendWith; 24 | import org.springframework.beans.factory.annotation.Autowired; 25 | import org.springframework.context.annotation.Bean; 26 | import org.springframework.context.annotation.Configuration; 27 | import org.springframework.data.annotation.Id; 28 | import org.springframework.data.keyvalue.core.KeyValueOperations; 29 | import org.springframework.data.keyvalue.core.KeyValueTemplate; 30 | import org.springframework.data.map.MapKeyValueAdapter; 31 | import org.springframework.data.repository.CrudRepository; 32 | import org.springframework.test.context.ContextConfiguration; 33 | import org.springframework.test.context.junit.jupiter.SpringExtension; 34 | 35 | /** 36 | * Integration tests for {@link MapRepositoriesRegistrar} with complete defaulting. 37 | * 38 | * @author Christoph Strobl 39 | * @author Mark Paluch 40 | */ 41 | @ExtendWith(SpringExtension.class) 42 | @ContextConfiguration 43 | public class MapRepositoryRegistrarWithTemplateDefinitionIntegrationTests { 44 | 45 | @Configuration 46 | @EnableMapRepositories(considerNestedRepositories = true) 47 | static class Config { 48 | 49 | @Bean 50 | public KeyValueOperations keyValueTemplate() { 51 | return new KeyValueTemplate(new MapKeyValueAdapter()); 52 | } 53 | } 54 | 55 | @Autowired PersonRepository repo; 56 | 57 | @Test // DATACMNS-525 58 | void shouldEnableMapRepositoryCorrectly() { 59 | assertThat(repo).isNotNull(); 60 | } 61 | 62 | static class Person { 63 | 64 | @Id String id; 65 | String firstname; 66 | 67 | public String getId() { 68 | return this.id; 69 | } 70 | 71 | public String getFirstname() { 72 | return this.firstname; 73 | } 74 | 75 | public void setId(String id) { 76 | this.id = id; 77 | } 78 | 79 | public void setFirstname(String firstname) { 80 | this.firstname = firstname; 81 | } 82 | } 83 | 84 | interface PersonRepository extends CrudRepository { 85 | 86 | List findByFirstname(String firstname); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/test/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | %d %5p %40.40c:%4L - %m%n 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | --------------------------------------------------------------------------------