diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
index 6c5d559a8a..e8f632af23 100644
--- a/.github/PULL_REQUEST_TEMPLATE.md
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -6,7 +6,6 @@ Make sure that:
-->
- [ ] You have read the [Spring Data contribution guidelines](https://github.com/spring-projects/spring-data-build/blob/master/CONTRIBUTING.adoc).
-- [ ] There is a ticket in the bug tracker for the project in our [JIRA](https://jira.spring.io/browse/DATAMONGO).
- [ ] 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.
- [ ] You submit test cases (unit or integration tests) that back your changes.
- [ ] 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).
diff --git a/.github/dco.yml b/.github/dco.yml
new file mode 100644
index 0000000000..0c4b142e9a
--- /dev/null
+++ b/.github/dco.yml
@@ -0,0 +1,2 @@
+require:
+ members: false
diff --git a/.github/workflows/project.yml b/.github/workflows/project.yml
new file mode 100644
index 0000000000..a5f764579a
--- /dev/null
+++ b/.github/workflows/project.yml
@@ -0,0 +1,40 @@
+# GitHub Actions to automate GitHub issues for Spring Data Project Management
+
+name: Spring Data GitHub Issues
+
+on:
+ issues:
+ types: [opened, edited, reopened]
+ issue_comment:
+ types: [created]
+ pull_request_target:
+ types: [opened, edited, reopened]
+
+jobs:
+ Inbox:
+ runs-on: ubuntu-latest
+ 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 ')
+ steps:
+ - name: Create or Update Issue Card
+ uses: actions/add-to-project@v1.0.2
+ with:
+ project-url: https://github.com/orgs/spring-projects/projects/25
+ github-token: ${{ secrets.GH_ISSUES_TOKEN_SPRING_DATA }}
+ Pull-Request:
+ runs-on: ubuntu-latest
+ if: github.repository_owner == 'spring-projects' && (github.event.action == 'opened' || github.event.action == 'reopened') && github.event.pull_request != null
+ steps:
+ - name: Create or Update Pull Request Card
+ uses: actions/add-to-project@v1.0.2
+ with:
+ project-url: https://github.com/orgs/spring-projects/projects/25
+ github-token: ${{ secrets.GH_ISSUES_TOKEN_SPRING_DATA }}
+ Feedback-Provided:
+ runs-on: ubuntu-latest
+ 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')
+ steps:
+ - name: Update Project Card
+ uses: actions/add-to-project@v1.0.2
+ with:
+ project-url: https://github.com/orgs/spring-projects/projects/25
+ github-token: ${{ secrets.GH_ISSUES_TOKEN_SPRING_DATA }}
diff --git a/.gitignore b/.gitignore
index be372b6209..27b7a78896 100644
--- a/.gitignore
+++ b/.gitignore
@@ -15,4 +15,9 @@ src/ant/.ant-targets-upload-dist.xml
atlassian-ide-plugin.xml
/.gradle/
/.idea/
-*.graphml
\ No newline at end of file
+*.graphml
+build/
+node_modules
+node
+package-lock.json
+.mvn/.develocity
diff --git a/.mvn/extensions.xml b/.mvn/extensions.xml
new file mode 100644
index 0000000000..e0857eaa25
--- /dev/null
+++ b/.mvn/extensions.xml
@@ -0,0 +1,8 @@
+
+
+
+ io.spring.develocity.conventions
+ develocity-conventions-maven-extension
+ 0.0.22
+
+
diff --git a/.mvn/jvm.config b/.mvn/jvm.config
new file mode 100644
index 0000000000..e27f6e8f5e
--- /dev/null
+++ b/.mvn/jvm.config
@@ -0,0 +1,14 @@
+--add-exports jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED
+--add-exports jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED
+--add-exports jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED
+--add-exports jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED
+--add-exports jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED
+--add-exports jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED
+--add-exports jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED
+--add-exports jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED
+--add-opens jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED
+--add-opens jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED
+--add-opens=java.base/java.util=ALL-UNNAMED
+--add-opens=java.base/java.lang.reflect=ALL-UNNAMED
+--add-opens=java.base/java.text=ALL-UNNAMED
+--add-opens=java.desktop/java.awt.font=ALL-UNNAMED
diff --git a/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties
index 00d32aab1d..5f3193b363 100755
--- a/.mvn/wrapper/maven-wrapper.properties
+++ b/.mvn/wrapper/maven-wrapper.properties
@@ -1 +1,2 @@
-distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.5.4/apache-maven-3.5.4-bin.zip
\ No newline at end of file
+#Thu Nov 07 09:47:19 CET 2024
+distributionUrl=https\://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.9/apache-maven-3.9.9-bin.zip
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index a6382b7217..0000000000
--- a/.travis.yml
+++ /dev/null
@@ -1,38 +0,0 @@
-language: java
-
-jdk:
- - oraclejdk8
-
-before_install:
- - mkdir -p downloads
- - mkdir -p var/db var/log
- - if [[ ! -d downloads/mongodb-linux-x86_64-ubuntu1604-${MONGO_VERSION} ]] ; then cd downloads && wget https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-ubuntu1604-${MONGO_VERSION}.tgz && tar xzf mongodb-linux-x86_64-ubuntu1604-${MONGO_VERSION}.tgz && cd ..; fi
- - downloads/mongodb-linux-x86_64-ubuntu1604-${MONGO_VERSION}/bin/mongod --version
- - downloads/mongodb-linux-x86_64-ubuntu1604-${MONGO_VERSION}/bin/mongod --dbpath var/db --replSet rs0 --fork --logpath var/log/mongod.log
- - sleep 10
- - |-
- downloads/mongodb-linux-x86_64-ubuntu1604-${MONGO_VERSION}/bin/mongo --eval "rs.initiate({_id: 'rs0', members:[{_id: 0, host: '127.0.0.1:27017'}]});"
- sleep 15
-
-env:
- matrix:
- - MONGO_VERSION=4.1.10
- - MONGO_VERSION=4.0.4
- - MONGO_VERSION=3.6.12
- - MONGO_VERSION=3.4.20
- global:
- - PROFILE=ci
-
-addons:
- apt:
- packages:
- - oracle-java8-installer
-
-sudo: false
-
-cache:
- directories:
- - $HOME/.m2
- - downloads
-
-script: "mvn clean dependency:list test -P${PROFILE} -Dsort"
diff --git a/CI.adoc b/CI.adoc
index c6cb467f2b..057100a955 100644
--- a/CI.adoc
+++ b/CI.adoc
@@ -1,6 +1,6 @@
= Continuous Integration
-image:https://jenkins.spring.io/buildStatus/icon?job=spring-data-mongodb%2Fmaster&subject=Moore%20(master)[link=https://jenkins.spring.io/view/SpringData/job/spring-data-mongodb/]
+image:https://jenkins.spring.io/buildStatus/icon?job=spring-data-mongodb%2Fmain&subject=Moore%20(main)[link=https://jenkins.spring.io/view/SpringData/job/spring-data-mongodb/]
image:https://jenkins.spring.io/buildStatus/icon?job=spring-data-mongodb%2F2.1.x&subject=Lovelace%20(2.1.x)[link=https://jenkins.spring.io/view/SpringData/job/spring-data-mongodb/]
image:https://jenkins.spring.io/buildStatus/icon?job=spring-data-mongodb%2F1.10.x&subject=Ingalls%20(1.10.x)[link=https://jenkins.spring.io/view/SpringData/job/spring-data-mongodb/]
@@ -16,7 +16,7 @@ All of these use cases are great reasons to essentially run what the CI server d
IMPORTANT: To do this you must have Docker installed on your machine.
-1. `docker run -it --mount type=bind,source="$(pwd)",target=/spring-data-mongodb-github springci/spring-data-openjdk8-with-mongodb-4.0:latest /bin/bash`
+1. `docker run -it --mount type=bind,source="$(pwd)",target=/spring-data-mongodb-github springci/spring-data-openjdk17-with-mongodb-5.0.3:latest /bin/bash`
+
This will launch the Docker image and mount your source code at `spring-data-mongodb-github`.
+
diff --git a/CODE_OF_CONDUCT.adoc b/CODE_OF_CONDUCT.adoc
deleted file mode 100644
index 33ae7bc9f1..0000000000
--- a/CODE_OF_CONDUCT.adoc
+++ /dev/null
@@ -1,27 +0,0 @@
-= Contributor Code of Conduct
-
-As contributors and maintainers of this project, and in the interest of fostering an open and welcoming community, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities.
-
-We are committed to making participation in this project a harassment-free experience for everyone, regardless of level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, ethnicity, age, religion, or nationality.
-
-Examples of unacceptable behavior by participants include:
-
-* The use of sexualized language or imagery
-* Personal attacks
-* Trolling or insulting/derogatory comments
-* Public or private harassment
-* Publishing other's private information, such as physical or electronic addresses,
- without explicit permission
-* Other unethical or unprofessional conduct
-
-Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
-
-By adopting this Code of Conduct, project maintainers commit themselves to fairly and consistently applying these principles to every aspect of managing this project. Project maintainers who do not follow or enforce the Code of Conduct may be permanently removed from the project team.
-
-This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community.
-
-Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting a project maintainer at spring-code-of-conduct@pivotal.io.
-All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances.
-Maintainers are obligated to maintain confidentiality with regard to the reporter of an incident.
-
-This Code of Conduct is adapted from the https://contributor-covenant.org[Contributor Covenant], version 1.3.0, available at https://contributor-covenant.org/version/1/3/0/[contributor-covenant.org/version/1/3/0/].
\ No newline at end of file
diff --git a/CONTRIBUTING.adoc b/CONTRIBUTING.adoc
index f007591467..740e8bd0bb 100644
--- a/CONTRIBUTING.adoc
+++ b/CONTRIBUTING.adoc
@@ -1,3 +1,3 @@
= Spring Data contribution guidelines
-You find the contribution guidelines for Spring Data projects https://github.com/spring-projects/spring-data-build/blob/master/CONTRIBUTING.adoc[here].
+You find the contribution guidelines for Spring Data projects https://github.com/spring-projects/spring-data-build/blob/main/CONTRIBUTING.adoc[here].
diff --git a/Jenkinsfile b/Jenkinsfile
index 005a15456b..ce2f272334 100644
--- a/Jenkinsfile
+++ b/Jenkinsfile
@@ -1,9 +1,15 @@
+def p = [:]
+node {
+ checkout scm
+ p = readProperties interpolate: true, file: 'ci/pipeline.properties'
+}
+
pipeline {
agent none
triggers {
pollSCM 'H/10 * * * *'
- upstream(upstreamProjects: "spring-data-commons/master", threshold: hudson.model.Result.SUCCESS)
+ upstream(upstreamProjects: "spring-data-commons/main", threshold: hudson.model.Result.SUCCESS)
}
options {
@@ -14,127 +20,105 @@ pipeline {
stages {
stage("Docker images") {
parallel {
- stage('Publish JDK 8 + MongoDB 4.0') {
+ stage('Publish JDK (Java 24) + MongoDB 8.0') {
when {
- changeset "ci/openjdk8-mongodb-4.0/**"
- }
- agent { label 'data' }
- options { timeout(time: 30, unit: 'MINUTES') }
+ anyOf {
+ changeset "ci/openjdk24-mongodb-8.0/**"
+ changeset "ci/pipeline.properties"
+ }
+ }
+ agent { label 'data' }
+ options { timeout(time: 30, unit: 'MINUTES') }
steps {
script {
- def image = docker.build("springci/spring-data-openjdk8-with-mongodb-4.0", "ci/openjdk8-mongodb-4.0/")
- docker.withRegistry('', 'hub.docker.com-springbuildmaster') {
+ def image = docker.build("springci/spring-data-with-mongodb-8.0:${p['java.main.tag']}", "--build-arg BASE=${p['docker.java.main.image']} --build-arg MONGODB=${p['docker.mongodb.7.0.version']} ci/openjdk24-mongodb-8.0/")
+ docker.withRegistry(p['docker.registry'], p['docker.credentials']) {
image.push()
}
}
}
}
- stage('Publish JDK 8 + MongoDB 4.1') {
+ stage('Publish JDK (Java.next) + MongoDB 8.0') {
when {
- changeset "ci/openjdk8-mongodb-4.1/**"
+ anyOf {
+ changeset "ci/openjdk24-mongodb-8.0/**"
+ changeset "ci/pipeline.properties"
+ }
}
agent { label 'data' }
options { timeout(time: 30, unit: 'MINUTES') }
steps {
script {
- def image = docker.build("springci/spring-data-openjdk8-with-mongodb-4.1", "ci/openjdk8-mongodb-4.1/")
- docker.withRegistry('', 'hub.docker.com-springbuildmaster') {
+ def image = docker.build("springci/spring-data-with-mongodb-8.0:${p['java.next.tag']}", "--build-arg BASE=${p['docker.java.next.image']} --build-arg MONGODB=${p['docker.mongodb.8.0.version']} ci/openjdk24-mongodb-8.0/")
+ docker.withRegistry(p['docker.registry'], p['docker.credentials']) {
image.push()
}
}
}
}
- stage('Publish JDK 8 + MongoDB 4.2') {
- when {
- changeset "ci/openjdk8-mongodb-4.2/**"
- }
- agent { label 'data' }
- options { timeout(time: 30, unit: 'MINUTES') }
-
- steps {
- script {
- def image = docker.build("springci/spring-data-openjdk8-with-mongodb-4.2", "ci/openjdk8-mongodb-4.2/")
- docker.withRegistry('', 'hub.docker.com-springbuildmaster') {
- image.push()
- }
- }
- }
- }
}
}
- stage("test: baseline") {
+ stage("test: baseline (main)") {
when {
+ beforeAgent(true)
anyOf {
- branch 'master'
+ branch(pattern: "main|(\\d\\.\\d\\.x)", comparator: "REGEXP")
not { triggeredBy 'UpstreamCause' }
}
}
agent {
- docker {
- image 'springci/spring-data-openjdk8-with-mongodb-4.2:latest'
- label 'data'
- args '-v $HOME:/tmp/jenkins-home'
- }
+ label 'data'
}
options { timeout(time: 30, unit: 'MINUTES') }
+ environment {
+ ARTIFACTORY = credentials("${p['artifactory.credentials']}")
+ DEVELOCITY_ACCESS_KEY = credentials("${p['develocity.access-key']}")
+ }
steps {
- sh 'rm -rf ?'
- sh 'mkdir -p /tmp/mongodb/db /tmp/mongodb/log'
- sh 'mongod --dbpath /tmp/mongodb/db --replSet rs0 --fork --logpath /tmp/mongodb/log/mongod.log &'
- sh 'sleep 10'
- sh 'mongo --eval "rs.initiate({_id: \'rs0\', members:[{_id: 0, host: \'127.0.0.1:27017\'}]});"'
- sh 'sleep 15'
- sh 'MAVEN_OPTS="-Duser.name=jenkins -Duser.home=/tmp/jenkins-home" ./mvnw clean dependency:list test -Dsort -U -B'
+ script {
+ docker.withRegistry(p['docker.proxy.registry'], p['docker.proxy.credentials']) {
+ docker.image("springci/spring-data-with-mongodb-8.0:${p['java.main.tag']}").inside(p['docker.java.inside.docker']) {
+ sh 'ci/start-replica.sh'
+ sh 'MAVEN_OPTS="-Duser.name=' + "${p['jenkins.user.name']}" + ' -Duser.home=/tmp/jenkins-home" ' +
+ "./mvnw -s settings.xml -Ddevelocity.storage.directory=/tmp/jenkins-home/.develocity-root -Dmaven.repo.local=/tmp/jenkins-home/.m2/spring-data-mongodb clean dependency:list test -Dsort -U -B"
+ }
+ }
+ }
}
}
stage("Test other configurations") {
when {
- anyOf {
- branch 'master'
+ beforeAgent(true)
+ allOf {
+ branch(pattern: "main|(\\d\\.\\d\\.x)", comparator: "REGEXP")
not { triggeredBy 'UpstreamCause' }
}
}
parallel {
- stage("test: mongodb 4.0") {
+
+ stage("test: MongoDB 8.0") {
agent {
- docker {
- image 'springci/spring-data-openjdk8-with-mongodb-4.0:latest'
- label 'data'
- args '-v $HOME:/tmp/jenkins-home'
- }
+ label 'data'
}
options { timeout(time: 30, unit: 'MINUTES') }
- steps {
- sh 'rm -rf ?'
- sh 'mkdir -p /tmp/mongodb/db /tmp/mongodb/log'
- sh 'mongod --dbpath /tmp/mongodb/db --replSet rs0 --fork --logpath /tmp/mongodb/log/mongod.log &'
- sh 'sleep 10'
- sh 'mongo --eval "rs.initiate({_id: \'rs0\', members:[{_id: 0, host: \'127.0.0.1:27017\'}]});"'
- sh 'sleep 15'
- sh 'MAVEN_OPTS="-Duser.name=jenkins -Duser.home=/tmp/jenkins-home" ./mvnw clean dependency:list test -Dsort -U -B'
- }
- }
- stage("test: mongodb 4.1") {
- agent {
- docker {
- image 'springci/spring-data-openjdk8-with-mongodb-4.1:latest'
- label 'data'
- args '-v $HOME:/tmp/jenkins-home'
- }
+ environment {
+ ARTIFACTORY = credentials("${p['artifactory.credentials']}")
+ DEVELOCITY_ACCESS_KEY = credentials("${p['develocity.access-key']}")
}
- options { timeout(time: 30, unit: 'MINUTES') }
steps {
- sh 'rm -rf ?'
- sh 'mkdir -p /tmp/mongodb/db /tmp/mongodb/log'
- sh 'mongod --dbpath /tmp/mongodb/db --replSet rs0 --fork --logpath /tmp/mongodb/log/mongod.log &'
- sh 'sleep 10'
- sh 'mongo --eval "rs.initiate({_id: \'rs0\', members:[{_id: 0, host: \'127.0.0.1:27017\'}]});"'
- sh 'sleep 15'
- sh 'MAVEN_OPTS="-Duser.name=jenkins -Duser.home=/tmp/jenkins-home" ./mvnw clean dependency:list test -Dsort -U -B'
+ script {
+ docker.withRegistry(p['docker.proxy.registry'], p['docker.proxy.credentials']) {
+ docker.image("springci/spring-data-with-mongodb-8.0:${p['java.next.tag']}").inside(p['docker.java.inside.docker']) {
+ sh 'ci/start-replica.sh'
+ sh 'MAVEN_OPTS="-Duser.name=' + "${p['jenkins.user.name']}" + ' -Duser.home=/tmp/jenkins-home" ' +
+ "./mvnw -s settings.xml -Ddevelocity.storage.directory=/tmp/jenkins-home/.develocity-root -Dmaven.repo.local=/tmp/jenkins-home/.m2/spring-data-mongodb clean dependency:list test -Dsort -U -B"
+ }
+ }
+ }
}
}
}
@@ -142,72 +126,45 @@ pipeline {
stage('Release to artifactory') {
when {
+ beforeAgent(true)
anyOf {
- branch 'master'
+ branch(pattern: "main|(\\d\\.\\d\\.x)", comparator: "REGEXP")
not { triggeredBy 'UpstreamCause' }
}
}
agent {
- docker {
- image 'adoptopenjdk/openjdk8:latest'
- label 'data'
- args '-v $HOME:/tmp/jenkins-home'
- }
+ label 'data'
}
options { timeout(time: 20, unit: 'MINUTES') }
-
environment {
- ARTIFACTORY = credentials('02bd1690-b54f-4c9f-819d-a77cb7a9822c')
+ ARTIFACTORY = credentials("${p['artifactory.credentials']}")
+ DEVELOCITY_ACCESS_KEY = credentials("${p['develocity.access-key']}")
}
-
steps {
- sh 'rm -rf ?'
- sh 'MAVEN_OPTS="-Duser.name=jenkins -Duser.home=/tmp/jenkins-home" ./mvnw -Pci,artifactory ' +
- '-Dartifactory.server=https://repo.spring.io ' +
- "-Dartifactory.username=${ARTIFACTORY_USR} " +
- "-Dartifactory.password=${ARTIFACTORY_PSW} " +
- "-Dartifactory.staging-repository=libs-snapshot-local " +
- "-Dartifactory.build-name=spring-data-mongodb " +
- "-Dartifactory.build-number=${BUILD_NUMBER} " +
- '-Dmaven.test.skip=true clean deploy -U -B'
- }
- }
-
- stage('Publish documentation') {
- when {
- branch 'master'
- }
- agent {
- docker {
- image 'adoptopenjdk/openjdk8:latest'
- label 'data'
- args '-v $HOME:/tmp/jenkins-home'
+ script {
+ docker.withRegistry(p['docker.proxy.registry'], p['docker.proxy.credentials']) {
+ docker.image(p['docker.java.main.image']).inside(p['docker.java.inside.docker']) {
+ sh 'MAVEN_OPTS="-Duser.name=' + "${p['jenkins.user.name']}" + ' -Duser.home=/tmp/jenkins-home" ' +
+ "./mvnw -s settings.xml -Pci,artifactory " +
+ "-Ddevelocity.storage.directory=/tmp/jenkins-home/.develocity-root " +
+ "-Dartifactory.server=${p['artifactory.url']} " +
+ "-Dartifactory.username=${ARTIFACTORY_USR} " +
+ "-Dartifactory.password=${ARTIFACTORY_PSW} " +
+ "-Dartifactory.staging-repository=${p['artifactory.repository.snapshot']} " +
+ "-Dartifactory.build-name=spring-data-mongodb " +
+ "-Dartifactory.build-number=spring-data-mongodb-${BRANCH_NAME}-build-${BUILD_NUMBER} " +
+ "-Dmaven.repo.local=/tmp/jenkins-home/.m2/spring-data-mongodb " +
+ "-Dmaven.test.skip=true clean deploy -U -B"
+ }
+ }
}
}
- options { timeout(time: 20, unit: 'MINUTES') }
-
- environment {
- ARTIFACTORY = credentials('02bd1690-b54f-4c9f-819d-a77cb7a9822c')
- }
-
- steps {
- sh 'MAVEN_OPTS="-Duser.name=jenkins -Duser.home=/tmp/jenkins-home" ./mvnw -Pci,distribute ' +
- '-Dartifactory.server=https://repo.spring.io ' +
- "-Dartifactory.username=${ARTIFACTORY_USR} " +
- "-Dartifactory.password=${ARTIFACTORY_PSW} " +
- "-Dartifactory.distribution-repository=temp-private-local " +
- '-Dmaven.test.skip=true clean deploy -U -B'
- }
}
}
post {
changed {
script {
- slackSend(
- color: (currentBuild.currentResult == 'SUCCESS') ? 'good' : 'danger',
- channel: '#spring-data-dev',
- message: "${currentBuild.fullDisplayName} - `${currentBuild.currentResult}`\n${env.BUILD_URL}")
emailext(
subject: "[${currentBuild.fullDisplayName}] ${currentBuild.currentResult}",
mimeType: 'text/html',
diff --git a/LICENSE.txt b/LICENSE.txt
new file mode 100644
index 0000000000..ff77379631
--- /dev/null
+++ b/LICENSE.txt
@@ -0,0 +1,202 @@
+
+ Apache License
+ Version 2.0, January 2004
+ https://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "{}"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright {yyyy} {name of copyright owner}
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ https://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/README.adoc b/README.adoc
index e2ee15c857..61b956fbfc 100644
--- a/README.adoc
+++ b/README.adoc
@@ -1,17 +1,19 @@
-image:https://spring.io/badges/spring-data-mongodb/ga.svg[Spring Data MongoDB,link=https://projects.spring.io/spring-data-mongodb#quick-start] image:https://spring.io/badges/spring-data-mongodb/snapshot.svg[Spring Data MongoDB,link=https://projects.spring.io/spring-data-mongodb#quick-start]
+image:https://spring.io/badges/spring-data-mongodb/ga.svg[Spring Data MongoDB,link=https://spring.io/projects/spring-data-mongodb#quick-start] image:https://spring.io/badges/spring-data-mongodb/snapshot.svg[Spring Data MongoDB,link=https://spring.io/projects/spring-data-mongodb#quick-start]
-= Spring Data MongoDB image:https://jenkins.spring.io/buildStatus/icon?job=spring-data-mongodb%2Fmaster&subject=Build[link=https://jenkins.spring.io/view/SpringData/job/spring-data-mongodb/] https://gitter.im/spring-projects/spring-data[image:https://badges.gitter.im/spring-projects/spring-data.svg[Gitter]]
+= Spring Data MongoDB image:https://jenkins.spring.io/buildStatus/icon?job=spring-data-mongodb%2Fmain&subject=Build[link=https://jenkins.spring.io/view/SpringData/job/spring-data-mongodb/] image:https://img.shields.io/badge/Revved%20up%20by-Develocity-06A0CE?logo=Gradle&labelColor=02303A["Revved up by Develocity", link="https://ge.spring.io/scans?search.rootProjectNames=Spring Data MongoDB"]
-The primary goal of the https://projects.spring.io/spring-data[Spring Data] project is to make it easier to build Spring-powered applications that use new data access technologies such as non-relational databases, map-reduce frameworks, and cloud based data services.
+The primary goal of the https://spring.io/projects/spring-data[Spring Data] project is to make it easier to build Spring-powered applications that use new data access technologies such as non-relational databases, map-reduce frameworks, and cloud based data services.
The Spring Data MongoDB project aims to provide a familiar and consistent Spring-based programming model for new datastores while retaining store-specific features and capabilities.
The Spring Data MongoDB project provides integration with the MongoDB document database.
Key functional areas of Spring Data MongoDB are a POJO centric model for interacting with a MongoDB `+Document+` and easily writing a repository style data access layer.
+[[code-of-conduct]]
== Code of Conduct
-This project is governed by the link:CODE_OF_CONDUCT.adoc[Spring Code of Conduct]. By participating, you are expected to uphold this code of conduct. Please report unacceptable behavior to spring-code-of-conduct@pivotal.io.
+This project is governed by the https://github.com/spring-projects/.github/blob/e3cc2ff230d8f1dca06535aa6b5a4a23815861d4/CODE_OF_CONDUCT.md[Spring Code of Conduct]. By participating, you are expected to uphold this code of conduct. Please report unacceptable behavior to spring-code-of-conduct@pivotal.io.
+[[getting-started]]
== Getting Started
Here is a quick teaser of an application using Spring Data Repositories in Java:
@@ -50,12 +52,7 @@ public class MyService {
@Configuration
@EnableMongoRepositories
-class ApplicationConfig extends AbstractMongoConfiguration {
-
- @Override
- public MongoClient mongoClient() {
- return new MongoClient();
- }
+class ApplicationConfig extends AbstractMongoClientConfiguration {
@Override
protected String getDatabaseName() {
@@ -64,6 +61,7 @@ class ApplicationConfig extends AbstractMongoConfiguration {
}
----
+[[maven-configuration]]
=== Maven configuration
Add the Maven dependency:
@@ -73,87 +71,161 @@ Add the Maven dependency:
org.springframework.dataspring-data-mongodb
- ${version}.RELEASE
+ ${version}
----
-If you'd rather like the latest snapshots of the upcoming major version, use our Maven snapshot repository and declare the appropriate dependency version.
+If you'd rather like the latest snapshots of the upcoming major version, use our Maven snapshot repository
+and declare the appropriate dependency version.
[source,xml]
----
org.springframework.dataspring-data-mongodb
- ${version}.BUILD-SNAPSHOT
+ ${version}-SNAPSHOT
- spring-libs-snapshot
+ spring-snapshotSpring Snapshot Repository
- https://repo.spring.io/libs-snapshot
+ https://repo.spring.io/snapshot
----
+[[upgrading]]
+== Upgrading
+
+Instructions for how to upgrade from earlier versions of Spring Data are provided on the project https://github.com/spring-projects/spring-data-commons/wiki[wiki].
+Follow the links in the https://github.com/spring-projects/spring-data-commons/wiki#release-notes[release notes section] to find the version that you want to upgrade to.
+
+[[getting-help]]
== Getting Help
Having trouble with Spring Data? We’d love to help!
* Check the
-https://docs.spring.io/spring-data/mongodb/docs/current/reference/html/[reference documentation], and https://docs.spring.io/spring-data/mongodb/docs/current/api/[Javadocs].
+https://docs.spring.io/spring-data/mongodb/reference/[reference documentation], and https://docs.spring.io/spring-data/mongodb/docs/current/api/[Javadocs]
* Learn the Spring basics – Spring Data builds on Spring Framework, check the https://spring.io[spring.io] web-site for a wealth of reference documentation.
If you are just starting out with Spring, try one of the https://spring.io/guides[guides].
* If you are upgrading, check out the https://docs.spring.io/spring-data/mongodb/docs/current/changelog.txt[changelog] for "`new and noteworthy`" features.
* Ask a question - we monitor https://stackoverflow.com[stackoverflow.com] for questions tagged with https://stackoverflow.com/tags/spring-data[`spring-data-mongodb`].
-You can also chat with the community on https://gitter.im/spring-projects/spring-data[Gitter].
-* Report bugs with Spring Data MongoDB at https://jira.spring.io/browse/DATAMONGO[jira.spring.io/browse/DATAMONGO].
+* Report bugs with Spring Data MongoDB at https://github.com/spring-projects/spring-data-mongodb/issues[github.com/spring-projects/spring-data-mongodb/issues].
+[[reporting-issues]]
== Reporting Issues
-Spring Data uses JIRA as issue tracking system to record bugs and feature requests. If you want to raise an issue, please follow the recommendations below:
+Spring Data uses Github as issue tracking system to record bugs and feature requests.
+If you want to raise an issue, please follow the recommendations below:
+
+* Before you log a bug, please search the https://github.com/spring-projects/spring-data-mongodb/issues[issue tracker] to see if someone has already reported the problem.
+* If the issue does not already exist, https://github.com/spring-projects/spring-data-mongodb/issues/new[create a new issue].
+* Please provide as much information as possible with the issue report, we like to know the version of Spring Data that you are using, the JVM version, Stacktrace, etc.
+* If you need to paste code, or include a stack trace use https://guides.github.com/features/mastering-markdown/[Markdown] code fences +++```+++.
+
+[[guides]]
+== Guides
+
+The https://spring.io/[spring.io] site contains several guides that show how to use Spring Data step-by-step:
-* Before you log a bug, please search the
-https://jira.spring.io/browse/DATAMONGO[issue tracker] to see if someone has already reported the problem.
-* If the issue doesn’t already exist, https://jira.spring.io/browse/DATAMONGO[create a new issue].
-* Please provide as much information as possible with the issue report, we like to know the version of Spring Data that you are using and JVM version.
-* If you need to paste code, or include a stack trace use JIRA `{code}…{code}` escapes before and after your text.
-* If possible try to create a test-case or project that replicates the issue. Attach a link to your code or a compressed file containing your code.
+* https://spring.io/guides/gs/accessing-data-mongodb/[Accessing Data with MongoDB] is a very basic guide that shows you how to create a simple application and how to access data using repositories.
+* https://spring.io/guides/gs/accessing-mongodb-data-rest/[Accessing MongoDB Data with REST] is a guide to creating a REST web service exposing data stored in MongoDB through repositories.
+
+[[examples]]
+== Examples
+* https://github.com/spring-projects/spring-data-examples/[Spring Data Examples] contains example projects that explain specific features in more detail.
+
+[[building-from-source]]
== Building from Source
-You don’t need to build from source to use Spring Data (binaries in https://repo.spring.io[repo.spring.io]), but if you want to try out the latest and greatest, Spring Data can be easily built with the https://github.com/takari/maven-wrapper[maven wrapper].
-You also need JDK 1.8.
+You do not need to build from source to use Spring Data. Binaries are available in https://repo.spring.io[repo.spring.io]
+and accessible from Maven using the Maven configuration noted <>.
+
+NOTE: Configuration for Gradle is similar to Maven.
+
+The best way to get started is by creating a Spring Boot project using MongoDB on https://start.spring.io[start.spring.io].
+Follow this https://start.spring.io/#type=maven-project&language=java&platformVersion=3.0.0&packaging=jar&jvmVersion=17&groupId=com.example&artifactId=demo&name=demo&description=Demo%20project%20for%20Spring%20Boot&packageName=com.example.demo&dependencies=data-mongodb[link]
+to build an imperative application and this https://start.spring.io/#type=maven-project&language=java&platformVersion=3.0.0&packaging=jar&jvmVersion=17&groupId=com.example&artifactId=demo&name=demo&description=Demo%20project%20for%20Spring%20Boot&packageName=com.example.demo&dependencies=data-mongodb-reactive[link]
+to build a reactive one.
+
+However, if you want to try out the latest and greatest, Spring Data MongoDB can be easily built with the https://github.com/takari/maven-wrapper[Maven wrapper]
+and minimally, JDK 17 (https://www.oracle.com/java/technologies/downloads/[JDK downloads]).
+
+In order to build Spring Data MongoDB, you will need to https://www.mongodb.com/try/download/community[download]
+and https://docs.mongodb.com/manual/installation/[install a MongoDB distribution].
+
+Once you have installed MongoDB, you need to start a MongoDB server. It is convenient to set an environment variable to
+your MongoDB installation directory (e.g. `MONGODB_HOME`).
+
+To run the full test suite, a https://docs.mongodb.com/manual/tutorial/deploy-replica-set/[MongoDB Replica Set]
+is required.
+
+To run the MongoDB server enter the following command from a command-line:
[source,bash]
----
- $ ./mvnw clean install
+$ $MONGODB_HOME/bin/mongod --dbpath $MONGODB_HOME/runtime/data --ipv6 --port 27017 --replSet rs0
+...
+"msg":"Successfully connected to host"
----
-If you want to build with the regular `mvn` command, you will need https://maven.apache.org/run-maven/index.html[Maven v3.5.0 or above].
+Once the MongoDB server starts up, you should see the message (`msg`), "_Successfully connected to host_".
-_Also see link:CONTRIBUTING.adoc[CONTRIBUTING.adoc] if you wish to submit pull requests, and in particular please sign the https://cla.pivotal.io/sign/spring[Contributor’s Agreement] before your first non-trivial change._
+Notice the `--dbpath` option to the `mongod` command. You can set this to anything you like, but in this case, we set
+the absolute path to a sub-directory (`runtime/data/`) under the MongoDB installation directory (in `$MONGODB_HOME`).
-=== Building reference documentation
+You need to initialize the MongoDB replica set only once on the first time the MongoDB server is started.
+To initialize the replica set, start a mongo client:
-Building the documentation builds also the project without running tests.
+[source,bash]
+----
+$ $MONGODB_HOME/bin/mongo
+MongoDB server version: 6.0.0
+...
+----
+
+Then enter the following command:
[source,bash]
----
- $ ./mvnw clean install -Pdistribute
+mongo> rs.initiate({ _id: 'rs0', members: [ { _id: 0, host: '127.0.0.1:27017' } ] })
----
-The generated documentation is available from `target/site/reference/html/index.html`.
+Finally, on UNIX-based system (for example, Linux or Mac OS X) you may need to adjust the `ulimit`.
+In case you need to, you can adjust the `ulimit` with the following command (32768 is just a recommendation):
-== Guides
+[source,bash]
+----
+$ ulimit -n 32768
+----
-The https://spring.io/[spring.io] site contains several guides that show how to use Spring Data step-by-step:
+You can use `ulimit -a` again to verify the `ulimit` for "_open files_" was set appropriately.
-* https://spring.io/guides/gs/accessing-data-mongodb/[Accessing Data with MongoDB] is a very basic guide that shows you how to create a simple application and how to access data using repositories.
-* https://spring.io/guides/gs/accessing-mongodb-data-rest/[Accessing MongoDB Data with REST] is a guide to creating a REST web service exposing data stored in MongoDB through repositories.
+Now you are ready to build Spring Data MongoDB. Simply enter the following `mvnw` (Maven Wrapper) command:
-== Examples
+[source,bash]
+----
+ $ ./mvnw clean install
+----
-* https://github.com/spring-projects/spring-data-examples/[Spring Data Examples] contains example projects that explain specific features in more detail.
+If you want to build with the regular `mvn` command, you will need https://maven.apache.org/run-maven/index.html[Maven v3.8.0 or above].
+
+_Also see link:CONTRIBUTING.adoc[CONTRIBUTING.adoc] if you wish to submit pull requests, and in particular, please sign
+the https://cla.pivotal.io/sign/spring[Contributor’s Agreement] before your first non-trivial change._
+
+=== Building reference documentation
+
+Building the documentation builds also the project without running tests.
+
+[source,bash]
+----
+ $ ./mvnw clean install -Pantora
+----
+
+The generated documentation is available from `target/antora/site/index.html`.
+[[license]]
== License
Spring Data MongoDB is Open Source software released under the https://www.apache.org/licenses/LICENSE-2.0.html[Apache 2.0 license].
diff --git a/ci/README.adoc b/ci/README.adoc
index 3e65271901..f1c11d8496 100644
--- a/ci/README.adoc
+++ b/ci/README.adoc
@@ -10,7 +10,7 @@ All of these use cases are great reasons to essentially run what Concourse does
IMPORTANT: To do this you must have Docker installed on your machine.
-1. `docker run -it --mount type=bind,source="$(pwd)",target=/spring-data-mongodb-github springci/spring-data-8-jdk-with-mongodb /bin/bash`
+1. `docker run -it --mount type=bind,source="$(pwd)",target=/spring-data-mongodb-github springci/spring-data-openjdk17-with-mongodb-5.0.3 /bin/bash`
+
This will launch the Docker image and mount your source code at `spring-data-mongodb-github`.
+
@@ -23,7 +23,7 @@ Since the container is binding to your source, you can make edits from your IDE
If you need to test the `build.sh` script, do this:
1. `mkdir /tmp/spring-data-mongodb-artifactory`
-2. `docker run -it --mount type=bind,source="$(pwd)",target=/spring-data-mongodb-github --mount type=bind,source="/tmp/spring-data-mongodb-artifactory",target=/spring-data-mongodb-artifactory springci/spring-data-8-jdk-with-mongodb /bin/bash`
+2. `docker run -it --mount type=bind,source="$(pwd)",target=/spring-data-mongodb-github --mount type=bind,source="/tmp/spring-data-mongodb-artifactory",target=/spring-data-mongodb-artifactory springci/spring-data-openjdk17-with-mongodb-5.0.3 /bin/bash`
+
This will launch the Docker image and mount your source code at `spring-data-mongodb-github` and the temporary
artifactory output directory at `spring-data-mongodb-artifactory`.
@@ -36,4 +36,4 @@ IMPORTANT: `build.sh` doesn't actually push to Artifactory so don't worry about
It just deploys to a local folder. That way, the `artifactory-resource` later in the pipeline can pick up these artifacts
and deliver them to artifactory.
-NOTE: Docker containers can eat up disk space fast! From time to time, run `docker system prune` to clean out old images.
\ No newline at end of file
+NOTE: Docker containers can eat up disk space fast! From time to time, run `docker system prune` to clean out old images.
diff --git a/ci/openjdk24-mongodb-8.0/Dockerfile b/ci/openjdk24-mongodb-8.0/Dockerfile
new file mode 100644
index 0000000000..0cb80001bf
--- /dev/null
+++ b/ci/openjdk24-mongodb-8.0/Dockerfile
@@ -0,0 +1,25 @@
+ARG BASE
+FROM ${BASE}
+# Any ARG statements before FROM are cleared.
+ARG MONGODB
+
+ENV TZ=Etc/UTC
+ENV DEBIAN_FRONTEND=noninteractive
+ENV MONGO_VERSION=${MONGODB}
+
+RUN set -eux; \
+ sed -i -e 's/archive.ubuntu.com/mirror.one.com/g' /etc/apt/sources.list && \
+ sed -i -e 's/security.ubuntu.com/mirror.one.com/g' /etc/apt/sources.list && \
+ sed -i -e 's/ports.ubuntu.com/mirrors.ocf.berkeley.edu/g' /etc/apt/sources.list && \
+ sed -i -e 's/http/https/g' /etc/apt/sources.list && \
+ apt-get update && apt-get install -y apt-transport-https apt-utils gnupg2 wget && \
+ # MongoDB 8.0 release signing key
+ wget -qO - https://www.mongodb.org/static/pgp/server-8.0.asc | apt-key add - && \
+ # Needed when MongoDB creates a 8.0 folder.
+ echo "deb [ arch=amd64,arm64 ] https://repo.mongodb.org/apt/ubuntu noble/mongodb-org/8.0 multiverse" | tee /etc/apt/sources.list.d/mongodb-org-8.0.list && \
+ echo ${TZ} > /etc/timezone
+
+RUN apt-get update && \
+ apt-get install -y mongodb-org=${MONGODB} mongodb-org-server=${MONGODB} mongodb-org-shell=${MONGODB} mongodb-org-mongos=${MONGODB} mongodb-org-tools=${MONGODB} && \
+ apt-get clean && \
+ rm -rf /var/lib/apt/lists/*
diff --git a/ci/openjdk8-mongodb-4.0/Dockerfile b/ci/openjdk8-mongodb-4.0/Dockerfile
deleted file mode 100644
index 18aec0387f..0000000000
--- a/ci/openjdk8-mongodb-4.0/Dockerfile
+++ /dev/null
@@ -1,14 +0,0 @@
-FROM adoptopenjdk/openjdk8:latest
-
-RUN apt-get update && apt-get install -y apt-transport-https apt-utils gnupg2
-
-RUN apt-key adv --keyserver hkps://keyserver.ubuntu.com:443 --recv 9DA31620334BD75D9DCB49F368818C72E52529D4
-
-RUN echo "deb [ arch=amd64 ] https://repo.mongodb.org/apt/ubuntu bionic/mongodb-org/4.0 multiverse" | tee /etc/apt/sources.list.d/mongodb-org-4.0.list
-
-RUN apt-get update
-
-RUN apt-get install -y mongodb-org=4.0.9 mongodb-org-server=4.0.9 mongodb-org-shell=4.0.9 mongodb-org-mongos=4.0.9 mongodb-org-tools=4.0.9
-
-RUN apt-get clean \
- && rm -rf /var/lib/apt/lists/*
\ No newline at end of file
diff --git a/ci/openjdk8-mongodb-4.1/Dockerfile b/ci/openjdk8-mongodb-4.1/Dockerfile
deleted file mode 100644
index 78b1278c18..0000000000
--- a/ci/openjdk8-mongodb-4.1/Dockerfile
+++ /dev/null
@@ -1,14 +0,0 @@
-FROM adoptopenjdk/openjdk8:latest
-
-RUN apt-get update && apt-get install -y apt-transport-https apt-utils gnupg2
-
-RUN apt-key adv --keyserver hkps://keyserver.ubuntu.com:443 --recv 4B7C549A058F8B6B
-
-RUN echo "deb [ arch=amd64 ] https://repo.mongodb.org/apt/ubuntu bionic/mongodb-org/4.1 multiverse" | tee /etc/apt/sources.list.d/mongodb-org-4.1.list
-
-RUN apt-get update
-
-RUN apt-get install -y mongodb-org-unstable=4.1.13 mongodb-org-unstable-server=4.1.13 mongodb-org-unstable-shell=4.1.13 mongodb-org-unstable-mongos=4.1.13 mongodb-org-unstable-tools=4.1.13
-
-RUN apt-get clean \
- && rm -rf /var/lib/apt/lists/*
\ No newline at end of file
diff --git a/ci/openjdk8-mongodb-4.2/Dockerfile b/ci/openjdk8-mongodb-4.2/Dockerfile
deleted file mode 100644
index 7b32ffb85c..0000000000
--- a/ci/openjdk8-mongodb-4.2/Dockerfile
+++ /dev/null
@@ -1,14 +0,0 @@
-FROM adoptopenjdk/openjdk8:latest
-
-RUN apt-get update && apt-get install -y apt-transport-https apt-utils gnupg2
-
-RUN apt-key adv --keyserver hkps://keyserver.ubuntu.com:443 --recv e162f504a20cdf15827f718d4b7c549a058f8b6b
-
-RUN echo "deb [ arch=amd64 ] https://repo.mongodb.org/apt/ubuntu bionic/mongodb-org/4.2 multiverse" | tee /etc/apt/sources.list.d/mongodb-org-4.2.list
-
-RUN apt-get update
-
-RUN apt-get install -y mongodb-org=4.2.0 mongodb-org-server=4.2.0 mongodb-org-shell=4.2.0 mongodb-org-mongos=4.2.0 mongodb-org-tools=4.2.0
-
-RUN apt-get clean \
- && rm -rf /var/lib/apt/lists/*
diff --git a/ci/pipeline.properties b/ci/pipeline.properties
new file mode 100644
index 0000000000..4beebb0dfe
--- /dev/null
+++ b/ci/pipeline.properties
@@ -0,0 +1,30 @@
+# Java versions
+java.main.tag=24.0.1_9-jdk-noble
+java.next.tag=24.0.1_9-jdk-noble
+
+# Docker container images - standard
+docker.java.main.image=library/eclipse-temurin:${java.main.tag}
+docker.java.next.image=library/eclipse-temurin:${java.next.tag}
+
+# Supported versions of MongoDB
+docker.mongodb.8.0.version=8.0.9
+
+# Supported versions of Redis
+docker.redis.6.version=6.2.13
+docker.redis.7.version=7.2.4
+docker.valkey.8.version=8.1.1
+
+# Docker environment settings
+docker.java.inside.basic=-v $HOME:/tmp/jenkins-home
+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
+
+# Credentials
+docker.registry=
+docker.credentials=hub.docker.com-springbuildmaster
+docker.proxy.registry=https://docker-hub.usw1.packages.broadcom.com
+docker.proxy.credentials=usw1_packages_broadcom_com-jenkins-token
+artifactory.credentials=02bd1690-b54f-4c9f-819d-a77cb7a9822c
+artifactory.url=https://repo.spring.io
+artifactory.repository.snapshot=libs-snapshot-local
+develocity.access-key=gradle_enterprise_secret_access_key
+jenkins.user.name=spring-builds+jenkins
diff --git a/ci/start-replica.sh b/ci/start-replica.sh
new file mode 100755
index 0000000000..9124976f39
--- /dev/null
+++ b/ci/start-replica.sh
@@ -0,0 +1,6 @@
+#!/bin/sh
+mkdir -p /tmp/mongodb/db /tmp/mongodb/log
+mongod --setParameter transactionLifetimeLimitSeconds=90 --setParameter maxTransactionLockRequestTimeoutMillis=10000 --dbpath /tmp/mongodb/db --replSet rs0 --fork --logpath /tmp/mongodb/log/mongod.log &
+sleep 10
+mongosh --eval "rs.initiate({_id: 'rs0', members:[{_id: 0, host: '127.0.0.1:27017'}]});"
+sleep 15
diff --git a/pom.xml b/pom.xml
index 4556c2de22..95fc8379d9 100644
--- a/pom.xml
+++ b/pom.xml
@@ -5,17 +5,17 @@
org.springframework.dataspring-data-mongodb-parent
- 2.3.0.BUILD-SNAPSHOT
+ 5.0.0-SNAPSHOTpomSpring Data MongoDBMongoDB support for Spring Data
- https://projects.spring.io/spring-data-mongodb
+ https://spring.io/projects/spring-data-mongodborg.springframework.data.buildspring-data-parent
- 2.3.0.BUILD-SNAPSHOT
+ 4.0.0-SNAPSHOT
@@ -26,9 +26,8 @@
multispring-data-mongodb
- 2.3.0.BUILD-SNAPSHOT
- 3.11.1
- 1.12.0
+ 4.0.0-SNAPSHOT
+ 5.5.01.19
@@ -112,43 +111,66 @@
+
+ scm:git:https://github.com/spring-projects/spring-data-mongodb.git
+ scm:git:git@github.com:spring-projects/spring-data-mongodb.git
+ https://github.com/spring-projects/spring-data-mongodb
+
+
+
+ GitHub
+ https://github.com/spring-projects/spring-data-mongodb/issues
+
+
- benchmarks
-
- spring-data-mongodb
- spring-data-mongodb-distribution
- spring-data-mongodb-benchmarks
-
+ jmh
+
+
+ jitpack.io
+ https://jitpack.io
+
+
+
+ mongo-4.x
+
+ 4.11.1
+ 1.8.0
+
+
+
-
-
-
- org.mongodb
- mongo-java-driver
- ${mongo}
-
-
+
+
+
+
+ org.mongodb
+ mongodb-driver-bom
+ ${mongo}
+ pom
+ import
+
+
+
+
- spring-libs-snapshot
- https://repo.spring.io/libs-snapshot
+ spring-snapshot
+ https://repo.spring.io/snapshot
+
+ true
+
+
+ false
+
+
+
+ spring-milestone
+ https://repo.spring.io/milestone
-
-
- spring-plugins-release
- https://repo.spring.io/plugins-release
-
-
- spring-libs-milestone
- https://repo.spring.io/libs-milestone
-
-
-
-
diff --git a/settings.xml b/settings.xml
new file mode 100644
index 0000000000..b3227cc110
--- /dev/null
+++ b/settings.xml
@@ -0,0 +1,29 @@
+
+
+
+
+ spring-plugins-release
+ ${env.ARTIFACTORY_USR}
+ ${env.ARTIFACTORY_PSW}
+
+
+ spring-libs-snapshot
+ ${env.ARTIFACTORY_USR}
+ ${env.ARTIFACTORY_PSW}
+
+
+ spring-libs-milestone
+ ${env.ARTIFACTORY_USR}
+ ${env.ARTIFACTORY_PSW}
+
+
+ spring-libs-release
+ ${env.ARTIFACTORY_USR}
+ ${env.ARTIFACTORY_PSW}
+
+
+
+
\ No newline at end of file
diff --git a/spring-data-mongodb-benchmarks/README.md b/spring-data-mongodb-benchmarks/README.md
deleted file mode 100644
index ca14cc11a9..0000000000
--- a/spring-data-mongodb-benchmarks/README.md
+++ /dev/null
@@ -1,76 +0,0 @@
-# Benchmarks
-
-Benchmarks are based on [JMH](https://openjdk.java.net/projects/code-tools/jmh/).
-
-# Running Benchmarks
-
-Running benchmarks is disabled by default and can be activated via the `benchmarks` profile.
-To run the benchmarks with default settings use.
-
-```bash
-mvn -P benchmarks clean test
-```
-
-A basic report will be printed to the CLI.
-
-```bash
-# Run complete. Total time: 00:00:15
-
-Benchmark Mode Cnt Score Error Units
-MappingMongoConverterBenchmark.readObject thrpt 10 1920157,631 ± 64310,809 ops/s
-MappingMongoConverterBenchmark.writeObject thrpt 10 782732,857 ± 53804,130 ops/s
-```
-
-## Running all Benchmarks of a specific class
-
-To run all Benchmarks of a specific class, just provide its simple class name via the `benchmark` command line argument.
-
-```bash
-mvn -P benchmarks clean test -D benchmark=MappingMongoConverterBenchmark
-```
-
-## Running a single Benchmark
-
-To run a single Benchmark provide its containing class simple name followed by `#` and the method name via the `benchmark` command line argument.
-
-```bash
-mvn -P benchmarks clean test -D benchmark=MappingMongoConverterBenchmark#readObjectWith2Properties
-```
-
-# Saving Benchmark Results
-
-A detailed benchmark report is stored in JSON format in the `/target/reports/performance` directory.
-To store the report in a different location use the `benchmarkReportDir` command line argument.
-
-## MongoDB
-
-Results can be directly piped to MongoDB by providing a valid [Connection String](https://docs.mongodb.com/manual/reference/connection-string/) via the `publishTo` command line argument.
-
-```bash
-mvn -P benchmarks clean test -D publishTo=mongodb://127.0.0.1:27017
-```
-
-NOTE: If the uri does not explicitly define a database the default `spring-data-mongodb-benchmarks` is used.
-
-## HTTP Endpoint
-
-The benchmark report can also be posted as `application/json` to an HTTP Endpoint by providing a valid URl via the `publishTo` command line argument.
-
-```bash
-mvn -P benchmarks clean test -D publishTo=http://127.0.0.1:8080/capture-benchmarks
-```
-
-# Customizing Benchmarks
-
-Following options can be set via command line.
-
-Option | Default Value
---- | ---
-warmupIterations | 10
-warmupTime | 1 (seconds)
-measurementIterations | 10
-measurementTime | 1 (seconds)
-forks | 1
-benchmarkReportDir | /target/reports/performance (always relative to project root dir)
-benchmark | .* (single benchmark via `classname#benchmark`)
-publishTo | \[not set\] (mongodb-uri or http-endpoint)
\ No newline at end of file
diff --git a/spring-data-mongodb-benchmarks/pom.xml b/spring-data-mongodb-benchmarks/pom.xml
deleted file mode 100644
index c4766040c1..0000000000
--- a/spring-data-mongodb-benchmarks/pom.xml
+++ /dev/null
@@ -1,112 +0,0 @@
-
-
-
- 4.0.0
-
-
- org.springframework.data
- spring-data-mongodb-parent
- 2.3.0.BUILD-SNAPSHOT
- ../pom.xml
-
-
- spring-data-mongodb-benchmarks
- jar
-
- Spring Data MongoDB - Microbenchmarks
-
-
-
- true
-
-
-
-
-
- ${project.groupId}
- spring-data-mongodb
- ${project.version}
-
-
-
- junit
- junit
- ${junit}
- compile
-
-
-
- org.openjdk.jmh
- jmh-core
- ${jmh.version}
-
-
-
- org.openjdk.jmh
- jmh-generator-annprocess
- ${jmh.version}
- provided
-
-
-
-
-
-
-
- benchmarks
-
- false
-
-
-
-
-
-
-
- pl.project13.maven
- git-commit-id-plugin
- 2.2.2
-
-
-
- revision
-
-
-
-
-
- maven-jar-plugin
-
-
- default-jar
- never
-
-
-
-
- maven-surefire-plugin
-
- false
- ${project.build.sourceDirectory}
- ${project.build.outputDirectory}
-
- **/AbstractMicrobenchmark.java
- **/*$*.class
- **/generated/*.class
-
-
- **/*Benchmark*
-
-
- ${project.build.directory}/reports/performance
- ${project.version}
- ${git.dirty}
- ${git.commit.id}
- ${git.branch}
-
-
-
-
-
-
diff --git a/spring-data-mongodb-benchmarks/src/main/resources/logback.xml b/spring-data-mongodb-benchmarks/src/main/resources/logback.xml
deleted file mode 100644
index bccb2dc4fa..0000000000
--- a/spring-data-mongodb-benchmarks/src/main/resources/logback.xml
+++ /dev/null
@@ -1,14 +0,0 @@
-
-
-
-
-
- %d %5p %40.40c:%4L - %m%n
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/spring-data-mongodb-distribution/package.json b/spring-data-mongodb-distribution/package.json
new file mode 100644
index 0000000000..4689506b3f
--- /dev/null
+++ b/spring-data-mongodb-distribution/package.json
@@ -0,0 +1,10 @@
+{
+ "dependencies": {
+ "antora": "3.2.0-alpha.6",
+ "@antora/atlas-extension": "1.0.0-alpha.2",
+ "@antora/collector-extension": "1.0.0-alpha.7",
+ "@asciidoctor/tabs": "1.0.0-beta.6",
+ "@springio/antora-extensions": "1.13.0",
+ "@springio/asciidoctor-extensions": "1.0.0-alpha.11"
+ }
+}
diff --git a/spring-data-mongodb-distribution/pom.xml b/spring-data-mongodb-distribution/pom.xml
index ed39c63e76..fc88571622 100644
--- a/spring-data-mongodb-distribution/pom.xml
+++ b/spring-data-mongodb-distribution/pom.xml
@@ -1,6 +1,7 @@
-
+4.0.0
@@ -14,30 +15,59 @@
org.springframework.dataspring-data-mongodb-parent
- 2.3.0.BUILD-SNAPSHOT
+ 5.0.0-SNAPSHOT../pom.xml${basedir}/..
- SDMONGO
+ ${project.basedir}/../src/main/antora/antora-playbook.yml
+
+
+ ${project.basedir}/../src/main/antora/resources/antora-resources
+ true
+
+
+
+ org.codehaus.mojo
+ build-helper-maven-plugin
+ 3.4.0
+
+
+ timestamp-property
+
+ timestamp-property
+
+ validate
+
+ current.year
+ yyyy
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-resources-plugin
+
+
+
+ resources
+
+
+
+ org.apache.maven.pluginsmaven-assembly-plugin
- org.asciidoctor
- asciidoctor-maven-plugin
-
-
- ${mongo.reactivestreams}
- ${reactor}
-
-
+ org.antora
+ antora-maven-plugin
diff --git a/spring-data-mongodb/pom.xml b/spring-data-mongodb/pom.xml
index 25cf02b5d5..6f34da5660 100644
--- a/spring-data-mongodb/pom.xml
+++ b/spring-data-mongodb/pom.xml
@@ -1,5 +1,7 @@
-
+4.0.0
@@ -11,7 +13,7 @@
org.springframework.dataspring-data-mongodb-parent
- 2.3.0.BUILD-SNAPSHOT
+ 5.0.0-SNAPSHOT../pom.xml
@@ -25,6 +27,30 @@
+
+
+ org.mongodb
+ mongodb-driver-core
+
+
+
+ org.mongodb
+ mongodb-driver-sync
+ true
+
+
+
+ org.mongodb
+ mongodb-driver-reactivestreams
+ true
+
+
+
+ org.mongodb
+ mongodb-crypt
+ true
+
+
org.springframework
@@ -41,12 +67,6 @@
org.springframeworkspring-core
-
-
- commons-logging
- commons-logging
-
- org.springframework
@@ -65,6 +85,12 @@
querydsl-mongodb${querydsl}true
+
+
+ org.mongodb
+ mongo-java-driver
+
+
@@ -81,31 +107,13 @@
true
-
- org.mongodb
- mongodb-driver-reactivestreams
- ${mongo.reactivestreams}
+ com.google.code.findbugs
+ jsr305
+ 3.0.2true
-
- org.mongodb
- mongodb-driver-async
- ${mongo}
- true
-
-
- org.mongodb
- mongodb-driver-core
-
-
- org.mongodb
- bson
-
-
-
-
io.projectreactorreactor-core
@@ -119,35 +127,28 @@
- io.reactivex
- rxjava
- ${rxjava}
- true
-
-
-
- io.reactivex
- rxjava-reactive-streams
- ${rxjava-reactive-streams}
- true
+ org.awaitility
+ awaitility
+ ${awaitility}
+ test
- io.reactivex.rxjava2
+ io.reactivex.rxjava3rxjava
- ${rxjava2}
+ ${rxjava3}true
-
-
- org.apache.geronimo.specs
- geronimo-jcdi_2.0_spec
- 1.0.1
+ net.javacrumbs.json-unit
+ json-unit-assertj
+ 4.1.0test
+
+
javax.interceptorjavax.interceptor-api
@@ -156,17 +157,17 @@
- javax.enterprise
- cdi-api
+ jakarta.enterprise
+ jakarta.enterprise.cdi-api${cdi}providedtrue
- javax.annotation
- javax.annotation-api
- ${javax-annotation-api}
+ jakarta.annotation
+ jakarta.annotation-api
+ ${jakarta-annotation-api}test
@@ -179,8 +180,8 @@
- javax.validation
- validation-api
+ jakarta.validation
+ jakarta.validation-api${validation}true
@@ -193,37 +194,44 @@
- org.hibernate
- hibernate-validator
- 5.2.4.Final
- test
+ io.micrometer
+ micrometer-observation
+ true
+
+
+
+ io.micrometer
+ micrometer-tracing
+ true
- joda-time
- joda-time
- ${jodatime}
+ org.hibernate.validator
+ hibernate-validator
+ 7.0.1.Finaltest
- org.threeten
- threetenbp
- ${threetenbp}
+ jakarta.el
+ jakarta.el-api
+ 4.0.0
+ providedtrue
- com.fasterxml.jackson.core
- jackson-databind
+ org.glassfish
+ jakarta.el
+ 4.0.2
+ providedtrue
- org.slf4j
- jul-to-slf4j
- ${slf4j}
- test
+ com.fasterxml.jackson.core
+ jackson-databind
+ true
@@ -254,9 +262,42 @@
- javax.transaction
- jta
- 1.1
+ org.junit-pioneer
+ junit-pioneer
+ 0.5.3
+ test
+
+
+
+ org.junit.platform
+ junit-platform-launcher
+ test
+
+
+
+ org.testcontainers
+ junit-jupiter
+ ${testcontainers}
+ test
+
+
+
+ org.testcontainers
+ mongodb
+ ${testcontainers}
+ test
+
+
+
+ jakarta.transaction
+ jakarta.transaction-api
+ 2.0.0
+ test
+
+
+
+ org.springframework
+ spring-core-testtest
@@ -287,17 +328,130 @@
io.mockk
- mockk
+ mockk-jvm${mockk}test
+
+ io.micrometer
+ micrometer-test
+ test
+
+
+ com.github.tomakehurst
+ wiremock-jre8-standalone
+
+
+
+
+ io.micrometer
+ micrometer-tracing-test
+ test
+
+
+
+ io.micrometer
+ micrometer-tracing-integration-test
+ test
+
+
+
+
+ org.jmolecules
+ jmolecules-ddd
+ ${jmolecules}
+ test
+
+
-
+
+
+ nullaway
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+
+
+ com.querydsl
+ querydsl-apt
+ ${querydsl}
+
+
+ org.openjdk.jmh
+ jmh-generator-annprocess
+ ${jmh}
+
+
+ com.google.errorprone
+ error_prone_core
+ ${errorprone}
+
+
+ com.uber.nullaway
+ nullaway
+ ${nullaway}
+
+
+
+
+
+ default-compile
+ none
+
+
+ default-testCompile
+ none
+
+
+ java-compile
+ compile
+
+ compile
+
+
+
+ -XDcompilePolicy=simple
+ --should-stop=ifError=FLOW
+ -Xplugin:ErrorProne -XepDisableAllChecks -Xep:NullAway:ERROR -XepOpt:NullAway:OnlyNullMarked=true -XepOpt:NullAway:TreatGeneratedAsUnannotated=true -XepOpt:NullAway:CustomContractAnnotations=org.springframework.lang.Contract
+
+
+
+
+ java-test-compile
+ test-compile
+
+ testCompile
+
+
+
+
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+
+
+ org.openjdk.jmh
+ jmh-generator-annprocess
+ ${jmh}
+
+
+
+
+
com.mysema.mavenapt-maven-plugin
@@ -316,8 +470,11 @@
test-process
- target/generated-test-sources
- org.springframework.data.mongodb.repository.support.MongoAnnotationProcessor
+ target/generated-test-sources
+
+
+ org.springframework.data.mongodb.repository.support.MongoAnnotationProcessor
+
@@ -337,15 +494,13 @@
**/ReactivePerformanceTests.java
- src/test/resources/logging.properties
+ ${mongo}
+ ${env.MONGO_VERSION}
+
+ src/test/resources/logging.properties
+ true
-
-
- listener
- org.springframework.data.mongodb.test.util.CleanMongoDBJunitRunListener
-
-
diff --git a/spring-data-mongodb-benchmarks/src/main/java/org/springframework/data/mongodb/core/ProjectionsBenchmark.java b/spring-data-mongodb/src/jmh/java/org/springframework/data/mongodb/core/ProjectionsBenchmark.java
similarity index 95%
rename from spring-data-mongodb-benchmarks/src/main/java/org/springframework/data/mongodb/core/ProjectionsBenchmark.java
rename to spring-data-mongodb/src/jmh/java/org/springframework/data/mongodb/core/ProjectionsBenchmark.java
index 4af306a4db..3b0c72cc0b 100644
--- a/spring-data-mongodb-benchmarks/src/main/java/org/springframework/data/mongodb/core/ProjectionsBenchmark.java
+++ b/spring-data-mongodb/src/jmh/java/org/springframework/data/mongodb/core/ProjectionsBenchmark.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2017-2019 the original author or authors.
+ * Copyright 2017-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -19,6 +19,7 @@
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.Setup;
import org.openjdk.jmh.annotations.TearDown;
+
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.ExecutableFindOperation.FindWithQuery;
@@ -27,8 +28,8 @@
import org.springframework.data.mongodb.core.query.BasicQuery;
import org.springframework.data.mongodb.microbenchmark.AbstractMicrobenchmark;
-import com.mongodb.MongoClient;
-import com.mongodb.ServerAddress;
+import com.mongodb.client.MongoClient;
+import com.mongodb.client.MongoClients;
import com.mongodb.client.MongoCollection;
/**
@@ -56,7 +57,7 @@ public class ProjectionsBenchmark extends AbstractMicrobenchmark {
@Setup
public void setUp() {
- client = new MongoClient(new ServerAddress());
+ client = MongoClients.create();
template = new MongoTemplate(client, DB_NAME);
source = new Person();
@@ -83,7 +84,7 @@ public void setUp() {
@TearDown
public void tearDown() {
- client.dropDatabase(DB_NAME);
+ client.getDatabase(DB_NAME).drop();
client.close();
}
diff --git a/spring-data-mongodb-benchmarks/src/main/java/org/springframework/data/mongodb/core/convert/DbRefMappingBenchmark.java b/spring-data-mongodb/src/jmh/java/org/springframework/data/mongodb/core/convert/DbRefMappingBenchmark.java
similarity index 78%
rename from spring-data-mongodb-benchmarks/src/main/java/org/springframework/data/mongodb/core/convert/DbRefMappingBenchmark.java
rename to spring-data-mongodb/src/jmh/java/org/springframework/data/mongodb/core/convert/DbRefMappingBenchmark.java
index d647ac6950..53f64f2a50 100644
--- a/spring-data-mongodb-benchmarks/src/main/java/org/springframework/data/mongodb/core/convert/DbRefMappingBenchmark.java
+++ b/spring-data-mongodb/src/jmh/java/org/springframework/data/mongodb/core/convert/DbRefMappingBenchmark.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2017-2019 the original author or authors.
+ * Copyright 2017-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,8 +18,6 @@
import static org.springframework.data.mongodb.core.query.Criteria.*;
import static org.springframework.data.mongodb.core.query.Query.*;
-import lombok.Data;
-
import java.util.ArrayList;
import java.util.List;
@@ -29,14 +27,15 @@
import org.openjdk.jmh.annotations.Setup;
import org.openjdk.jmh.annotations.State;
import org.openjdk.jmh.annotations.TearDown;
+
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.mapping.DBRef;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.microbenchmark.AbstractMicrobenchmark;
-import com.mongodb.MongoClient;
-import com.mongodb.ServerAddress;
+import com.mongodb.client.MongoClient;
+import com.mongodb.client.MongoClients;
/**
* @author Christoph Strobl
@@ -55,7 +54,7 @@ public class DbRefMappingBenchmark extends AbstractMicrobenchmark {
@Setup
public void setUp() throws Exception {
- client = new MongoClient(new ServerAddress());
+ client = MongoClients.create();
template = new MongoTemplate(client, DB_NAME);
List refObjects = new ArrayList<>();
@@ -80,7 +79,7 @@ public void setUp() throws Exception {
@TearDown
public void tearDown() {
- client.dropDatabase(DB_NAME);
+ client.getDatabase(DB_NAME).drop();
client.close();
}
@@ -94,18 +93,56 @@ public ObjectWithDBRef readMultipleDbRefs() {
return template.findOne(queryObjectWithDBRefList, ObjectWithDBRef.class);
}
- @Data
static class ObjectWithDBRef {
private @Id ObjectId id;
private @DBRef RefObject ref;
private @DBRef List refList;
+
+ public ObjectId getId() {
+ return id;
+ }
+
+ public void setId(ObjectId id) {
+ this.id = id;
+ }
+
+ public RefObject getRef() {
+ return ref;
+ }
+
+ public void setRef(RefObject ref) {
+ this.ref = ref;
+ }
+
+ public List getRefList() {
+ return refList;
+ }
+
+ public void setRefList(List refList) {
+ this.refList = refList;
+ }
}
- @Data
static class RefObject {
private @Id String id;
private String someValue;
+
+ public String getId() {
+ return id;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ public String getSomeValue() {
+ return someValue;
+ }
+
+ public void setSomeValue(String someValue) {
+ this.someValue = someValue;
+ }
}
}
diff --git a/spring-data-mongodb-benchmarks/src/main/java/org/springframework/data/mongodb/core/convert/MappingMongoConverterBenchmark.java b/spring-data-mongodb/src/jmh/java/org/springframework/data/mongodb/core/convert/MappingMongoConverterBenchmark.java
similarity index 74%
rename from spring-data-mongodb-benchmarks/src/main/java/org/springframework/data/mongodb/core/convert/MappingMongoConverterBenchmark.java
rename to spring-data-mongodb/src/jmh/java/org/springframework/data/mongodb/core/convert/MappingMongoConverterBenchmark.java
index 3b9efee29a..00d2e7034a 100644
--- a/spring-data-mongodb-benchmarks/src/main/java/org/springframework/data/mongodb/core/convert/MappingMongoConverterBenchmark.java
+++ b/spring-data-mongodb/src/jmh/java/org/springframework/data/mongodb/core/convert/MappingMongoConverterBenchmark.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2017-2019 the original author or authors.
+ * Copyright 2017-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -15,11 +15,6 @@
*/
package org.springframework.data.mongodb.core.convert;
-import lombok.AllArgsConstructor;
-import lombok.Data;
-import lombok.Getter;
-import lombok.RequiredArgsConstructor;
-
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashMap;
@@ -29,25 +24,29 @@
import org.bson.Document;
import org.bson.types.ObjectId;
+import org.junit.platform.commons.annotation.Testable;
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.Setup;
import org.openjdk.jmh.annotations.State;
import org.openjdk.jmh.annotations.TearDown;
+
import org.springframework.data.annotation.Id;
import org.springframework.data.geo.Point;
-import org.springframework.data.mongodb.core.SimpleMongoDbFactory;
+import org.springframework.data.mongodb.core.SimpleMongoClientDatabaseFactory;
import org.springframework.data.mongodb.core.mapping.Field;
import org.springframework.data.mongodb.core.mapping.MongoMappingContext;
import org.springframework.data.mongodb.microbenchmark.AbstractMicrobenchmark;
+import org.springframework.util.ObjectUtils;
-import com.mongodb.MongoClient;
-import com.mongodb.ServerAddress;
+import com.mongodb.client.MongoClient;
+import com.mongodb.client.MongoClients;
/**
* @author Christoph Strobl
*/
@State(Scope.Benchmark)
+@Testable
public class MappingMongoConverterBenchmark extends AbstractMicrobenchmark {
private static final String DB_NAME = "mapping-mongo-converter-benchmark";
@@ -64,13 +63,13 @@ public class MappingMongoConverterBenchmark extends AbstractMicrobenchmark {
@Setup
public void setUp() throws Exception {
- client = new MongoClient(new ServerAddress());
+ client = MongoClients.create();
this.mappingContext = new MongoMappingContext();
this.mappingContext.setInitialEntitySet(Collections.singleton(Customer.class));
this.mappingContext.afterPropertiesSet();
- DbRefResolver dbRefResolver = new DefaultDbRefResolver(new SimpleMongoDbFactory(client, DB_NAME));
+ DbRefResolver dbRefResolver = new DefaultDbRefResolver(new SimpleMongoClientDatabaseFactory(client, DB_NAME));
this.converter = new MappingMongoConverter(dbRefResolver, mappingContext);
this.converter.setCustomConversions(new MongoCustomConversions(Collections.emptyList()));
@@ -116,7 +115,7 @@ public void setUp() throws Exception {
@TearDown
public void tearDown() {
- client.dropDatabase(DB_NAME);
+ client.getDatabase(DB_NAME).drop();
client.close();
}
@@ -151,22 +150,36 @@ public Object writeObjectWithListAndMapsOfComplexType() {
return sink;
}
- @Getter
- @RequiredArgsConstructor
static class Customer {
private @Id ObjectId id;
private final String firstname, lastname;
private final Address address;
+
+ public Customer(String firstname, String lastname, Address address) {
+ this.firstname = firstname;
+ this.lastname = lastname;
+ this.address = address;
+ }
}
- @Getter
- @AllArgsConstructor
static class Address {
private String zipCode, city;
+
+ public Address(String zipCode, String city) {
+ this.zipCode = zipCode;
+ this.city = city;
+ }
+
+ public String getZipCode() {
+ return zipCode;
+ }
+
+ public String getCity() {
+ return city;
+ }
}
- @Data
static class SlightlyMoreComplexObject {
@Id String id;
@@ -177,5 +190,59 @@ static class SlightlyMoreComplexObject {
Customer customer;
List addressList;
Map customerMap;
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (!(o instanceof SlightlyMoreComplexObject)) {
+ return false;
+ }
+ SlightlyMoreComplexObject that = (SlightlyMoreComplexObject) o;
+ if (intOne != that.intOne) {
+ return false;
+ }
+ if (intTwo != that.intTwo) {
+ return false;
+ }
+ if (!ObjectUtils.nullSafeEquals(id, that.id)) {
+ return false;
+ }
+ if (!ObjectUtils.nullSafeEquals(stringOne, that.stringOne)) {
+ return false;
+ }
+ if (!ObjectUtils.nullSafeEquals(stringTwo, that.stringTwo)) {
+ return false;
+ }
+ if (!ObjectUtils.nullSafeEquals(renamedField, that.renamedField)) {
+ return false;
+ }
+ if (!ObjectUtils.nullSafeEquals(location, that.location)) {
+ return false;
+ }
+ if (!ObjectUtils.nullSafeEquals(customer, that.customer)) {
+ return false;
+ }
+ if (!ObjectUtils.nullSafeEquals(addressList, that.addressList)) {
+ return false;
+ }
+ return ObjectUtils.nullSafeEquals(customerMap, that.customerMap);
+ }
+
+ @Override
+ public int hashCode() {
+ int result = ObjectUtils.nullSafeHashCode(id);
+ result = 31 * result + intOne;
+ result = 31 * result + intTwo;
+ result = 31 * result + ObjectUtils.nullSafeHashCode(stringOne);
+ result = 31 * result + ObjectUtils.nullSafeHashCode(stringTwo);
+ result = 31 * result + ObjectUtils.nullSafeHashCode(renamedField);
+ result = 31 * result + ObjectUtils.nullSafeHashCode(location);
+ result = 31 * result + ObjectUtils.nullSafeHashCode(customer);
+ result = 31 * result + ObjectUtils.nullSafeHashCode(addressList);
+ result = 31 * result + ObjectUtils.nullSafeHashCode(customerMap);
+ return result;
+ }
}
}
diff --git a/spring-data-mongodb-benchmarks/src/main/java/org/springframework/data/mongodb/microbenchmark/AbstractMicrobenchmark.java b/spring-data-mongodb/src/jmh/java/org/springframework/data/mongodb/microbenchmark/AbstractMicrobenchmark.java
similarity index 97%
rename from spring-data-mongodb-benchmarks/src/main/java/org/springframework/data/mongodb/microbenchmark/AbstractMicrobenchmark.java
rename to spring-data-mongodb/src/jmh/java/org/springframework/data/mongodb/microbenchmark/AbstractMicrobenchmark.java
index 72119225e8..615500904d 100644
--- a/spring-data-mongodb-benchmarks/src/main/java/org/springframework/data/mongodb/microbenchmark/AbstractMicrobenchmark.java
+++ b/spring-data-mongodb/src/jmh/java/org/springframework/data/mongodb/microbenchmark/AbstractMicrobenchmark.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2017-2019 the original author or authors.
+ * Copyright 2017-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -21,7 +21,6 @@
import java.util.Collection;
import java.util.Date;
-import org.junit.Test;
import org.openjdk.jmh.annotations.Fork;
import org.openjdk.jmh.annotations.Measurement;
import org.openjdk.jmh.annotations.Scope;
@@ -33,6 +32,7 @@
import org.openjdk.jmh.runner.options.ChainedOptionsBuilder;
import org.openjdk.jmh.runner.options.OptionsBuilder;
import org.openjdk.jmh.runner.options.TimeValue;
+
import org.springframework.core.env.StandardEnvironment;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ResourceUtils;
@@ -41,8 +41,8 @@
/**
* @author Christoph Strobl
*/
-@Warmup(iterations = AbstractMicrobenchmark.WARMUP_ITERATIONS)
-@Measurement(iterations = AbstractMicrobenchmark.MEASUREMENT_ITERATIONS)
+@Warmup(iterations = AbstractMicrobenchmark.WARMUP_ITERATIONS, time = 2)
+@Measurement(iterations = AbstractMicrobenchmark.MEASUREMENT_ITERATIONS, time = 2)
@Fork(AbstractMicrobenchmark.FORKS)
@State(Scope.Thread)
public class AbstractMicrobenchmark {
@@ -62,7 +62,6 @@ public class AbstractMicrobenchmark {
* @throws Exception
* @see #options(String)
*/
- @Test
public void run() throws Exception {
String includes = includes();
@@ -322,7 +321,7 @@ private void publishResults(Collection results) {
try {
ResultsWriter.forUri(uri).write(results);
} catch (Exception e) {
- System.err.println(String.format("Cannot save benchmark results to '%s'. Error was %s.", uri, e));
+ System.err.println(String.format("Cannot save benchmark results to '%s'; Error was %s", uri, e));
}
}
}
diff --git a/spring-data-mongodb-benchmarks/src/main/java/org/springframework/data/mongodb/microbenchmark/HttpResultsWriter.java b/spring-data-mongodb/src/jmh/java/org/springframework/data/mongodb/microbenchmark/HttpResultsWriter.java
similarity index 91%
rename from spring-data-mongodb-benchmarks/src/main/java/org/springframework/data/mongodb/microbenchmark/HttpResultsWriter.java
rename to spring-data-mongodb/src/jmh/java/org/springframework/data/mongodb/microbenchmark/HttpResultsWriter.java
index 2f271527a5..af56908755 100644
--- a/spring-data-mongodb-benchmarks/src/main/java/org/springframework/data/mongodb/microbenchmark/HttpResultsWriter.java
+++ b/spring-data-mongodb/src/jmh/java/org/springframework/data/mongodb/microbenchmark/HttpResultsWriter.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2017-2019 the original author or authors.
+ * Copyright 2017-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -15,8 +15,8 @@
*/
package org.springframework.data.mongodb.microbenchmark;
-import lombok.SneakyThrows;
+import java.io.IOException;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
@@ -43,13 +43,20 @@ class HttpResultsWriter implements ResultsWriter {
}
@Override
- @SneakyThrows
public void write(Collection results) {
if (CollectionUtils.isEmpty(results)) {
return;
}
+ try {
+ doWrite(results);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private void doWrite(Collection results) throws IOException {
StandardEnvironment env = new StandardEnvironment();
String projectVersion = env.getProperty("project.version", "unknown");
diff --git a/spring-data-mongodb-benchmarks/src/main/java/org/springframework/data/mongodb/microbenchmark/MongoResultsWriter.java b/spring-data-mongodb/src/jmh/java/org/springframework/data/mongodb/microbenchmark/MongoResultsWriter.java
similarity index 86%
rename from spring-data-mongodb-benchmarks/src/main/java/org/springframework/data/mongodb/microbenchmark/MongoResultsWriter.java
rename to spring-data-mongodb/src/jmh/java/org/springframework/data/mongodb/microbenchmark/MongoResultsWriter.java
index bcd9de082e..2114d2a06a 100644
--- a/spring-data-mongodb-benchmarks/src/main/java/org/springframework/data/mongodb/microbenchmark/MongoResultsWriter.java
+++ b/spring-data-mongodb/src/jmh/java/org/springframework/data/mongodb/microbenchmark/MongoResultsWriter.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2017-2019 the original author or authors.
+ * Copyright 2017-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -21,15 +21,16 @@
import org.bson.Document;
import org.openjdk.jmh.results.RunResult;
+
import org.springframework.core.env.StandardEnvironment;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
import com.mongodb.BasicDBObject;
-import com.mongodb.MongoClient;
-import com.mongodb.MongoClientURI;
+import com.mongodb.ConnectionString;
+import com.mongodb.client.MongoClient;
+import com.mongodb.client.MongoClients;
import com.mongodb.client.MongoDatabase;
-import com.mongodb.util.JSON;
/**
* MongoDB specific {@link ResultsWriter} implementation.
@@ -56,13 +57,14 @@ public void write(Collection results) {
String gitDirty = env.getProperty("git.dirty", "no");
String gitCommitId = env.getProperty("git.commit.id", "unknown");
- MongoClientURI uri = new MongoClientURI(this.uri);
- MongoClient client = new MongoClient(uri);
+ ConnectionString connectionString = new ConnectionString(this.uri);
+ MongoClient client = MongoClients.create(this.uri);
- String dbName = StringUtils.hasText(uri.getDatabase()) ? uri.getDatabase() : "spring-data-mongodb-benchmarks";
+ String dbName = StringUtils.hasText(connectionString.getDatabase()) ? connectionString.getDatabase()
+ : "spring-data-mongodb-benchmarks";
MongoDatabase db = client.getDatabase(dbName);
- for (BasicDBObject dbo : (List) JSON.parse(ResultsWriter.jsonifyResults(results))) {
+ for (Document dbo : (List) Document.parse(ResultsWriter.jsonifyResults(results))) {
String collectionName = extractClass(dbo.get("benchmark").toString());
diff --git a/spring-data-mongodb-benchmarks/src/main/java/org/springframework/data/mongodb/microbenchmark/ResultsWriter.java b/spring-data-mongodb/src/jmh/java/org/springframework/data/mongodb/microbenchmark/ResultsWriter.java
similarity index 89%
rename from spring-data-mongodb-benchmarks/src/main/java/org/springframework/data/mongodb/microbenchmark/ResultsWriter.java
rename to spring-data-mongodb/src/jmh/java/org/springframework/data/mongodb/microbenchmark/ResultsWriter.java
index 59eb58e342..95da1750bc 100644
--- a/spring-data-mongodb-benchmarks/src/main/java/org/springframework/data/mongodb/microbenchmark/ResultsWriter.java
+++ b/spring-data-mongodb/src/jmh/java/org/springframework/data/mongodb/microbenchmark/ResultsWriter.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2017-2019 the original author or authors.
+ * Copyright 2017-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -15,8 +15,6 @@
*/
package org.springframework.data.mongodb.microbenchmark;
-import lombok.SneakyThrows;
-
import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import java.nio.charset.StandardCharsets;
@@ -54,13 +52,12 @@ static ResultsWriter forUri(String uri) {
*
* @param results
* @return json string representation of results.
- * @see org.openjdk.jmh.results.format.JSONResultFormat
*/
- @SneakyThrows
static String jsonifyResults(Collection results) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
- ResultFormatFactory.getInstance(ResultFormatType.JSON, new PrintStream(baos, true, "UTF-8")).writeOut(results);
+ ResultFormatFactory.getInstance(ResultFormatType.JSON, new PrintStream(baos, true, StandardCharsets.UTF_8))
+ .writeOut(results);
return new String(baos.toByteArray(), StandardCharsets.UTF_8);
}
diff --git a/spring-data-mongodb/src/jmh/java/org/springframework/data/mongodb/repository/AotRepositoryBenchmark.java b/spring-data-mongodb/src/jmh/java/org/springframework/data/mongodb/repository/AotRepositoryBenchmark.java
new file mode 100644
index 0000000000..ba9da66da4
--- /dev/null
+++ b/spring-data-mongodb/src/jmh/java/org/springframework/data/mongodb/repository/AotRepositoryBenchmark.java
@@ -0,0 +1,153 @@
+/*
+ * Copyright 2025 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.data.mongodb.repository;
+
+import org.junit.platform.commons.annotation.Testable;
+import org.openjdk.jmh.annotations.Benchmark;
+import org.openjdk.jmh.annotations.Level;
+import org.openjdk.jmh.annotations.Scope;
+import org.openjdk.jmh.annotations.Setup;
+import org.openjdk.jmh.annotations.State;
+import org.openjdk.jmh.annotations.TearDown;
+
+import org.springframework.aot.test.generate.TestGenerationContext;
+import org.springframework.core.test.tools.TestCompiler;
+import org.springframework.data.mongodb.core.MongoOperations;
+import org.springframework.data.mongodb.core.MongoTemplate;
+import org.springframework.data.mongodb.microbenchmark.AbstractMicrobenchmark;
+import org.springframework.data.mongodb.repository.aot.MongoRepositoryContributor;
+import org.springframework.data.mongodb.repository.aot.TestMongoAotRepositoryContext;
+import org.springframework.data.mongodb.repository.support.MongoRepositoryFactory;
+import org.springframework.data.mongodb.repository.support.QuerydslMongoPredicateExecutor;
+import org.springframework.data.mongodb.repository.support.SimpleMongoRepository;
+import org.springframework.data.projection.ProjectionFactory;
+import org.springframework.data.projection.SpelAwareProxyProjectionFactory;
+import org.springframework.data.repository.core.RepositoryMetadata;
+import org.springframework.data.repository.core.support.RepositoryComposition;
+import org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport;
+import org.springframework.data.repository.core.support.RepositoryFragment;
+import org.springframework.data.repository.query.ValueExpressionDelegate;
+
+import com.mongodb.client.MongoClient;
+import com.mongodb.client.MongoClients;
+
+/**
+ * Benchmark for AOT repositories.
+ *
+ * @author Mark Paluch
+ */
+@Testable
+public class AotRepositoryBenchmark extends AbstractMicrobenchmark {
+
+ @State(Scope.Benchmark)
+ public static class BenchmarkParameters {
+
+ public static Class> aot;
+ public static TestMongoAotRepositoryContext repositoryContext = new TestMongoAotRepositoryContext(
+ SmallerPersonRepository.class,
+ RepositoryComposition.of(RepositoryFragment.structural(SimpleMongoRepository.class),
+ RepositoryFragment.structural(QuerydslMongoPredicateExecutor.class)));
+
+ MongoClient mongoClient;
+ MongoTemplate mongoTemplate;
+ RepositoryComposition.RepositoryFragments fragments;
+ SmallerPersonRepository repositoryProxy;
+
+ @Setup(Level.Trial)
+ public void doSetup() {
+
+ mongoClient = MongoClients.create();
+ mongoTemplate = new MongoTemplate(mongoClient, "jmh");
+
+ if (this.aot == null) {
+
+ TestGenerationContext generationContext = new TestGenerationContext(PersonRepository.class);
+
+ new MongoRepositoryContributor(repositoryContext).contribute(generationContext);
+
+ TestCompiler.forSystem().withCompilerOptions("-parameters").with(generationContext).compile(compiled -> {
+
+ try {
+ this.aot = compiled.getClassLoader().loadClass(SmallerPersonRepository.class.getName() + "Impl__Aot");
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ });
+ }
+
+ try {
+ RepositoryFactoryBeanSupport.FragmentCreationContext creationContext = getCreationContext(repositoryContext);
+ fragments = RepositoryComposition.RepositoryFragments
+ .just(aot.getConstructor(MongoOperations.class, RepositoryFactoryBeanSupport.FragmentCreationContext.class)
+ .newInstance(mongoTemplate, creationContext));
+
+ this.repositoryProxy = createRepository(fragments);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private RepositoryFactoryBeanSupport.FragmentCreationContext getCreationContext(
+ TestMongoAotRepositoryContext repositoryContext) {
+
+ RepositoryFactoryBeanSupport.FragmentCreationContext creationContext = new RepositoryFactoryBeanSupport.FragmentCreationContext() {
+ @Override
+ public RepositoryMetadata getRepositoryMetadata() {
+ return repositoryContext.getRepositoryInformation();
+ }
+
+ @Override
+ public ValueExpressionDelegate getValueExpressionDelegate() {
+ return ValueExpressionDelegate.create();
+ }
+
+ @Override
+ public ProjectionFactory getProjectionFactory() {
+ return new SpelAwareProxyProjectionFactory();
+ }
+ };
+
+ return creationContext;
+ }
+
+ @TearDown(Level.Trial)
+ public void doTearDown() {
+ mongoClient.close();
+ }
+
+ public SmallerPersonRepository createRepository(RepositoryComposition.RepositoryFragments fragments) {
+ MongoRepositoryFactory repositoryFactory = new MongoRepositoryFactory(mongoTemplate);
+ return repositoryFactory.getRepository(SmallerPersonRepository.class, fragments);
+ }
+
+ }
+
+ @Benchmark
+ public SmallerPersonRepository repositoryBootstrap(BenchmarkParameters parameters) {
+ return parameters.createRepository(parameters.fragments);
+ }
+
+ @Benchmark
+ public Object findDerived(BenchmarkParameters parameters) {
+ return parameters.repositoryProxy.findByFirstname("foo");
+ }
+
+ @Benchmark
+ public Object findAnnotated(BenchmarkParameters parameters) {
+ return parameters.repositoryProxy.findByThePersonsFirstname("foo");
+ }
+
+}
diff --git a/spring-data-mongodb/src/jmh/java/org/springframework/data/mongodb/repository/SmallerPersonRepository.java b/spring-data-mongodb/src/jmh/java/org/springframework/data/mongodb/repository/SmallerPersonRepository.java
new file mode 100644
index 0000000000..bc3868e052
--- /dev/null
+++ b/spring-data-mongodb/src/jmh/java/org/springframework/data/mongodb/repository/SmallerPersonRepository.java
@@ -0,0 +1,477 @@
+/*
+ * Copyright 2010-2025 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.data.mongodb.repository;
+
+import java.util.Collection;
+import java.util.Date;
+import java.util.List;
+import java.util.Optional;
+import java.util.UUID;
+import java.util.stream.Stream;
+
+import org.jspecify.annotations.Nullable;
+
+import org.springframework.data.domain.Limit;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.domain.ScrollPosition;
+import org.springframework.data.domain.Slice;
+import org.springframework.data.domain.Sort;
+import org.springframework.data.domain.Window;
+import org.springframework.data.mongodb.core.aggregation.AggregationResults;
+import org.springframework.data.mongodb.core.query.UpdateDefinition;
+import org.springframework.data.mongodb.repository.Person.Sex;
+import org.springframework.data.querydsl.QuerydslPredicateExecutor;
+import org.springframework.data.repository.query.Param;
+
+/**
+ * Sample repository managing {@link Person} entities.
+ *
+ * @author Oliver Gierke
+ * @author Thomas Darimont
+ * @author Christoph Strobl
+ * @author Fırat KÜÇÜK
+ * @author Mark Paluch
+ */
+public interface SmallerPersonRepository extends MongoRepository, QuerydslPredicateExecutor {
+
+ /**
+ * Returns all {@link Person}s with the given lastname.
+ *
+ * @param lastname
+ * @return
+ */
+ List findByLastname(String lastname);
+
+ List findByLastnameStartsWith(String prefix);
+
+ List findByLastnameEndsWith(String postfix);
+
+ /**
+ * Returns all {@link Person}s with the given lastname ordered by their firstname.
+ *
+ * @param lastname
+ * @return
+ */
+ List findByLastnameOrderByFirstnameAsc(String lastname);
+
+ /**
+ * Returns the {@link Person}s with the given firstname. Uses {@link Query} annotation to define the query to be
+ * executed.
+ *
+ * @param firstname
+ * @return
+ */
+ @Query(value = "{ 'lastname' : ?0 }", fields = "{ 'firstname': 1, 'lastname': 1}")
+ List findByThePersonsLastname(String lastname);
+
+ /**
+ * Returns the {@link Person}s with the given firstname. Uses {@link Query} annotation to define the query to be
+ * executed.
+ *
+ * @param firstname
+ * @return
+ */
+ @Query(value = "{ 'firstname' : ?0 }", fields = "{ 'firstname': 1, 'lastname': 1}")
+ List findByThePersonsFirstname(String firstname);
+
+ // DATAMONGO-871
+ @Query(value = "{ 'firstname' : ?0 }")
+ Person[] findByThePersonsFirstnameAsArray(String firstname);
+
+ /**
+ * Returns all {@link Person}s with a firstname matching the given one (*-wildcard supported).
+ *
+ * @param firstname
+ * @return
+ */
+ List findByFirstnameLike(@Nullable String firstname);
+
+ List findByFirstnameNotContains(String firstname);
+
+ /**
+ * Returns all {@link Person}s with a firstname not matching the given one (*-wildcard supported).
+ *
+ * @param firstname
+ * @return
+ */
+ List findByFirstnameNotLike(String firstname);
+
+ List findByFirstnameLikeOrderByLastnameAsc(String firstname, Sort sort);
+
+ List findBySkillsContains(List skills);
+
+ List findBySkillsNotContains(List skills);
+
+ @Query("{'age' : { '$lt' : ?0 } }")
+ List findByAgeLessThan(int age, Sort sort);
+
+ /**
+ * Returns a scroll of {@link Person}s with a lastname matching the given one (*-wildcards supported).
+ *
+ * @param lastname
+ * @param scrollPosition
+ * @return
+ */
+ Window findTop2ByLastnameLikeOrderByLastnameAscFirstnameAsc(String lastname, ScrollPosition scrollPosition);
+
+ Window findByLastnameLikeOrderByLastnameAscFirstnameAsc(String lastname, ScrollPosition scrollPosition,
+ Limit limit);
+
+ /**
+ * Returns a scroll of {@link Person}s applying projections with a lastname matching the given one (*-wildcards
+ * supported).
+ *
+ * @param lastname
+ * @param pageable
+ * @return
+ */
+ Window findCursorProjectionByLastnameLike(String lastname, Pageable pageable);
+
+ /**
+ * Returns a page of {@link Person}s with a lastname matching the given one (*-wildcards supported).
+ *
+ * @param lastname
+ * @param pageable
+ * @return
+ */
+ Page findByLastnameLike(String lastname, Pageable pageable);
+
+ List findByLastnameLike(String lastname, Sort sort, Limit limit);
+
+ @Query("{ 'lastname' : { '$regex' : '?0', '$options' : 'i'}}")
+ Page findByLastnameLikeWithPageable(String lastname, Pageable pageable);
+
+ List findByFirstname(String firstname);
+
+ List findByLastnameIgnoreCaseIn(String... lastname);
+
+ /**
+ * Returns all {@link Person}s with a firstname contained in the given varargs.
+ *
+ * @param firstnames
+ * @return
+ */
+ List findByFirstnameIn(String... firstnames);
+
+ /**
+ * Returns all {@link Person}s with a firstname not contained in the given collection.
+ *
+ * @param firstnames
+ * @return
+ */
+ List findByFirstnameNotIn(Collection firstnames);
+
+ List findByFirstnameAndLastname(String firstname, String lastname);
+
+ /**
+ * Returns all {@link Person}s with an age between the two given values.
+ *
+ * @param from
+ * @param to
+ * @return
+ */
+ List findByAgeBetween(int from, int to);
+
+ /**
+ * Returns the {@link Person} with the given {@link Address} as shipping address.
+ *
+ * @param address
+ * @return
+ */
+ Person findByShippingAddresses(Address address);
+
+ /**
+ * Returns all {@link Person}s with the given {@link Address}.
+ *
+ * @param address
+ * @return
+ */
+ List findByAddress(Address address);
+
+ List findByAddressZipCode(String zipCode);
+
+ List findByLastnameLikeAndAgeBetween(String lastname, int from, int to);
+
+ List findByAgeOrLastnameLikeAndFirstnameLike(int age, String lastname, String firstname);
+
+ // TODO: List findByLocationNear(Point point);
+
+ // TODO: List findByLocationWithin(Circle circle);
+
+ // TODO: List findByLocationWithin(Box box);
+
+ // TODO: List findByLocationWithin(Polygon polygon);
+
+ List findBySex(Sex sex);
+
+ List findBySex(Sex sex, Pageable pageable);
+
+ // TODO: List findByNamedQuery(String firstname);
+
+ List findByCreator(User user);
+
+ // DATAMONGO-425
+ List findByCreatedAtLessThan(Date date);
+
+ // DATAMONGO-425
+ List findByCreatedAtGreaterThan(Date date);
+
+ // DATAMONGO-425
+ @Query("{ 'createdAt' : { '$lt' : ?0 }}")
+ List findByCreatedAtLessThanManually(Date date);
+
+ // DATAMONGO-427
+ List findByCreatedAtBefore(Date date);
+
+ // DATAMONGO-427
+ List findByCreatedAtAfter(Date date);
+
+ // DATAMONGO-472
+ List findByLastnameNot(String lastname);
+
+ // DATAMONGO-600
+ List findByCredentials(Credentials credentials);
+
+ // DATAMONGO-636
+ long countByLastname(String lastname);
+
+ // DATAMONGO-636
+ int countByFirstname(String firstname);
+
+ // DATAMONGO-636
+ @Query(value = "{ 'lastname' : ?0 }", count = true)
+ long someCountQuery(String lastname);
+
+ // DATAMONGO-1454
+ boolean existsByFirstname(String firstname);
+
+ // DATAMONGO-1454
+ @ExistsQuery(value = "{ 'lastname' : ?0 }")
+ boolean someExistQuery(String lastname);
+
+ // DATAMONGO-770
+ List findByFirstnameIgnoreCase(@Nullable String firstName);
+
+ // DATAMONGO-770
+ List findByFirstnameNotIgnoreCase(String firstName);
+
+ // DATAMONGO-770
+ List findByFirstnameStartingWithIgnoreCase(String firstName);
+
+ // DATAMONGO-770
+ List findByFirstnameEndingWithIgnoreCase(String firstName);
+
+ // DATAMONGO-770
+ List findByFirstnameContainingIgnoreCase(String firstName);
+
+ // DATAMONGO-870
+ Slice findByAgeGreaterThan(int age, Pageable pageable);
+
+ // DATAMONGO-821
+ @Query("{ creator : { $exists : true } }")
+ Page findByHavingCreator(Pageable page);
+
+ // DATAMONGO-566
+ List deleteByLastname(String lastname);
+
+ // DATAMONGO-566
+ Long deletePersonByLastname(String lastname);
+
+ // DATAMONGO-1997
+ Optional deleteOptionalByLastname(String lastname);
+
+ // DATAMONGO-566
+ @Query(value = "{ 'lastname' : ?0 }", delete = true)
+ List removeByLastnameUsingAnnotatedQuery(String lastname);
+
+ // DATAMONGO-566
+ @Query(value = "{ 'lastname' : ?0 }", delete = true)
+ Long removePersonByLastnameUsingAnnotatedQuery(String lastname);
+
+ // DATAMONGO-893
+ Page findByAddressIn(List address, Pageable page);
+
+ // DATAMONGO-745
+ @Query("{firstname:{$in:?0}, lastname:?1}")
+ Page findByCustomQueryFirstnamesAndLastname(List firstnames, String lastname, Pageable page);
+
+ // DATAMONGO-745
+ @Query("{lastname:?0, 'address.street':{$in:?1}}")
+ Page findByCustomQueryLastnameAndAddressStreetInList(String lastname, List streetNames,
+ Pageable page);
+
+ // DATAMONGO-950
+ List findTop3ByLastnameStartingWith(String lastname);
+
+ // DATAMONGO-950
+ Page findTop3ByLastnameStartingWith(String lastname, Pageable pageRequest);
+
+ // DATAMONGO-1865
+ Person findFirstBy(); // limits to 1 result if more, just return the first one
+
+ // DATAMONGO-1865
+ Person findPersonByLastnameLike(String firstname); // single person, error if more than one
+
+ // DATAMONGO-1865
+ Optional findOptionalPersonByLastnameLike(String firstname); // optional still, error when more than one
+
+ // DATAMONGO-1030
+ PersonSummaryDto findSummaryByLastname(String lastname);
+
+ PersonSummaryWithOptional findSummaryWithOptionalByLastname(String lastname);
+
+ @Query("{ ?0 : ?1 }")
+ List findByKeyValue(String key, String value);
+
+ // DATAMONGO-1165
+ @Query("{ firstname : { $in : ?0 }}")
+ Stream findByCustomQueryWithStreamingCursorByFirstnames(List firstnames);
+
+ // DATAMONGO-990
+ @Query("{ firstname : ?#{[0]}}")
+ List findWithSpelByFirstnameForSpELExpressionWithParameterIndexOnly(String firstname);
+
+ // DATAMONGO-990
+ @Query("{ firstname : ?#{[0]}, email: ?#{principal.email} }")
+ List findWithSpelByFirstnameAndCurrentUserWithCustomQuery(String firstname);
+
+ // DATAMONGO-990
+ @Query("{ firstname : :#{#firstname}}")
+ List findWithSpelByFirstnameForSpELExpressionWithParameterVariableOnly(@Param("firstname") String firstname);
+
+ // DATAMONGO-1911
+ @Query("{ uniqueId: ?0}")
+ Person findByUniqueId(UUID uniqueId);
+
+ /**
+ * Returns the count of {@link Person} with the given firstname. Uses {@link CountQuery} annotation to define the
+ * query to be executed.
+ *
+ * @param firstname
+ * @return
+ */
+ @CountQuery("{ 'firstname' : ?0 }") // DATAMONGO-1539
+ long countByThePersonsFirstname(String firstname);
+
+ /**
+ * Deletes {@link Person} entities with the given firstname. Uses {@link DeleteQuery} annotation to define the query
+ * to be executed.
+ *
+ * @param firstname
+ */
+ @DeleteQuery("{ 'firstname' : ?0 }") // DATAMONGO-1539
+ void deleteByThePersonsFirstname(String firstname);
+
+ // DATAMONGO-1752
+ Iterable findOpenProjectionBy();
+
+ // DATAMONGO-1752
+ Iterable findClosedProjectionBy();
+
+ @Query(sort = "{ age : -1 }")
+ List findByAgeGreaterThan(int age);
+
+ @Query(sort = "{ age : -1 }")
+ List findByAgeGreaterThan(int age, Sort sort);
+
+ // TODO: List findByFirstnameRegex(Pattern pattern);
+
+ @Query(value = "{ 'id' : ?0 }", fields = "{ 'fans': { '$slice': [ ?1, ?2 ] } }")
+ Person findWithSliceInProjection(String id, int skip, int limit);
+
+ @Query(value = "{ 'id' : ?0 }", fields = "{ 'firstname': { '$toUpper': '$firstname' } }")
+ Person findWithAggregationInProjection(String id);
+
+ @Query(value = "{ 'shippingAddresses' : { '$elemMatch' : { 'city' : { '$eq' : 'lnz' } } } }",
+ fields = "{ 'shippingAddresses.$': ?0 }")
+ Person findWithArrayPositionInProjection(int position);
+
+ @Query(value = "{ 'fans' : { '$elemMatch' : { '$ref' : 'user' } } }", fields = "{ 'fans.$': ?0 }")
+ Person findWithArrayPositionInProjectionWithDbRef(int position);
+
+ @Aggregation("{ '$project': { '_id' : '$lastname' } }")
+ List findAllLastnames();
+
+ @Aggregation("{ '$project': { '_id' : '$lastname' } }")
+ Stream findAllLastnamesAsStream();
+
+ @Aggregation("{ '$group': { '_id' : '$lastname', names : { $addToSet : '$?0' } } }")
+ Stream groupStreamByLastnameAnd(String property);
+
+ @Aggregation("{ '$group': { '_id' : '$lastname', names : { $addToSet : '$?0' } } }")
+ List groupByLastnameAnd(String property);
+
+ @Aggregation("{ '$group': { '_id' : '$lastname', names : { $addToSet : '$?0' } } }")
+ Slice groupByLastnameAndAsSlice(String property, Pageable pageable);
+
+ @Aggregation("{ '$group': { '_id' : '$lastname', names : { $addToSet : '$?0' } } }")
+ List groupByLastnameAnd(String property, Sort sort);
+
+ @Aggregation("{ '$group': { '_id' : '$lastname', names : { $addToSet : '$?0' } } }")
+ List groupByLastnameAnd(String property, Pageable page);
+
+ @Aggregation(pipeline = "{ '$group' : { '_id' : null, 'total' : { $sum: '$age' } } }")
+ int sumAge();
+
+ @Aggregation(pipeline = "{ '$group' : { '_id' : null, 'total' : { $sum: '$age' } } }")
+ AggregationResults sumAgeAndReturnAggregationResultWrapper();
+
+ @Aggregation(pipeline = "{ '$group' : { '_id' : null, 'total' : { $sum: '$age' } } }")
+ AggregationResults sumAgeAndReturnAggregationResultWrapperWithConcreteType();
+
+ @Aggregation({ "{ '$match' : { 'lastname' : 'Matthews'} }",
+ "{ '$project': { _id : 0, firstname : 1, lastname : 1 } }" })
+ Iterable findAggregatedClosedInterfaceProjectionBy();
+
+ @Query(value = "{_id:?0}")
+ Optional findDocumentById(String id);
+
+ @Query(value = "{ 'firstname' : ?0, 'lastname' : ?1, 'email' : ?2 , 'age' : ?3, 'sex' : ?4, "
+ + "'createdAt' : ?5, 'skills' : ?6, 'address.street' : ?7, 'address.zipCode' : ?8, " //
+ + "'address.city' : ?9, 'uniqueId' : ?10, 'credentials.username' : ?11, 'credentials.password' : ?12 }")
+ Person findPersonByManyArguments(String firstname, String lastname, String email, Integer age, Sex sex,
+ Date createdAt, List skills, String street, String zipCode, //
+ String city, UUID uniqueId, String username, String password);
+
+ List findByUnwrappedUserUsername(String username);
+
+ List findByUnwrappedUser(User user);
+
+ int findAndUpdateViaMethodArgAllByLastname(String lastname, UpdateDefinition update);
+
+ @Update("{ '$inc' : { 'visits' : ?1 } }")
+ int findAndIncrementVisitsByLastname(String lastname, int increment);
+
+ @Query("{ 'lastname' : ?0 }")
+ @Update("{ '$inc' : { 'visits' : ?1 } }")
+ int updateAllByLastname(String lastname, int increment);
+
+ @Update(pipeline = { "{ '$set' : { 'visits' : { '$add' : [ '$visits', ?1 ] } } }" })
+ void findAndIncrementVisitsViaPipelineByLastname(String lastname, int increment);
+
+ @Update("{ '$inc' : { 'visits' : ?#{[1]} } }")
+ int findAndIncrementVisitsUsingSpELByLastname(String lastname, int increment);
+
+ @Update("{ '$push' : { 'shippingAddresses' : ?1 } }")
+ int findAndPushShippingAddressByEmail(String email, Address address);
+
+ @Query("{ 'age' : null }")
+ Person findByQueryWithNullEqualityCheck();
+
+ List findBySpiritAnimal(User user);
+
+}
diff --git a/spring-data-mongodb/src/jmh/java/org/springframework/data/mongodb/repository/SmallerRepositoryBenchmark.java b/spring-data-mongodb/src/jmh/java/org/springframework/data/mongodb/repository/SmallerRepositoryBenchmark.java
new file mode 100644
index 0000000000..f461a22d31
--- /dev/null
+++ b/spring-data-mongodb/src/jmh/java/org/springframework/data/mongodb/repository/SmallerRepositoryBenchmark.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright 2025 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.data.mongodb.repository;
+
+import org.junit.platform.commons.annotation.Testable;
+import org.openjdk.jmh.annotations.Benchmark;
+import org.openjdk.jmh.annotations.Level;
+import org.openjdk.jmh.annotations.Scope;
+import org.openjdk.jmh.annotations.Setup;
+import org.openjdk.jmh.annotations.State;
+import org.openjdk.jmh.annotations.TearDown;
+
+import org.springframework.data.mongodb.core.MongoTemplate;
+import org.springframework.data.mongodb.microbenchmark.AbstractMicrobenchmark;
+import org.springframework.data.mongodb.repository.support.MongoRepositoryFactory;
+
+import com.mongodb.client.MongoClient;
+import com.mongodb.client.MongoClients;
+
+/**
+ * Benchmark for AOT repositories.
+ *
+ * @author Mark Paluch
+ */
+@Testable
+public class SmallerRepositoryBenchmark extends AbstractMicrobenchmark {
+
+ @State(Scope.Benchmark)
+ public static class BenchmarkParameters {
+
+ MongoClient mongoClient;
+ MongoTemplate mongoTemplate;
+ SmallerPersonRepository repositoryProxy;
+
+ @Setup(Level.Trial)
+ public void doSetup() {
+
+ mongoClient = MongoClients.create();
+ mongoTemplate = new MongoTemplate(mongoClient, "jmh");
+ repositoryProxy = createRepository();
+ }
+
+ @TearDown(Level.Trial)
+ public void doTearDown() {
+ mongoClient.close();
+ }
+
+ public SmallerPersonRepository createRepository() {
+ MongoRepositoryFactory repositoryFactory = new MongoRepositoryFactory(mongoTemplate);
+ return repositoryFactory.getRepository(SmallerPersonRepository.class);
+ }
+
+ }
+
+ @Benchmark
+ public SmallerPersonRepository repositoryBootstrap(BenchmarkParameters parameters) {
+ return parameters.createRepository();
+ }
+
+ @Benchmark
+ public Object findDerived(BenchmarkParameters parameters) {
+ return parameters.repositoryProxy.findByFirstname("foo");
+ }
+
+ @Benchmark
+ public Object findAnnotated(BenchmarkParameters parameters) {
+ return parameters.repositoryProxy.findByThePersonsFirstname("foo");
+ }
+
+}
diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/BindableMongoExpression.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/BindableMongoExpression.java
new file mode 100644
index 0000000000..3ae41aad35
--- /dev/null
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/BindableMongoExpression.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright 2021-2025 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.data.mongodb;
+
+import java.util.Arrays;
+
+import org.bson.Document;
+import org.bson.codecs.DocumentCodec;
+import org.bson.codecs.configuration.CodecRegistry;
+import org.jspecify.annotations.Nullable;
+import org.springframework.data.mongodb.util.json.ParameterBindingDocumentCodec;
+import org.springframework.data.util.Lazy;
+import org.springframework.lang.Contract;
+import org.springframework.util.Assert;
+import org.springframework.util.ObjectUtils;
+import org.springframework.util.StringUtils;
+
+/**
+ * A {@link MongoExpression} using the {@link ParameterBindingDocumentCodec} for parsing a raw ({@literal json})
+ * expression. The expression will be wrapped within { ... } if necessary. The actual parsing and parameter
+ * binding of placeholders like {@code ?0} is delayed upon first call on the target {@link Document} via
+ * {@link #toDocument()}.
+ *
+ *
+ *
+ * Some types might require a special {@link org.bson.codecs.Codec}. If so, make sure to provide a {@link CodecRegistry}
+ * containing the required {@link org.bson.codecs.Codec codec} via {@link #withCodecRegistry(CodecRegistry)}.
+ *
+ * @author Christoph Strobl
+ * @author Giacomo Baso
+ * @since 3.2
+ */
+public class BindableMongoExpression implements MongoExpression {
+
+ private final String expressionString;
+
+ private final @Nullable CodecRegistryProvider codecRegistryProvider;
+
+ private final Object @Nullable [] args;
+
+ private final Lazy target;
+
+ /**
+ * Create a new instance of {@link BindableMongoExpression}.
+ *
+ * @param expression must not be {@literal null}.
+ * @param args must not be {@literal null} but may contain {@literal null} elements.
+ */
+ public BindableMongoExpression(String expression, Object @Nullable [] args) {
+ this(expression, null, args);
+ }
+
+ /**
+ * Create a new instance of {@link BindableMongoExpression}.
+ *
+ * @param expression must not be {@literal null}.
+ * @param codecRegistryProvider can be {@literal null}.
+ * @param args must not be {@literal null} but may contain {@literal null} elements.
+ */
+ public BindableMongoExpression(String expression, @Nullable CodecRegistryProvider codecRegistryProvider,
+ Object @Nullable [] args) {
+
+ Assert.notNull(expression, "Expression must not be null");
+
+ this.expressionString = expression;
+ this.codecRegistryProvider = codecRegistryProvider;
+ this.args = args;
+ this.target = Lazy.of(this::parse);
+ }
+
+ /**
+ * Provide the {@link CodecRegistry} used to convert expressions.
+ *
+ * @param codecRegistry must not be {@literal null}.
+ * @return new instance of {@link BindableMongoExpression}.
+ */
+ @Contract("_ -> new")
+ public BindableMongoExpression withCodecRegistry(CodecRegistry codecRegistry) {
+ return new BindableMongoExpression(expressionString, () -> codecRegistry, args);
+ }
+
+ /**
+ * Provide the arguments to bind to the placeholders via their index.
+ *
+ * @param args must not be {@literal null}.
+ * @return new instance of {@link BindableMongoExpression}.
+ */
+ @Contract("_ -> new")
+ public BindableMongoExpression bind(Object... args) {
+ return new BindableMongoExpression(expressionString, codecRegistryProvider, args);
+ }
+
+ @Override
+ public Document toDocument() {
+ return target.get();
+ }
+
+ @Override
+ public String toString() {
+ return "BindableMongoExpression{" + "expressionString='" + expressionString + '\'' + ", args="
+ + Arrays.toString(args) + '}';
+ }
+
+ private Document parse() {
+
+ String expression = wrapJsonIfNecessary(expressionString);
+
+ if (ObjectUtils.isEmpty(args)) {
+
+ if (codecRegistryProvider == null) {
+ return Document.parse(expression);
+ }
+
+ return Document.parse(expression, codecRegistryProvider.getCodecFor(Document.class)
+ .orElseGet(() -> new DocumentCodec(codecRegistryProvider.getCodecRegistry())));
+ }
+
+ ParameterBindingDocumentCodec codec = codecRegistryProvider == null ? new ParameterBindingDocumentCodec()
+ : new ParameterBindingDocumentCodec(codecRegistryProvider.getCodecRegistry());
+ return codec.decode(expression, args);
+ }
+
+ private static String wrapJsonIfNecessary(String json) {
+
+ if (!StringUtils.hasText(json)) {
+ return json;
+ }
+
+ String raw = json.trim();
+ return (raw.startsWith("{") && raw.endsWith("}")) ? raw : "{%s}".formatted(raw);
+ }
+}
diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/BulkOperationException.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/BulkOperationException.java
index 678f5c7f59..12d8c966af 100644
--- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/BulkOperationException.java
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/BulkOperationException.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2015-2019 the original author or authors.
+ * Copyright 2015-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,11 +17,12 @@
import java.util.List;
+import org.jspecify.annotations.Nullable;
import org.springframework.dao.DataAccessException;
-import com.mongodb.BulkWriteError;
-import com.mongodb.BulkWriteException;
-import com.mongodb.BulkWriteResult;
+import com.mongodb.MongoBulkWriteException;
+import com.mongodb.bulk.BulkWriteError;
+import com.mongodb.bulk.BulkWriteResult;
/**
* Is thrown when errors occur during bulk operations.
@@ -38,12 +39,12 @@ public class BulkOperationException extends DataAccessException {
private final BulkWriteResult result;
/**
- * Creates a new {@link BulkOperationException} with the given message and source {@link BulkWriteException}.
+ * Creates a new {@link BulkOperationException} with the given message and source {@link MongoBulkWriteException}.
*
- * @param message must not be {@literal null}.
+ * @param message can be {@literal null}.
* @param source must not be {@literal null}.
*/
- public BulkOperationException(String message, BulkWriteException source) {
+ public BulkOperationException(@Nullable String message, MongoBulkWriteException source) {
super(message, source);
diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/ClientSessionException.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/ClientSessionException.java
index 14a4e39cd8..c59eecb43a 100644
--- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/ClientSessionException.java
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/ClientSessionException.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2018-2019 the original author or authors.
+ * Copyright 2018-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -15,8 +15,8 @@
*/
package org.springframework.data.mongodb;
+import org.jspecify.annotations.Nullable;
import org.springframework.dao.NonTransientDataAccessException;
-import org.springframework.lang.Nullable;
/**
* {@link NonTransientDataAccessException} specific to MongoDB {@link com.mongodb.session.ClientSession} related data
diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/CodecRegistryProvider.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/CodecRegistryProvider.java
index 4ede50ce00..53515f9fcd 100644
--- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/CodecRegistryProvider.java
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/CodecRegistryProvider.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2017-2019 the original author or authors.
+ * Copyright 2017-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -62,7 +62,7 @@ default boolean hasCodecFor(Class> type) {
*/
default Optional> getCodecFor(Class type) {
- Assert.notNull(type, "Type must not be null!");
+ Assert.notNull(type, "Type must not be null");
try {
return Optional.of(getCodecRegistry().get(type));
diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/DefaultMongoTransactionOptionsResolver.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/DefaultMongoTransactionOptionsResolver.java
new file mode 100644
index 0000000000..87201ef9ee
--- /dev/null
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/DefaultMongoTransactionOptionsResolver.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2024-2025 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.data.mongodb;
+
+import java.util.Map;
+import java.util.Set;
+
+import org.jspecify.annotations.Nullable;
+
+/**
+ * Default implementation of {@link MongoTransactionOptions} using {@literal mongo:} as {@link #getLabelPrefix() label
+ * prefix} creating {@link SimpleMongoTransactionOptions} out of a given argument {@link Map}. Uses
+ * {@link SimpleMongoTransactionOptions#KNOWN_KEYS} to validate entries in arguments to resolve and errors on unknown
+ * entries.
+ *
+ * @author Christoph Strobl
+ * @since 4.3
+ */
+enum DefaultMongoTransactionOptionsResolver implements MongoTransactionOptionsResolver {
+
+ INSTANCE;
+
+ private static final String PREFIX = "mongo:";
+
+ @Override
+ public MongoTransactionOptions convert(Map options) {
+
+ validateKeys(options.keySet());
+ return SimpleMongoTransactionOptions.of(options);
+ }
+
+ @Override
+ public @Nullable String getLabelPrefix() {
+ return PREFIX;
+ }
+
+ private static void validateKeys(Set keys) {
+
+ if (!SimpleMongoTransactionOptions.KNOWN_KEYS.containsAll(keys)) {
+
+ throw new IllegalArgumentException("Transaction labels contained invalid values. Has to be one of %s"
+ .formatted(SimpleMongoTransactionOptions.KNOWN_KEYS));
+ }
+ }
+}
diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/InvalidMongoDbApiUsageException.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/InvalidMongoDbApiUsageException.java
index bd3cbfe20a..f95a3c5310 100644
--- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/InvalidMongoDbApiUsageException.java
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/InvalidMongoDbApiUsageException.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2010-2019 the original author or authors.
+ * Copyright 2010-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/LazyLoadingException.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/LazyLoadingException.java
index f05eff0739..3fc3f82fbf 100644
--- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/LazyLoadingException.java
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/LazyLoadingException.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2013-2019 the original author or authors.
+ * Copyright 2013-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -25,8 +25,10 @@ public class LazyLoadingException extends UncategorizedDataAccessException {
private static final long serialVersionUID = -7089224903873220037L;
/**
- * @param msg
- * @param cause
+ * Constructor for LazyLoadingException.
+ *
+ * @param msg the detail message.
+ * @param cause the exception thrown by underlying data access API.
*/
public LazyLoadingException(String msg, Throwable cause) {
super(msg, cause);
diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/MongoCollectionUtils.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/MongoCollectionUtils.java
index 88ddc7e86d..72b2794d05 100644
--- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/MongoCollectionUtils.java
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/MongoCollectionUtils.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2011-2019 the original author or authors.
+ * Copyright 2011-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -20,8 +20,8 @@
/**
* Helper class featuring helper methods for working with MongoDb collections.
- *
- *
+ *
+ *
* Mainly intended for internal use within the framework.
*
* @author Thomas Risberg
diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/MongoDbFactory.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/MongoDatabaseFactory.java
similarity index 65%
rename from spring-data-mongodb/src/main/java/org/springframework/data/mongodb/MongoDbFactory.java
rename to spring-data-mongodb/src/main/java/org/springframework/data/mongodb/MongoDatabaseFactory.java
index 50a9871899..1fcd5de516 100644
--- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/MongoDbFactory.java
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/MongoDatabaseFactory.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2011-2019 the original author or authors.
+ * Copyright 2011-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -21,7 +21,6 @@
import org.springframework.data.mongodb.core.MongoExceptionTranslator;
import com.mongodb.ClientSessionOptions;
-import com.mongodb.DB;
import com.mongodb.client.ClientSession;
import com.mongodb.client.MongoDatabase;
@@ -31,25 +30,26 @@
* @author Mark Pollack
* @author Thomas Darimont
* @author Christoph Strobl
+ * @since 3.0
*/
-public interface MongoDbFactory extends CodecRegistryProvider, MongoSessionProvider {
+public interface MongoDatabaseFactory extends CodecRegistryProvider, MongoSessionProvider {
/**
- * Creates a default {@link MongoDatabase} instance.
+ * Obtain a {@link MongoDatabase} from the underlying factory.
*
- * @return
+ * @return never {@literal null}.
* @throws DataAccessException
*/
- MongoDatabase getDb() throws DataAccessException;
+ MongoDatabase getMongoDatabase() throws DataAccessException;
/**
- * Creates a {@link DB} instance to access the database with the given name.
+ * Obtain a {@link MongoDatabase} instance to access the database with the given name.
*
- * @param dbName must not be {@literal null} or empty.
- * @return
+ * @param dbName must not be {@literal null}.
+ * @return never {@literal null}.
* @throws DataAccessException
*/
- MongoDatabase getDb(String dbName) throws DataAccessException;
+ MongoDatabase getMongoDatabase(String dbName) throws DataAccessException;
/**
* Exposes a shared {@link MongoExceptionTranslator}.
@@ -58,16 +58,6 @@ public interface MongoDbFactory extends CodecRegistryProvider, MongoSessionProvi
*/
PersistenceExceptionTranslator getExceptionTranslator();
- /**
- * Get the legacy database entry point. Please consider {@link #getDb()} instead.
- *
- * @return
- * @deprecated since 2.1, use {@link #getDb()}. This method will be removed with a future version as it works only
- * with the legacy MongoDB driver.
- */
- @Deprecated
- DB getLegacyDb();
-
/**
* Get the underlying {@link CodecRegistry} used by the MongoDB Java driver.
*
@@ -75,7 +65,7 @@ public interface MongoDbFactory extends CodecRegistryProvider, MongoSessionProvi
*/
@Override
default CodecRegistry getCodecRegistry() {
- return getDb().getCodecRegistry();
+ return getMongoDatabase().getCodecRegistry();
}
/**
@@ -88,29 +78,29 @@ default CodecRegistry getCodecRegistry() {
ClientSession getSession(ClientSessionOptions options);
/**
- * Obtain a {@link ClientSession} bound instance of {@link MongoDbFactory} returning {@link MongoDatabase} instances
- * that are aware and bound to a new session with given {@link ClientSessionOptions options}.
+ * Obtain a {@link ClientSession} bound instance of {@link MongoDatabaseFactory} returning {@link MongoDatabase}
+ * instances that are aware and bound to a new session with given {@link ClientSessionOptions options}.
*
* @param options must not be {@literal null}.
* @return never {@literal null}.
* @since 2.1
*/
- default MongoDbFactory withSession(ClientSessionOptions options) {
+ default MongoDatabaseFactory withSession(ClientSessionOptions options) {
return withSession(getSession(options));
}
/**
- * Obtain a {@link ClientSession} bound instance of {@link MongoDbFactory} returning {@link MongoDatabase} instances
- * that are aware and bound to the given session.
+ * Obtain a {@link ClientSession} bound instance of {@link MongoDatabaseFactory} returning {@link MongoDatabase}
+ * instances that are aware and bound to the given session.
*
* @param session must not be {@literal null}.
* @return never {@literal null}.
* @since 2.1
*/
- MongoDbFactory withSession(ClientSession session);
+ MongoDatabaseFactory withSession(ClientSession session);
/**
- * Returns if the given {@link MongoDbFactory} is bound to a {@link ClientSession} that has an
+ * Returns if the given {@link MongoDatabaseFactory} is bound to a {@link ClientSession} that has an
* {@link ClientSession#hasActiveTransaction() active transaction}.
*
* @return {@literal true} if there's an active transaction, {@literal false} otherwise.
diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/MongoDatabaseUtils.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/MongoDatabaseUtils.java
index 2311c00098..042a5ba1d3 100644
--- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/MongoDatabaseUtils.java
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/MongoDatabaseUtils.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2018-2019 the original author or authors.
+ * Copyright 2018-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -15,7 +15,7 @@
*/
package org.springframework.data.mongodb;
-import org.springframework.lang.Nullable;
+import org.jspecify.annotations.Nullable;
import org.springframework.transaction.support.ResourceHolderSynchronization;
import org.springframework.transaction.support.TransactionSynchronization;
import org.springframework.transaction.support.TransactionSynchronizationManager;
@@ -27,10 +27,9 @@
import com.mongodb.client.MongoDatabase;
/**
- * Helper class for managing a {@link MongoDatabase} instances via {@link MongoDbFactory}. Used for obtaining
+ * Helper class for managing a {@link MongoDatabase} instances via {@link MongoDatabaseFactory}. Used for obtaining
* {@link ClientSession session bound} resources, such as {@link MongoDatabase} and
- * {@link com.mongodb.client.MongoCollection} suitable for transactional usage.
- *
+ * {@link com.mongodb.client.MongoCollection} suitable for transactional usage.
* Note: Intended for internal usage only.
*
* @author Christoph Strobl
@@ -41,93 +40,92 @@
public class MongoDatabaseUtils {
/**
- * Obtain the default {@link MongoDatabase database} form the given {@link MongoDbFactory factory} using
- * {@link SessionSynchronization#ON_ACTUAL_TRANSACTION native session synchronization}.
- *
+ * Obtain the default {@link MongoDatabase database} form the given {@link MongoDatabaseFactory factory} using
+ * {@link SessionSynchronization#ON_ACTUAL_TRANSACTION native session synchronization}.
* Registers a {@link MongoSessionSynchronization MongoDB specific transaction synchronization} within the current
* {@link Thread} if {@link TransactionSynchronizationManager#isSynchronizationActive() synchronization is active}.
*
- * @param factory the {@link MongoDbFactory} to get the {@link MongoDatabase} from.
+ * @param factory the {@link MongoDatabaseFactory} to get the {@link MongoDatabase} from.
* @return the {@link MongoDatabase} that is potentially associated with a transactional {@link ClientSession}.
*/
- public static MongoDatabase getDatabase(MongoDbFactory factory) {
+ public static MongoDatabase getDatabase(MongoDatabaseFactory factory) {
return doGetMongoDatabase(null, factory, SessionSynchronization.ON_ACTUAL_TRANSACTION);
}
/**
- * Obtain the default {@link MongoDatabase database} form the given {@link MongoDbFactory factory}.
- *
+ * Obtain the default {@link MongoDatabase database} form the given {@link MongoDatabaseFactory factory}.
* Registers a {@link MongoSessionSynchronization MongoDB specific transaction synchronization} within the current
* {@link Thread} if {@link TransactionSynchronizationManager#isSynchronizationActive() synchronization is active}.
*
- * @param factory the {@link MongoDbFactory} to get the {@link MongoDatabase} from.
+ * @param factory the {@link MongoDatabaseFactory} to get the {@link MongoDatabase} from.
* @param sessionSynchronization the synchronization to use. Must not be {@literal null}.
* @return the {@link MongoDatabase} that is potentially associated with a transactional {@link ClientSession}.
*/
- public static MongoDatabase getDatabase(MongoDbFactory factory, SessionSynchronization sessionSynchronization) {
+ public static MongoDatabase getDatabase(MongoDatabaseFactory factory, SessionSynchronization sessionSynchronization) {
return doGetMongoDatabase(null, factory, sessionSynchronization);
}
/**
- * Obtain the {@link MongoDatabase database} with given name form the given {@link MongoDbFactory factory} using
- * {@link SessionSynchronization#ON_ACTUAL_TRANSACTION native session synchronization}.
- *
+ * Obtain the {@link MongoDatabase database} with given name form the given {@link MongoDatabaseFactory factory} using
+ * {@link SessionSynchronization#ON_ACTUAL_TRANSACTION native session synchronization}.
* Registers a {@link MongoSessionSynchronization MongoDB specific transaction synchronization} within the current
* {@link Thread} if {@link TransactionSynchronizationManager#isSynchronizationActive() synchronization is active}.
*
* @param dbName the name of the {@link MongoDatabase} to get.
- * @param factory the {@link MongoDbFactory} to get the {@link MongoDatabase} from.
+ * @param factory the {@link MongoDatabaseFactory} to get the {@link MongoDatabase} from.
* @return the {@link MongoDatabase} that is potentially associated with a transactional {@link ClientSession}.
*/
- public static MongoDatabase getDatabase(String dbName, MongoDbFactory factory) {
+ public static MongoDatabase getDatabase(@Nullable String dbName, MongoDatabaseFactory factory) {
return doGetMongoDatabase(dbName, factory, SessionSynchronization.ON_ACTUAL_TRANSACTION);
}
/**
- * Obtain the {@link MongoDatabase database} with given name form the given {@link MongoDbFactory factory}.
- *
+ * Obtain the {@link MongoDatabase database} with given name form the given {@link MongoDatabaseFactory factory}.
+ *
* Registers a {@link MongoSessionSynchronization MongoDB specific transaction synchronization} within the current
* {@link Thread} if {@link TransactionSynchronizationManager#isSynchronizationActive() synchronization is active}.
- *
+ *
* @param dbName the name of the {@link MongoDatabase} to get.
- * @param factory the {@link MongoDbFactory} to get the {@link MongoDatabase} from.
+ * @param factory the {@link MongoDatabaseFactory} to get the {@link MongoDatabase} from.
* @param sessionSynchronization the synchronization to use. Must not be {@literal null}.
* @return the {@link MongoDatabase} that is potentially associated with a transactional {@link ClientSession}.
*/
- public static MongoDatabase getDatabase(String dbName, MongoDbFactory factory,
+ public static MongoDatabase getDatabase(@Nullable String dbName, MongoDatabaseFactory factory,
SessionSynchronization sessionSynchronization) {
return doGetMongoDatabase(dbName, factory, sessionSynchronization);
}
- private static MongoDatabase doGetMongoDatabase(@Nullable String dbName, MongoDbFactory factory,
+ private static MongoDatabase doGetMongoDatabase(@Nullable String dbName, MongoDatabaseFactory factory,
SessionSynchronization sessionSynchronization) {
- Assert.notNull(factory, "Factory must not be null!");
+ Assert.notNull(factory, "Factory must not be null");
- if (!TransactionSynchronizationManager.isSynchronizationActive()) {
- return StringUtils.hasText(dbName) ? factory.getDb(dbName) : factory.getDb();
+ if (sessionSynchronization == SessionSynchronization.NEVER
+ || !TransactionSynchronizationManager.isSynchronizationActive()) {
+ return StringUtils.hasText(dbName) ? factory.getMongoDatabase(dbName) : factory.getMongoDatabase();
}
ClientSession session = doGetSession(factory, sessionSynchronization);
if (session == null) {
- return StringUtils.hasText(dbName) ? factory.getDb(dbName) : factory.getDb();
+ return StringUtils.hasText(dbName) ? factory.getMongoDatabase(dbName) : factory.getMongoDatabase();
}
- MongoDbFactory factoryToUse = factory.withSession(session);
- return StringUtils.hasText(dbName) ? factoryToUse.getDb(dbName) : factoryToUse.getDb();
+ MongoDatabaseFactory factoryToUse = factory.withSession(session);
+ return StringUtils.hasText(dbName) ? factoryToUse.getMongoDatabase(dbName) : factoryToUse.getMongoDatabase();
}
/**
- * Check if the {@link MongoDbFactory} is actually bound to a {@link ClientSession} that has an active transaction, or
- * if a {@link TransactionSynchronization} has been registered for the {@link MongoDbFactory resource} and if the
- * associated {@link ClientSession} has an {@link ClientSession#hasActiveTransaction() active transaction}.
+ * Check if the {@link MongoDatabaseFactory} is actually bound to a {@link ClientSession} that has an active
+ * transaction, or if a {@link TransactionSynchronization} has been registered for the {@link MongoDatabaseFactory
+ * resource} and if the associated {@link ClientSession} has an {@link ClientSession#hasActiveTransaction() active
+ * transaction}.
*
* @param dbFactory the resource to check transactions for. Must not be {@literal null}.
* @return {@literal true} if the factory has an ongoing transaction.
* @since 2.1.3
*/
- public static boolean isTransactionActive(MongoDbFactory dbFactory) {
+ public static boolean isTransactionActive(MongoDatabaseFactory dbFactory) {
if (dbFactory.isTransactionActive()) {
return true;
@@ -137,8 +135,8 @@ public static boolean isTransactionActive(MongoDbFactory dbFactory) {
return resourceHolder != null && resourceHolder.hasActiveTransaction();
}
- @Nullable
- private static ClientSession doGetSession(MongoDbFactory dbFactory, SessionSynchronization sessionSynchronization) {
+ private static @Nullable ClientSession doGetSession(MongoDatabaseFactory dbFactory,
+ SessionSynchronization sessionSynchronization) {
MongoResourceHolder resourceHolder = (MongoResourceHolder) TransactionSynchronizationManager.getResource(dbFactory);
@@ -169,7 +167,7 @@ private static ClientSession doGetSession(MongoDbFactory dbFactory, SessionSynch
return resourceHolder.getSession();
}
- private static ClientSession createClientSession(MongoDbFactory dbFactory) {
+ private static ClientSession createClientSession(MongoDatabaseFactory dbFactory) {
return dbFactory.getSession(ClientSessionOptions.builder().causallyConsistent(true).build());
}
@@ -184,25 +182,17 @@ private static class MongoSessionSynchronization extends ResourceHolderSynchroni
private final MongoResourceHolder resourceHolder;
- MongoSessionSynchronization(MongoResourceHolder resourceHolder, MongoDbFactory dbFactory) {
+ MongoSessionSynchronization(MongoResourceHolder resourceHolder, MongoDatabaseFactory dbFactory) {
super(resourceHolder, dbFactory);
this.resourceHolder = resourceHolder;
}
- /*
- * (non-Javadoc)
- * @see org.springframework.transaction.support.ResourceHolderSynchronization#shouldReleaseBeforeCompletion()
- */
@Override
protected boolean shouldReleaseBeforeCompletion() {
return false;
}
- /*
- * (non-Javadoc)
- * @see org.springframework.transaction.support.ResourceHolderSynchronization#processResourceAfterCommit(java.lang.Object)
- */
@Override
protected void processResourceAfterCommit(MongoResourceHolder resourceHolder) {
@@ -211,10 +201,6 @@ protected void processResourceAfterCommit(MongoResourceHolder resourceHolder) {
}
}
- /*
- * (non-Javadoc)
- * @see org.springframework.transaction.support.ResourceHolderSynchronization#afterCompletion(int)
- */
@Override
public void afterCompletion(int status) {
@@ -225,10 +211,6 @@ public void afterCompletion(int status) {
super.afterCompletion(status);
}
- /*
- * (non-Javadoc)
- * @see org.springframework.transaction.support.ResourceHolderSynchronization#releaseResource(java.lang.Object, java.lang.Object)
- */
@Override
protected void releaseResource(MongoResourceHolder resourceHolder, Object resourceKey) {
diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/MongoExpression.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/MongoExpression.java
new file mode 100644
index 0000000000..a087439d72
--- /dev/null
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/MongoExpression.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2021-2025 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.data.mongodb;
+
+/**
+ * Wrapper object for MongoDB expressions like {@code $toUpper : $name} that manifest as {@link org.bson.Document} when
+ * passed on to the driver.
+ *
+ * A set of predefined {@link MongoExpression expressions}, including a
+ * {@link org.springframework.data.mongodb.core.aggregation.AggregationSpELExpression SpEL based variant} for method
+ * like expressions (eg. {@code toUpper(name)}) are available via the
+ * {@link org.springframework.data.mongodb.core.aggregation Aggregation API}.
+ *
+ * @author Christoph Strobl
+ * @since 3.2
+ * @see org.springframework.data.mongodb.core.aggregation.ArithmeticOperators
+ * @see org.springframework.data.mongodb.core.aggregation.ArrayOperators
+ * @see org.springframework.data.mongodb.core.aggregation.ComparisonOperators
+ * @see org.springframework.data.mongodb.core.aggregation.ConditionalOperators
+ * @see org.springframework.data.mongodb.core.aggregation.ConvertOperators
+ * @see org.springframework.data.mongodb.core.aggregation.DateOperators
+ * @see org.springframework.data.mongodb.core.aggregation.ObjectOperators
+ * @see org.springframework.data.mongodb.core.aggregation.SetOperators
+ * @see org.springframework.data.mongodb.core.aggregation.StringOperators
+ */
+@FunctionalInterface
+public interface MongoExpression {
+
+ /**
+ * Create a new {@link MongoExpression} from plain {@link String} (eg. {@code $toUpper : $name}).
+ * The given expression will be wrapped with { ... } to match an actual MongoDB {@link org.bson.Document}
+ * if necessary.
+ *
+ * @param expression must not be {@literal null}.
+ * @return new instance of {@link MongoExpression}.
+ */
+ static MongoExpression create(String expression) {
+ return new BindableMongoExpression(expression, null);
+ }
+
+ /**
+ * Create a new {@link MongoExpression} from plain {@link String} containing placeholders (eg. {@code $toUpper : ?0})
+ * that will be resolved on first call of {@link #toDocument()}.
+ * The given expression will be wrapped with { ... } to match an actual MongoDB {@link org.bson.Document}
+ * if necessary.
+ *
+ * @param expression must not be {@literal null}.
+ * @return new instance of {@link MongoExpression}.
+ */
+ static MongoExpression create(String expression, Object... args) {
+ return new BindableMongoExpression(expression, args);
+ }
+
+ /**
+ * Obtain the native {@link org.bson.Document} representation.
+ *
+ * @return never {@literal null}.
+ */
+ org.bson.Document toDocument();
+}
diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/MongoManagedTypes.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/MongoManagedTypes.java
new file mode 100644
index 0000000000..39c4815d47
--- /dev/null
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/MongoManagedTypes.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2022-2025 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.data.mongodb;
+
+import java.util.Arrays;
+import java.util.function.Consumer;
+
+import org.springframework.data.domain.ManagedTypes;
+
+/**
+ * @author Christoph Strobl
+ * @since 4.0
+ */
+public final class MongoManagedTypes implements ManagedTypes {
+
+ private final ManagedTypes delegate;
+
+ private MongoManagedTypes(ManagedTypes types) {
+ this.delegate = types;
+ }
+
+ /**
+ * Wraps an existing {@link ManagedTypes} object with {@link MongoManagedTypes}.
+ *
+ * @param managedTypes
+ * @return
+ */
+ public static MongoManagedTypes from(ManagedTypes managedTypes) {
+ return new MongoManagedTypes(managedTypes);
+ }
+
+ /**
+ * Factory method used to construct {@link MongoManagedTypes} from the given array of {@link Class types}.
+ *
+ * @param types array of {@link Class types} used to initialize the {@link ManagedTypes}; must not be {@literal null}.
+ * @return new instance of {@link MongoManagedTypes} initialized from {@link Class types}.
+ */
+ public static MongoManagedTypes from(Class>... types) {
+ return fromIterable(Arrays.asList(types));
+ }
+
+ /**
+ * Factory method used to construct {@link MongoManagedTypes} from the given, required {@link Iterable} of
+ * {@link Class types}.
+ *
+ * @param types {@link Iterable} of {@link Class types} used to initialize the {@link ManagedTypes}; must not be
+ * {@literal null}.
+ * @return new instance of {@link MongoManagedTypes} initialized the given, required {@link Iterable} of {@link Class
+ * types}.
+ */
+ public static MongoManagedTypes fromIterable(Iterable extends Class>> types) {
+ return from(ManagedTypes.fromIterable(types));
+ }
+
+ /**
+ * Factory method to return an empty {@link MongoManagedTypes} object.
+ *
+ * @return an empty {@link MongoManagedTypes} object.
+ */
+ public static MongoManagedTypes empty() {
+ return from(ManagedTypes.empty());
+ }
+
+ @Override
+ public void forEach(Consumer> action) {
+ delegate.forEach(action);
+ }
+}
diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/MongoResourceHolder.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/MongoResourceHolder.java
index 8c2c2b9547..81c25d0998 100644
--- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/MongoResourceHolder.java
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/MongoResourceHolder.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2018-2019 the original author or authors.
+ * Copyright 2018-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -15,7 +15,7 @@
*/
package org.springframework.data.mongodb;
-import org.springframework.lang.Nullable;
+import org.jspecify.annotations.Nullable;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.support.ResourceHolderSupport;
@@ -23,8 +23,7 @@
/**
* MongoDB specific {@link ResourceHolderSupport resource holder}, wrapping a {@link ClientSession}.
- * {@link MongoTransactionManager} binds instances of this class to the thread.
- *
+ * {@link MongoTransactionManager} binds instances of this class to the thread.
* Note: Intended for internal usage only.
*
* @author Christoph Strobl
@@ -36,15 +35,15 @@
class MongoResourceHolder extends ResourceHolderSupport {
private @Nullable ClientSession session;
- private MongoDbFactory dbFactory;
+ private MongoDatabaseFactory dbFactory;
/**
* Create a new {@link MongoResourceHolder} for a given {@link ClientSession session}.
*
* @param session the associated {@link ClientSession}. Can be {@literal null}.
- * @param dbFactory the associated {@link MongoDbFactory}. must not be {@literal null}.
+ * @param dbFactory the associated {@link MongoDatabaseFactory}. must not be {@literal null}.
*/
- MongoResourceHolder(@Nullable ClientSession session, MongoDbFactory dbFactory) {
+ MongoResourceHolder(@Nullable ClientSession session, MongoDatabaseFactory dbFactory) {
this.session = session;
this.dbFactory = dbFactory;
@@ -68,16 +67,16 @@ ClientSession getRequiredSession() {
ClientSession session = getSession();
if (session == null) {
- throw new IllegalStateException("No session available!");
+ throw new IllegalStateException("No session available");
}
return session;
}
/**
- * @return the associated {@link MongoDbFactory}.
+ * @return the associated {@link MongoDatabaseFactory}.
*/
- public MongoDbFactory getDbFactory() {
+ public MongoDatabaseFactory getDbFactory() {
return dbFactory;
}
diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/MongoSessionProvider.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/MongoSessionProvider.java
index d264f4e9cc..645b3508db 100644
--- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/MongoSessionProvider.java
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/MongoSessionProvider.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2018-2019 the original author or authors.
+ * Copyright 2018-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/MongoTransactionException.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/MongoTransactionException.java
index ff41b171cf..3d7bec6780 100644
--- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/MongoTransactionException.java
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/MongoTransactionException.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2018-2019 the original author or authors.
+ * Copyright 2018-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -15,7 +15,7 @@
*/
package org.springframework.data.mongodb;
-import org.springframework.lang.Nullable;
+import org.jspecify.annotations.Nullable;
/**
* A specific {@link ClientSessionException} related to issues with a transaction such as aborted or non existing
diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/MongoTransactionManager.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/MongoTransactionManager.java
index 43f0289d61..1f97bb69e9 100644
--- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/MongoTransactionManager.java
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/MongoTransactionManager.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2018-2019 the original author or authors.
+ * Copyright 2018-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -15,8 +15,8 @@
*/
package org.springframework.data.mongodb;
+import org.jspecify.annotations.Nullable;
import org.springframework.beans.factory.InitializingBean;
-import org.springframework.lang.Nullable;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionException;
import org.springframework.transaction.TransactionSystemException;
@@ -36,18 +36,15 @@
/**
* A {@link org.springframework.transaction.PlatformTransactionManager} implementation that manages
- * {@link ClientSession} based transactions for a single {@link MongoDbFactory}.
- *
- * Binds a {@link ClientSession} from the specified {@link MongoDbFactory} to the thread.
- *
+ * {@link ClientSession} based transactions for a single {@link MongoDatabaseFactory}.
+ * Binds a {@link ClientSession} from the specified {@link MongoDatabaseFactory} to the thread.
* {@link TransactionDefinition#isReadOnly() Readonly} transactions operate on a {@link ClientSession} and enable causal
* consistency, and also {@link ClientSession#startTransaction() start}, {@link ClientSession#commitTransaction()
- * commit} or {@link ClientSession#abortTransaction() abort} a transaction.
- *
+ * commit} or {@link ClientSession#abortTransaction() abort} a transaction.
* Application code is required to retrieve the {@link com.mongodb.client.MongoDatabase} via
- * {@link MongoDatabaseUtils#getDatabase(MongoDbFactory)} instead of a standard {@link MongoDbFactory#getDb()} call.
- * Spring classes such as {@link org.springframework.data.mongodb.core.MongoTemplate} use this strategy implicitly.
- *
+ * {@link MongoDatabaseUtils#getDatabase(MongoDatabaseFactory)} instead of a standard
+ * {@link MongoDatabaseFactory#getMongoDatabase()} call. Spring classes such as
+ * {@link org.springframework.data.mongodb.core.MongoTemplate} use this strategy implicitly.
* By default failure of a {@literal commit} operation raises a {@link TransactionSystemException}. One may override
* {@link #doCommit(MongoTransactionObject)} to implement the
* Retry Commit Operation
@@ -58,57 +55,72 @@
* @currentRead Shadow's Edge - Brent Weeks
* @since 2.1
* @see MongoDB Transaction Documentation
- * @see MongoDatabaseUtils#getDatabase(MongoDbFactory, SessionSynchronization)
+ * @see MongoDatabaseUtils#getDatabase(MongoDatabaseFactory, SessionSynchronization)
*/
public class MongoTransactionManager extends AbstractPlatformTransactionManager
implements ResourceTransactionManager, InitializingBean {
- private @Nullable MongoDbFactory dbFactory;
- private @Nullable TransactionOptions options;
+ private @Nullable MongoDatabaseFactory databaseFactory;
+ private MongoTransactionOptions options;
+ private final MongoTransactionOptionsResolver transactionOptionsResolver;
/**
- * Create a new {@link MongoTransactionManager} for bean-style usage.
- *
- * Note:The {@link MongoDbFactory db factory} has to be {@link #setDbFactory(MongoDbFactory) set}
- * before using the instance. Use this constructor to prepare a {@link MongoTransactionManager} via a
- * {@link org.springframework.beans.factory.BeanFactory}.
- *
+ * Create a new {@link MongoTransactionManager} for bean-style usage.
+ * Note:The {@link MongoDatabaseFactory db factory} has to be
+ * {@link #setDatabaseFactory(MongoDatabaseFactory) set} before using the instance. Use this constructor to prepare a
+ * {@link MongoTransactionManager} via a {@link org.springframework.beans.factory.BeanFactory}.
* Optionally it is possible to set default {@link TransactionOptions transaction options} defining
* {@link com.mongodb.ReadConcern} and {@link com.mongodb.WriteConcern}.
*
- * @see #setDbFactory(MongoDbFactory)
+ * @see #setDatabaseFactory(MongoDatabaseFactory)
* @see #setTransactionSynchronization(int)
*/
- public MongoTransactionManager() {}
+ public MongoTransactionManager() {
+
+ this.transactionOptionsResolver = MongoTransactionOptionsResolver.defaultResolver();
+ this.options = MongoTransactionOptions.NONE;
+ }
/**
- * Create a new {@link MongoTransactionManager} obtaining sessions from the given {@link MongoDbFactory}.
+ * Create a new {@link MongoTransactionManager} obtaining sessions from the given {@link MongoDatabaseFactory}.
*
- * @param dbFactory must not be {@literal null}.
+ * @param databaseFactory must not be {@literal null}.
*/
- public MongoTransactionManager(MongoDbFactory dbFactory) {
- this(dbFactory, null);
+ public MongoTransactionManager(MongoDatabaseFactory databaseFactory) {
+ this(databaseFactory, null);
}
/**
- * Create a new {@link MongoTransactionManager} obtaining sessions from the given {@link MongoDbFactory} applying the
- * given {@link TransactionOptions options}, if present, when starting a new transaction.
+ * Create a new {@link MongoTransactionManager} obtaining sessions from the given {@link MongoDatabaseFactory}
+ * applying the given {@link TransactionOptions options}, if present, when starting a new transaction.
*
- * @param dbFactory must not be {@literal null}.
+ * @param databaseFactory must not be {@literal null}.
* @param options can be {@literal null}.
*/
- public MongoTransactionManager(MongoDbFactory dbFactory, @Nullable TransactionOptions options) {
+ public MongoTransactionManager(MongoDatabaseFactory databaseFactory, @Nullable TransactionOptions options) {
+ this(databaseFactory, MongoTransactionOptionsResolver.defaultResolver(), MongoTransactionOptions.of(options));
+ }
+
+ /**
+ * Create a new {@link MongoTransactionManager} obtaining sessions from the given {@link MongoDatabaseFactory}
+ * applying the given {@link TransactionOptions options}, if present, when starting a new transaction.
+ *
+ * @param databaseFactory must not be {@literal null}.
+ * @param transactionOptionsResolver must not be {@literal null}.
+ * @param defaultTransactionOptions can be {@literal null}.
+ * @since 4.3
+ */
+ public MongoTransactionManager(MongoDatabaseFactory databaseFactory,
+ MongoTransactionOptionsResolver transactionOptionsResolver, MongoTransactionOptions defaultTransactionOptions) {
- Assert.notNull(dbFactory, "DbFactory must not be null!");
+ Assert.notNull(databaseFactory, "MongoDatabaseFactory must not be null");
+ Assert.notNull(transactionOptionsResolver, "MongoTransactionOptionsResolver must not be null");
- this.dbFactory = dbFactory;
- this.options = options;
+ this.databaseFactory = databaseFactory;
+ this.transactionOptionsResolver = transactionOptionsResolver;
+ this.options = defaultTransactionOptions;
}
- /*
- * (non-Javadoc)
- * org.springframework.transaction.support.AbstractPlatformTransactionManager#doGetTransaction()
- */
@Override
protected Object doGetTransaction() throws TransactionException {
@@ -117,19 +129,11 @@ protected Object doGetTransaction() throws TransactionException {
return new MongoTransactionObject(resourceHolder);
}
- /*
- * (non-Javadoc)
- * org.springframework.transaction.support.AbstractPlatformTransactionManager#isExistingTransaction(java.lang.Object)
- */
@Override
protected boolean isExistingTransaction(Object transaction) throws TransactionException {
return extractMongoTransaction(transaction).hasResourceHolder();
}
- /*
- * (non-Javadoc)
- * org.springframework.transaction.support.AbstractPlatformTransactionManager#doBegin(java.lang.Object, org.springframework.transaction.TransactionDefinition)
- */
@Override
protected void doBegin(Object transaction, TransactionDefinition definition) throws TransactionException {
@@ -145,7 +149,9 @@ protected void doBegin(Object transaction, TransactionDefinition definition) thr
}
try {
- mongoTransactionObject.startTransaction(options);
+ MongoTransactionOptions mongoTransactionOptions = transactionOptionsResolver.resolve(definition)
+ .mergeWith(options);
+ mongoTransactionObject.startTransaction(mongoTransactionOptions.toDriverOptions());
} catch (MongoException ex) {
throw new TransactionSystemException(String.format("Could not start Mongo transaction for session %s.",
debugString(mongoTransactionObject.getSession())), ex);
@@ -159,10 +165,6 @@ protected void doBegin(Object transaction, TransactionDefinition definition) thr
TransactionSynchronizationManager.bindResource(getRequiredDbFactory(), resourceHolder);
}
- /*
- * (non-Javadoc)
- * org.springframework.transaction.support.AbstractPlatformTransactionManager#doSuspend(java.lang.Object)
- */
@Override
protected Object doSuspend(Object transaction) throws TransactionException {
@@ -172,19 +174,11 @@ protected Object doSuspend(Object transaction) throws TransactionException {
return TransactionSynchronizationManager.unbindResource(getRequiredDbFactory());
}
- /*
- * (non-Javadoc)
- * org.springframework.transaction.support.AbstractPlatformTransactionManager#doResume(java.lang.Object, java.lang.Object)
- */
@Override
protected void doResume(@Nullable Object transaction, Object suspendedResources) {
TransactionSynchronizationManager.bindResource(getRequiredDbFactory(), suspendedResources);
}
- /*
- * (non-Javadoc)
- * org.springframework.transaction.support.AbstractPlatformTransactionManager#doCommit(org.springframework.transaction.support.DefaultTransactionStatus)
- */
@Override
protected final void doCommit(DefaultTransactionStatus status) throws TransactionException {
@@ -211,8 +205,9 @@ protected final void doCommit(DefaultTransactionStatus status) throws Transactio
* By default those labels are ignored, nevertheless one might check for
* {@link MongoException#UNKNOWN_TRANSACTION_COMMIT_RESULT_LABEL transient commit errors labels} and retry the the
* commit.
+ *
+ *
*
- *
* int retries = 3;
* do {
* try {
@@ -225,8 +220,8 @@ protected final void doCommit(DefaultTransactionStatus status) throws Transactio
* }
* Thread.sleep(500);
* } while (--retries > 0);
- *
*
+ *
*
* @param transactionObject never {@literal null}.
* @throws Exception in case of transaction errors.
@@ -235,10 +230,6 @@ protected void doCommit(MongoTransactionObject transactionObject) throws Excepti
transactionObject.commitTransaction();
}
- /*
- * (non-Javadoc)
- * org.springframework.transaction.support.AbstractPlatformTransactionManager#doRollback(org.springframework.transaction.support.DefaultTransactionStatus)
- */
@Override
protected void doRollback(DefaultTransactionStatus status) throws TransactionException {
@@ -258,10 +249,6 @@ protected void doRollback(DefaultTransactionStatus status) throws TransactionExc
}
}
- /*
- * (non-Javadoc)
- * org.springframework.transaction.support.AbstractPlatformTransactionManager#doSetRollbackOnly(org.springframework.transaction.support.DefaultTransactionStatus)
- */
@Override
protected void doSetRollbackOnly(DefaultTransactionStatus status) throws TransactionException {
@@ -269,10 +256,6 @@ protected void doSetRollbackOnly(DefaultTransactionStatus status) throws Transac
transactionObject.getRequiredResourceHolder().setRollbackOnly();
}
- /*
- * (non-Javadoc)
- * org.springframework.transaction.support.AbstractPlatformTransactionManager#doCleanupAfterCompletion(java.lang.Object)
- */
@Override
protected void doCleanupAfterCompletion(Object transaction) {
@@ -295,14 +278,14 @@ protected void doCleanupAfterCompletion(Object transaction) {
}
/**
- * Set the {@link MongoDbFactory} that this instance should manage transactions for.
+ * Set the {@link MongoDatabaseFactory} that this instance should manage transactions for.
*
- * @param dbFactory must not be {@literal null}.
+ * @param databaseFactory must not be {@literal null}.
*/
- public void setDbFactory(MongoDbFactory dbFactory) {
+ public void setDatabaseFactory(MongoDatabaseFactory databaseFactory) {
- Assert.notNull(dbFactory, "DbFactory must not be null!");
- this.dbFactory = dbFactory;
+ Assert.notNull(databaseFactory, "DbFactory must not be null");
+ this.databaseFactory = databaseFactory;
}
/**
@@ -311,32 +294,23 @@ public void setDbFactory(MongoDbFactory dbFactory) {
* @param options can be {@literal null}.
*/
public void setOptions(@Nullable TransactionOptions options) {
- this.options = options;
+ this.options = MongoTransactionOptions.of(options);
}
/**
- * Get the {@link MongoDbFactory} that this instance manages transactions for.
+ * Get the {@link MongoDatabaseFactory} that this instance manages transactions for.
*
* @return can be {@literal null}.
*/
- @Nullable
- public MongoDbFactory getDbFactory() {
- return dbFactory;
+ public @Nullable MongoDatabaseFactory getDatabaseFactory() {
+ return databaseFactory;
}
- /*
- * (non-Javadoc)
- * @see org.springframework.transaction.support.ResourceTransactionManager#getResourceFactory()
- */
@Override
- public MongoDbFactory getResourceFactory() {
+ public MongoDatabaseFactory getResourceFactory() {
return getRequiredDbFactory();
}
- /*
- * (non-Javadoc)
- * @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
- */
@Override
public void afterPropertiesSet() {
getRequiredDbFactory();
@@ -344,7 +318,7 @@ public void afterPropertiesSet() {
private MongoResourceHolder newResourceHolder(TransactionDefinition definition, ClientSessionOptions options) {
- MongoDbFactory dbFactory = getResourceFactory();
+ MongoDatabaseFactory dbFactory = getResourceFactory();
MongoResourceHolder resourceHolder = new MongoResourceHolder(dbFactory.getSession(options), dbFactory);
resourceHolder.setTimeoutIfNotDefaulted(determineTimeout(definition));
@@ -353,14 +327,14 @@ private MongoResourceHolder newResourceHolder(TransactionDefinition definition,
}
/**
- * @throws IllegalStateException if {@link #dbFactory} is {@literal null}.
+ * @throws IllegalStateException if {@link #databaseFactory} is {@literal null}.
*/
- private MongoDbFactory getRequiredDbFactory() {
+ private MongoDatabaseFactory getRequiredDbFactory() {
- Assert.state(dbFactory != null,
- "MongoTransactionManager operates upon a MongoDbFactory. Did you forget to provide one? It's required.");
+ Assert.state(databaseFactory != null,
+ "MongoTransactionManager operates upon a MongoDbFactory; Did you forget to provide one; It's required");
- return dbFactory;
+ return databaseFactory;
}
private static MongoTransactionObject extractMongoTransaction(Object transaction) {
@@ -396,7 +370,7 @@ private static String debugString(@Nullable ClientSession session) {
debugString += String.format("causallyConsistent = %s, ", session.isCausallyConsistent());
debugString += String.format("txActive = %s, ", session.hasActiveTransaction());
debugString += String.format("txNumber = %d, ", session.getServerSession().getTransactionNumber());
- debugString += String.format("closed = %d, ", session.getServerSession().isClosed());
+ debugString += String.format("closed = %b, ", session.getServerSession().isClosed());
debugString += String.format("clusterTime = %s", session.getClusterTime());
} else {
debugString += "id = n/a";
@@ -486,37 +460,28 @@ void closeSession() {
}
}
- @Nullable
- public ClientSession getSession() {
+ public @Nullable ClientSession getSession() {
return resourceHolder != null ? resourceHolder.getSession() : null;
}
private MongoResourceHolder getRequiredResourceHolder() {
- Assert.state(resourceHolder != null, "MongoResourceHolder is required but not present. o_O");
+ Assert.state(resourceHolder != null, "MongoResourceHolder is required but not present; o_O");
return resourceHolder;
}
private ClientSession getRequiredSession() {
ClientSession session = getSession();
- Assert.state(session != null, "A Session is required but it turned out to be null.");
+ Assert.state(session != null, "A Session is required but it turned out to be null");
return session;
}
- /*
- * (non-Javadoc)
- * @see org.springframework.transaction.support.SmartTransactionObject#isRollbackOnly()
- */
@Override
public boolean isRollbackOnly() {
return this.resourceHolder != null && this.resourceHolder.isRollbackOnly();
}
- /*
- * (non-Javadoc)
- * @see org.springframework.transaction.support.SmartTransactionObject#flush()
- */
@Override
public void flush() {
TransactionSynchronizationUtils.triggerFlush();
diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/MongoTransactionOptions.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/MongoTransactionOptions.java
new file mode 100644
index 0000000000..04bcd36e35
--- /dev/null
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/MongoTransactionOptions.java
@@ -0,0 +1,193 @@
+/*
+ * Copyright 2024-2025 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.data.mongodb;
+
+import java.time.Duration;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Function;
+
+import org.jspecify.annotations.Nullable;
+import org.springframework.data.mongodb.core.ReadConcernAware;
+import org.springframework.data.mongodb.core.ReadPreferenceAware;
+import org.springframework.data.mongodb.core.WriteConcernAware;
+
+import com.mongodb.ReadConcern;
+import com.mongodb.ReadPreference;
+import com.mongodb.TransactionOptions;
+import com.mongodb.WriteConcern;
+import org.springframework.lang.Contract;
+
+/**
+ * Options to be applied within a specific transaction scope.
+ *
+ * @author Christoph Strobl
+ * @since 4.3
+ */
+public interface MongoTransactionOptions
+ extends TransactionMetadata, ReadConcernAware, ReadPreferenceAware, WriteConcernAware {
+
+ /**
+ * Value Object representing empty options enforcing client defaults. Returns {@literal null} for all getter methods.
+ */
+ MongoTransactionOptions NONE = new MongoTransactionOptions() {
+
+ @Override
+ public @Nullable Duration getMaxCommitTime() {
+ return null;
+ }
+
+ @Override
+ public @Nullable ReadConcern getReadConcern() {
+ return null;
+ }
+
+ @Override
+ public @Nullable ReadPreference getReadPreference() {
+ return null;
+ }
+
+ @Override
+ public @Nullable WriteConcern getWriteConcern() {
+ return null;
+ }
+ };
+
+ /**
+ * Merge current options with given ones. Will return first non {@literal null} value from getters whereas the
+ * {@literal this} has precedence over the given fallbackOptions.
+ *
+ * @param fallbackOptions can be {@literal null}.
+ * @return new instance of {@link MongoTransactionOptions} or this if {@literal fallbackOptions} is {@literal null} or
+ * {@link #NONE}.
+ */
+ @Contract("null -> this")
+ default MongoTransactionOptions mergeWith(@Nullable MongoTransactionOptions fallbackOptions) {
+
+ if (fallbackOptions == null || MongoTransactionOptions.NONE.equals(fallbackOptions)) {
+ return this;
+ }
+
+ return new MongoTransactionOptions() {
+
+ @Override
+ public @Nullable Duration getMaxCommitTime() {
+ return MongoTransactionOptions.this.hasMaxCommitTime() ? MongoTransactionOptions.this.getMaxCommitTime()
+ : fallbackOptions.getMaxCommitTime();
+ }
+
+ @Override
+ public @Nullable ReadConcern getReadConcern() {
+ return MongoTransactionOptions.this.hasReadConcern() ? MongoTransactionOptions.this.getReadConcern()
+ : fallbackOptions.getReadConcern();
+ }
+
+ @Override
+ public @Nullable ReadPreference getReadPreference() {
+ return MongoTransactionOptions.this.hasReadPreference() ? MongoTransactionOptions.this.getReadPreference()
+ : fallbackOptions.getReadPreference();
+ }
+
+ @Override
+ public @Nullable WriteConcern getWriteConcern() {
+ return MongoTransactionOptions.this.hasWriteConcern() ? MongoTransactionOptions.this.getWriteConcern()
+ : fallbackOptions.getWriteConcern();
+ }
+ };
+ }
+
+ /**
+ * Apply the current options using the given mapping {@link Function} and return its result.
+ *
+ * @param mappingFunction
+ * @return result of the mapping function.
+ */
+ default T map(Function mappingFunction) {
+ return mappingFunction.apply(this);
+ }
+
+ /**
+ * @return MongoDB driver native {@link TransactionOptions}.
+ * @see MongoTransactionOptions#map(Function)
+ */
+ @SuppressWarnings("NullAway")
+ default @Nullable TransactionOptions toDriverOptions() {
+
+ return map(it -> {
+
+ if (MongoTransactionOptions.NONE.equals(it)) {
+ return null;
+ }
+
+ TransactionOptions.Builder builder = TransactionOptions.builder();
+ if (it.hasMaxCommitTime()) {
+ builder.maxCommitTime(it.getMaxCommitTime().toMillis(), TimeUnit.MILLISECONDS);
+ }
+ if (it.hasReadConcern()) {
+ builder.readConcern(it.getReadConcern());
+ }
+ if (it.hasReadPreference()) {
+ builder.readPreference(it.getReadPreference());
+ }
+ if (it.hasWriteConcern()) {
+ builder.writeConcern(it.getWriteConcern());
+ }
+ return builder.build();
+ });
+ }
+
+ /**
+ * Factory method to wrap given MongoDB driver native {@link TransactionOptions} into {@link MongoTransactionOptions}.
+ *
+ * @param options can be {@literal null}.
+ * @return {@link MongoTransactionOptions#NONE} if given object is {@literal null}.
+ */
+ static MongoTransactionOptions of(@Nullable TransactionOptions options) {
+
+ if (options == null) {
+ return NONE;
+ }
+
+ return new MongoTransactionOptions() {
+
+ @Override
+ public @Nullable Duration getMaxCommitTime() {
+
+ Long millis = options.getMaxCommitTime(TimeUnit.MILLISECONDS);
+ return millis != null ? Duration.ofMillis(millis) : null;
+ }
+
+ @Override
+ public @Nullable ReadConcern getReadConcern() {
+ return options.getReadConcern();
+ }
+
+ @Override
+ public @Nullable ReadPreference getReadPreference() {
+ return options.getReadPreference();
+ }
+
+ @Override
+ public @Nullable WriteConcern getWriteConcern() {
+ return options.getWriteConcern();
+ }
+
+ @Override
+ public @Nullable TransactionOptions toDriverOptions() {
+ return options;
+ }
+ };
+ }
+}
diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/MongoTransactionOptionsResolver.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/MongoTransactionOptionsResolver.java
new file mode 100644
index 0000000000..c4bdbcca53
--- /dev/null
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/MongoTransactionOptionsResolver.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright 2024-2025 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.data.mongodb;
+
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import org.jspecify.annotations.Nullable;
+import org.springframework.transaction.TransactionDefinition;
+import org.springframework.transaction.interceptor.TransactionAttribute;
+import org.springframework.util.Assert;
+import org.springframework.util.StringUtils;
+
+/**
+ * A {@link TransactionOptionResolver} reading MongoDB specific {@link MongoTransactionOptions transaction options} from
+ * a {@link TransactionDefinition}. Implementations of {@link MongoTransactionOptions} may choose a specific
+ * {@link #getLabelPrefix() prefix} for {@link TransactionAttribute#getLabels() transaction attribute labels} to avoid
+ * evaluating non-store specific ones.
+ *
+ * {@link TransactionAttribute#getLabels()} evaluated by default should follow the property style using {@code =} to
+ * separate key and value pairs.
+ *
+ * By default {@link #resolve(TransactionDefinition)} will filter labels by the {@link #getLabelPrefix() prefix} and
+ * strip the prefix from the label before handing the pruned {@link Map} to the {@link #convert(Map)} function.
+ *
+ * A transaction definition with labels targeting MongoDB may look like the following:
+ *
+ *
+ * @Transactional(label = { "mongo:readConcern=majority" })
+ *
+ *
+ * @author Christoph Strobl
+ * @since 4.3
+ */
+public interface MongoTransactionOptionsResolver extends TransactionOptionResolver {
+
+ /**
+ * Obtain the default {@link MongoTransactionOptionsResolver} implementation using a {@literal mongo:}
+ * {@link #getLabelPrefix() prefix}.
+ *
+ * @return instance of default {@link MongoTransactionOptionsResolver} implementation.
+ */
+ static MongoTransactionOptionsResolver defaultResolver() {
+ return DefaultMongoTransactionOptionsResolver.INSTANCE;
+ }
+
+ /**
+ * Get the prefix used to filter applicable {@link TransactionAttribute#getLabels() labels}.
+ *
+ * @return {@literal null} if no label defined.
+ */
+ @Nullable
+ String getLabelPrefix();
+
+ /**
+ * Resolve {@link MongoTransactionOptions} from a given {@link TransactionDefinition} by evaluating
+ * {@link TransactionAttribute#getLabels()} labels if possible.
+ *
+ * Splits applicable labels property style using {@literal =} as deliminator and removes a potential
+ * {@link #getLabelPrefix() prefix} before calling {@link #convert(Map)} with filtered label values.
+ *
+ * @param definition
+ * @return {@link MongoTransactionOptions#NONE} in case the given {@link TransactionDefinition} is not a
+ * {@link TransactionAttribute} if no matching {@link TransactionAttribute#getLabels() labels} could be found.
+ * @throws IllegalArgumentException for options that do not map to valid transactions options or malformatted labels.
+ */
+ @Override
+ default MongoTransactionOptions resolve(TransactionDefinition definition) {
+
+ if (!(definition instanceof TransactionAttribute attribute)) {
+ return MongoTransactionOptions.NONE;
+ }
+
+ if (attribute.getLabels().isEmpty()) {
+ return MongoTransactionOptions.NONE;
+ }
+
+ Map attributeMap = attribute.getLabels().stream()
+ .filter(it -> !StringUtils.hasText(getLabelPrefix()) || it.startsWith(getLabelPrefix()))
+ .map(it -> StringUtils.hasText(getLabelPrefix()) ? it.substring(getLabelPrefix().length()) : it).map(it -> {
+
+ String[] kvPair = StringUtils.split(it, "=");
+ Assert.isTrue(kvPair != null && kvPair.length == 2,
+ () -> "No value present for transaction option %s".formatted(kvPair != null ? kvPair[0] : it));
+ return kvPair;
+ })
+
+ .collect(Collectors.toMap(it -> it[0].trim(), it -> it[1].trim()));
+
+ return attributeMap.isEmpty() ? MongoTransactionOptions.NONE : convert(attributeMap);
+ }
+
+ /**
+ * Convert the given {@link Map} into an instance of {@link MongoTransactionOptions}.
+ *
+ * @param options never {@literal null}.
+ * @return never {@literal null}.
+ * @throws IllegalArgumentException for invalid options.
+ */
+ MongoTransactionOptions convert(Map options);
+}
diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/ReactiveMongoDatabaseFactory.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/ReactiveMongoDatabaseFactory.java
index ac50328917..f2a6714a95 100644
--- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/ReactiveMongoDatabaseFactory.java
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/ReactiveMongoDatabaseFactory.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2019 the original author or authors.
+ * Copyright 2016-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -31,6 +31,7 @@
*
* @author Mark Paluch
* @author Christoph Strobl
+ * @author Mathieu Ouellet
* @since 2.0
*/
public interface ReactiveMongoDatabaseFactory extends CodecRegistryProvider {
@@ -38,19 +39,19 @@ public interface ReactiveMongoDatabaseFactory extends CodecRegistryProvider {
/**
* Creates a default {@link MongoDatabase} instance.
*
- * @return
+ * @return never {@literal null}.
* @throws DataAccessException
*/
- MongoDatabase getMongoDatabase() throws DataAccessException;
+ Mono getMongoDatabase() throws DataAccessException;
/**
- * Creates a {@link MongoDatabase} instance to access the database with the given name.
+ * Obtain a {@link MongoDatabase} instance to access the database with the given name.
*
* @param dbName must not be {@literal null} or empty.
- * @return
+ * @return never {@literal null}.
* @throws DataAccessException
*/
- MongoDatabase getMongoDatabase(String dbName) throws DataAccessException;
+ Mono getMongoDatabase(String dbName) throws DataAccessException;
/**
* Exposes a shared {@link MongoExceptionTranslator}.
@@ -64,10 +65,7 @@ public interface ReactiveMongoDatabaseFactory extends CodecRegistryProvider {
*
* @return never {@literal null}.
*/
- @Override
- default CodecRegistry getCodecRegistry() {
- return getMongoDatabase().getCodecRegistry();
- }
+ CodecRegistry getCodecRegistry();
/**
* Obtain a {@link Mono} emitting a {@link ClientSession} for given {@link ClientSessionOptions options}.
diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/ReactiveMongoDatabaseUtils.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/ReactiveMongoDatabaseUtils.java
index 9067b74ece..3d1c2ee89c 100644
--- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/ReactiveMongoDatabaseUtils.java
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/ReactiveMongoDatabaseUtils.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2019 the original author or authors.
+ * Copyright 2019-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,7 +18,7 @@
import reactor.core.publisher.Mono;
import reactor.util.context.Context;
-import org.springframework.lang.Nullable;
+import org.jspecify.annotations.Nullable;
import org.springframework.transaction.NoTransactionException;
import org.springframework.transaction.reactive.ReactiveResourceSynchronization;
import org.springframework.transaction.reactive.TransactionSynchronization;
@@ -35,12 +35,12 @@
/**
* Helper class for managing reactive {@link MongoDatabase} instances via {@link ReactiveMongoDatabaseFactory}. Used for
* obtaining {@link ClientSession session bound} resources, such as {@link MongoDatabase} and {@link MongoCollection}
- * suitable for transactional usage.
- *
+ * suitable for transactional usage.
* Note: Intended for internal usage only.
*
* @author Mark Paluch
* @author Christoph Strobl
+ * @author Mathieu Ouellet
* @since 2.2
*/
public class ReactiveMongoDatabaseUtils {
@@ -73,8 +73,7 @@ public static Mono isTransactionActive(ReactiveMongoDatabaseFactory dat
/**
* Obtain the default {@link MongoDatabase database} form the given {@link ReactiveMongoDatabaseFactory factory} using
- * {@link SessionSynchronization#ON_ACTUAL_TRANSACTION native session synchronization}.
- *
+ * {@link SessionSynchronization#ON_ACTUAL_TRANSACTION native session synchronization}.
* Registers a {@link MongoSessionSynchronization MongoDB specific transaction synchronization} within the subscriber
* {@link Context} if {@link TransactionSynchronizationManager#isSynchronizationActive() synchronization is active}.
*
@@ -87,7 +86,7 @@ public static Mono getDatabase(ReactiveMongoDatabaseFactory facto
/**
* Obtain the default {@link MongoDatabase database} form the given {@link ReactiveMongoDatabaseFactory factory}.
- *
+ *
* Registers a {@link MongoSessionSynchronization MongoDB specific transaction synchronization} within the subscriber
* {@link Context} if {@link TransactionSynchronizationManager#isSynchronizationActive() synchronization is active}.
*
@@ -102,32 +101,32 @@ public static Mono getDatabase(ReactiveMongoDatabaseFactory facto
/**
* Obtain the {@link MongoDatabase database} with given name form the given {@link ReactiveMongoDatabaseFactory
- * factory} using {@link SessionSynchronization#ON_ACTUAL_TRANSACTION native session synchronization}.
- *
+ * factory} using {@link SessionSynchronization#ON_ACTUAL_TRANSACTION native session synchronization}.
* Registers a {@link MongoSessionSynchronization MongoDB specific transaction synchronization} within the subscriber
* {@link Context} if {@link TransactionSynchronizationManager#isSynchronizationActive() synchronization is active}.
*
- * @param dbName the name of the {@link MongoDatabase} to get.
+ * @param dbName the name of the {@link MongoDatabase} to get. If {@literal null} the default database of the
+ * {@link ReactiveMongoDatabaseFactory}.
* @param factory the {@link ReactiveMongoDatabaseFactory} to get the {@link MongoDatabase} from.
* @return the {@link MongoDatabase} that is potentially associated with a transactional {@link ClientSession}.
*/
- public static Mono getDatabase(String dbName, ReactiveMongoDatabaseFactory factory) {
+ public static Mono getDatabase(@Nullable String dbName, ReactiveMongoDatabaseFactory factory) {
return doGetMongoDatabase(dbName, factory, SessionSynchronization.ON_ACTUAL_TRANSACTION);
}
/**
* Obtain the {@link MongoDatabase database} with given name form the given {@link ReactiveMongoDatabaseFactory
- * factory}.
- *
+ * factory}.
* Registers a {@link MongoSessionSynchronization MongoDB specific transaction synchronization} within the subscriber
* {@link Context} if {@link TransactionSynchronizationManager#isSynchronizationActive() synchronization is active}.
*
- * @param dbName the name of the {@link MongoDatabase} to get.
+ * @param dbName the name of the {@link MongoDatabase} to get. If {@literal null} the default database of the *
+ * {@link ReactiveMongoDatabaseFactory}.
* @param factory the {@link ReactiveMongoDatabaseFactory} to get the {@link MongoDatabase} from.
* @param sessionSynchronization the synchronization to use. Must not be {@literal null}.
* @return the {@link MongoDatabase} that is potentially associated with a transactional {@link ClientSession}.
*/
- public static Mono getDatabase(String dbName, ReactiveMongoDatabaseFactory factory,
+ public static Mono getDatabase(@Nullable String dbName, ReactiveMongoDatabaseFactory factory,
SessionSynchronization sessionSynchronization) {
return doGetMongoDatabase(dbName, factory, sessionSynchronization);
}
@@ -135,21 +134,24 @@ public static Mono getDatabase(String dbName, ReactiveMongoDataba
private static Mono doGetMongoDatabase(@Nullable String dbName, ReactiveMongoDatabaseFactory factory,
SessionSynchronization sessionSynchronization) {
- Assert.notNull(factory, "DatabaseFactory must not be null!");
+ Assert.notNull(factory, "DatabaseFactory must not be null");
+
+ if (sessionSynchronization == SessionSynchronization.NEVER) {
+ return getMongoDatabaseOrDefault(dbName, factory);
+ }
return TransactionSynchronizationManager.forCurrentTransaction()
.filter(TransactionSynchronizationManager::isSynchronizationActive) //
.flatMap(synchronizationManager -> {
return doGetSession(synchronizationManager, factory, sessionSynchronization) //
- .map(it -> getMongoDatabaseOrDefault(dbName, factory.withSession(it)));
- })
- .onErrorResume(NoTransactionException.class,
- e -> Mono.fromSupplier(() -> getMongoDatabaseOrDefault(dbName, factory)))
- .defaultIfEmpty(getMongoDatabaseOrDefault(dbName, factory));
+ .flatMap(it -> getMongoDatabaseOrDefault(dbName, factory.withSession(it)));
+ }) //
+ .onErrorResume(NoTransactionException.class, e -> getMongoDatabaseOrDefault(dbName, factory))
+ .switchIfEmpty(getMongoDatabaseOrDefault(dbName, factory));
}
- private static MongoDatabase getMongoDatabaseOrDefault(@Nullable String dbName,
+ private static Mono getMongoDatabaseOrDefault(@Nullable String dbName,
ReactiveMongoDatabaseFactory factory) {
return StringUtils.hasText(dbName) ? factory.getMongoDatabase(dbName) : factory.getMongoDatabase();
}
@@ -210,19 +212,11 @@ private static class MongoSessionSynchronization
this.resourceHolder = resourceHolder;
}
- /*
- * (non-Javadoc)
- * @see org.springframework.transaction.reactive.ReactiveResourceSynchronization#shouldReleaseBeforeCompletion()
- */
@Override
protected boolean shouldReleaseBeforeCompletion() {
return false;
}
- /*
- * (non-Javadoc)
- * @see org.springframework.transaction.reactive.ReactiveResourceSynchronization#processResourceAfterCommit(java.lang.Object)
- */
@Override
protected Mono processResourceAfterCommit(ReactiveMongoResourceHolder resourceHolder) {
@@ -233,10 +227,6 @@ protected Mono processResourceAfterCommit(ReactiveMongoResourceHolder reso
return Mono.empty();
}
- /*
- * (non-Javadoc)
- * @see org.springframework.transaction.reactive.ReactiveResourceSynchronization#afterCompletion(int)
- */
@Override
public Mono afterCompletion(int status) {
@@ -252,10 +242,6 @@ public Mono afterCompletion(int status) {
});
}
- /*
- * (non-Javadoc)
- * @see org.springframework.transaction.reactive.ReactiveResourceSynchronization#releaseResource(java.lang.Object, java.lang.Object)
- */
@Override
protected Mono releaseResource(ReactiveMongoResourceHolder resourceHolder, Object resourceKey) {
diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/ReactiveMongoResourceHolder.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/ReactiveMongoResourceHolder.java
index cb19f15ba9..d01364b202 100644
--- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/ReactiveMongoResourceHolder.java
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/ReactiveMongoResourceHolder.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2019 the original author or authors.
+ * Copyright 2019-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -15,16 +15,15 @@
*/
package org.springframework.data.mongodb;
+import org.jspecify.annotations.Nullable;
import org.springframework.data.mongodb.core.ReactiveMongoTemplate;
-import org.springframework.lang.Nullable;
import org.springframework.transaction.support.ResourceHolderSupport;
import com.mongodb.reactivestreams.client.ClientSession;
/**
* MongoDB specific resource holder, wrapping a {@link ClientSession}. {@link ReactiveMongoTransactionManager} binds
- * instances of this class to the subscriber context.
- *
+ * instances of this class to the subscriber context.
* Note: Intended for internal usage only.
*
* @author Mark Paluch
@@ -42,7 +41,7 @@ class ReactiveMongoResourceHolder extends ResourceHolderSupport {
* Create a new {@link ReactiveMongoResourceHolder} for a given {@link ClientSession session}.
*
* @param session the associated {@link ClientSession}. Can be {@literal null}.
- * @param databaseFactory the associated {@link MongoDbFactory}. must not be {@literal null}.
+ * @param databaseFactory the associated {@link MongoDatabaseFactory}. must not be {@literal null}.
*/
ReactiveMongoResourceHolder(@Nullable ClientSession session, ReactiveMongoDatabaseFactory databaseFactory) {
@@ -99,12 +98,11 @@ boolean hasSession() {
* If the {@link ReactiveMongoResourceHolder} is {@link #hasSession() not already associated} with a
* {@link ClientSession} the given value is {@link #setSession(ClientSession) set} and returned, otherwise the current
* bound session is returned.
- *
+ *
* @param session
* @return
*/
- @Nullable
- public ClientSession setSessionIfAbsent(@Nullable ClientSession session) {
+ public @Nullable ClientSession setSessionIfAbsent(@Nullable ClientSession session) {
if (!hasSession()) {
setSession(session);
diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/ReactiveMongoTransactionManager.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/ReactiveMongoTransactionManager.java
index 9bd0fbfe06..4f293c8ed6 100644
--- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/ReactiveMongoTransactionManager.java
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/ReactiveMongoTransactionManager.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2019 the original author or authors.
+ * Copyright 2019-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,8 +17,8 @@
import reactor.core.publisher.Mono;
+import org.jspecify.annotations.Nullable;
import org.springframework.beans.factory.InitializingBean;
-import org.springframework.lang.Nullable;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionException;
import org.springframework.transaction.TransactionSystemException;
@@ -37,22 +37,19 @@
/**
* A {@link org.springframework.transaction.ReactiveTransactionManager} implementation that manages
* {@link com.mongodb.reactivestreams.client.ClientSession} based transactions for a single
- * {@link org.springframework.data.mongodb.ReactiveMongoDatabaseFactory}.
- *
+ * {@link org.springframework.data.mongodb.ReactiveMongoDatabaseFactory}.
* Binds a {@link ClientSession} from the specified
* {@link org.springframework.data.mongodb.ReactiveMongoDatabaseFactory} to the subscriber
- * {@link reactor.util.context.Context}.
- *
+ * {@link reactor.util.context.Context}.
* {@link org.springframework.transaction.TransactionDefinition#isReadOnly() Readonly} transactions operate on a
* {@link ClientSession} and enable causal consistency, and also {@link ClientSession#startTransaction() start},
* {@link com.mongodb.reactivestreams.client.ClientSession#commitTransaction() commit} or
- * {@link ClientSession#abortTransaction() abort} a transaction.
- *
+ * {@link ClientSession#abortTransaction() abort} a transaction.
* Application code is required to retrieve the {@link com.mongodb.reactivestreams.client.MongoDatabase} via
* {@link org.springframework.data.mongodb.ReactiveMongoDatabaseUtils#getDatabase(ReactiveMongoDatabaseFactory)} instead
* of a standard {@link org.springframework.data.mongodb.ReactiveMongoDatabaseFactory#getMongoDatabase()} call. Spring
* classes such as {@link org.springframework.data.mongodb.core.ReactiveMongoTemplate} use this strategy implicitly.
- *
+ *
* By default failure of a {@literal commit} operation raises a {@link TransactionSystemException}. You can override
* {@link #doCommit(TransactionSynchronizationManager, ReactiveMongoTransactionObject)} to implement the
* Retry Commit Operation
@@ -67,21 +64,25 @@
public class ReactiveMongoTransactionManager extends AbstractReactiveTransactionManager implements InitializingBean {
private @Nullable ReactiveMongoDatabaseFactory databaseFactory;
- private @Nullable TransactionOptions options;
+ private MongoTransactionOptions options;
+ private final MongoTransactionOptionsResolver transactionOptionsResolver;
/**
- * Create a new {@link ReactiveMongoTransactionManager} for bean-style usage.
- *
+ * Create a new {@link ReactiveMongoTransactionManager} for bean-style usage.
* Note:The {@link org.springframework.data.mongodb.ReactiveMongoDatabaseFactory db factory} has to
* be {@link #setDatabaseFactory(ReactiveMongoDatabaseFactory)} set} before using the instance. Use this constructor
* to prepare a {@link ReactiveMongoTransactionManager} via a {@link org.springframework.beans.factory.BeanFactory}.
- *
+ *
* Optionally it is possible to set default {@link TransactionOptions transaction options} defining
* {@link com.mongodb.ReadConcern} and {@link com.mongodb.WriteConcern}.
*
* @see #setDatabaseFactory(ReactiveMongoDatabaseFactory)
*/
- public ReactiveMongoTransactionManager() {}
+ public ReactiveMongoTransactionManager() {
+
+ this.transactionOptionsResolver = MongoTransactionOptionsResolver.defaultResolver();
+ this.options = MongoTransactionOptions.NONE;
+ }
/**
* Create a new {@link ReactiveMongoTransactionManager} obtaining sessions from the given
@@ -99,21 +100,36 @@ public ReactiveMongoTransactionManager(ReactiveMongoDatabaseFactory databaseFact
* starting a new transaction.
*
* @param databaseFactory must not be {@literal null}.
- * @param options can be {@literal null}.
+ * @param options can be {@literal null}. Will default {@link MongoTransactionOptions#NONE} if {@literal null}.
*/
public ReactiveMongoTransactionManager(ReactiveMongoDatabaseFactory databaseFactory,
@Nullable TransactionOptions options) {
+ this(databaseFactory, MongoTransactionOptionsResolver.defaultResolver(), MongoTransactionOptions.of(options));
+ }
+
+ /**
+ * Create a new {@link ReactiveMongoTransactionManager} obtaining sessions from the given
+ * {@link ReactiveMongoDatabaseFactory} applying the given {@link TransactionOptions options}, if present, when
+ * starting a new transaction.
+ *
+ * @param databaseFactory must not be {@literal null}.
+ * @param transactionOptionsResolver must not be {@literal null}.
+ * @param defaultTransactionOptions can be {@literal null}. Will default {@link MongoTransactionOptions#NONE} if
+ * {@literal null}.
+ * @since 4.3
+ */
+ public ReactiveMongoTransactionManager(ReactiveMongoDatabaseFactory databaseFactory,
+ MongoTransactionOptionsResolver transactionOptionsResolver,
+ @Nullable MongoTransactionOptions defaultTransactionOptions) {
- Assert.notNull(databaseFactory, "DatabaseFactory must not be null!");
+ Assert.notNull(databaseFactory, "DatabaseFactory must not be null");
+ Assert.notNull(transactionOptionsResolver, "MongoTransactionOptionsResolver must not be null");
this.databaseFactory = databaseFactory;
- this.options = options;
+ this.transactionOptionsResolver = transactionOptionsResolver;
+ this.options = defaultTransactionOptions != null ? defaultTransactionOptions : MongoTransactionOptions.NONE;
}
- /*
- * (non-Javadoc)
- * @see org.springframework.transaction.reactive.AbstractReactiveTransactionManager#doGetTransaction(org.springframework.transaction.reactive.TransactionSynchronizationManager)
- */
@Override
protected Object doGetTransaction(TransactionSynchronizationManager synchronizationManager)
throws TransactionException {
@@ -123,19 +139,11 @@ protected Object doGetTransaction(TransactionSynchronizationManager synchronizat
return new ReactiveMongoTransactionObject(resourceHolder);
}
- /*
- * (non-Javadoc)
- * @see org.springframework.transaction.reactive.AbstractReactiveTransactionManager#isExistingTransaction(java.lang.Object)
- */
@Override
protected boolean isExistingTransaction(Object transaction) throws TransactionException {
return extractMongoTransaction(transaction).hasResourceHolder();
}
- /*
- * (non-Javadoc)
- * @see org.springframework.transaction.reactive.AbstractReactiveTransactionManager#doBegin(org.springframework.transaction.reactive.TransactionSynchronizationManager, java.lang.Object, org.springframework.transaction.TransactionDefinition)
- */
@Override
protected Mono doBegin(TransactionSynchronizationManager synchronizationManager, Object transaction,
TransactionDefinition definition) throws TransactionException {
@@ -158,7 +166,9 @@ protected Mono doBegin(TransactionSynchronizationManager synchronizationMa
}).doOnNext(resourceHolder -> {
- mongoTransactionObject.startTransaction(options);
+ MongoTransactionOptions mongoTransactionOptions = transactionOptionsResolver.resolve(definition)
+ .mergeWith(options);
+ mongoTransactionObject.startTransaction(mongoTransactionOptions.toDriverOptions());
if (logger.isDebugEnabled()) {
logger.debug(String.format("Started transaction for session %s.", debugString(resourceHolder.getSession())));
@@ -175,10 +185,6 @@ protected Mono doBegin(TransactionSynchronizationManager synchronizationMa
});
}
- /*
- * (non-Javadoc)
- * @see org.springframework.transaction.reactive.AbstractReactiveTransactionManager#doSuspend(org.springframework.transaction.reactive.TransactionSynchronizationManager, java.lang.Object)
- */
@Override
protected Mono