diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml
new file mode 100644
index 000000000..6ede4f0c9
--- /dev/null
+++ b/.github/workflows/build.yaml
@@ -0,0 +1,27 @@
+name: CI Build
+
+on:
+ push:
+ branches: [ main ]
+
+jobs:
+ build:
+ name: Build project
+ runs-on: ubuntu-latest
+
+ steps:
+
+ - name: Check out sources
+ uses: actions/checkout@v4
+
+ - name: Set up JDK 17
+ uses: actions/setup-java@v4
+ with:
+ distribution: 'temurin'
+ java-version: 17
+ cache: 'maven'
+
+ - name: Build with Maven
+ env:
+ DEVELOCITY_ACCESS_KEY: ${{ secrets.GRADLE_ENTERPRISE_SECRET_ACCESS_KEY }}
+ run: ./mvnw clean verify -B
diff --git a/.gitignore b/.gitignore
index fd2270241..7cc5f4de1 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,8 +1,10 @@
.project
.classpath
.springBeans
+.develocity/
.settings/
target/
+.mvn/.gradle-enterprise
#IntelliJ Stuff
.idea
diff --git a/.mvn/extensions.xml b/.mvn/extensions.xml
new file mode 100644
index 000000000..1e3bb355f
--- /dev/null
+++ b/.mvn/extensions.xml
@@ -0,0 +1,8 @@
+
+
+
+ io.spring.develocity.conventions
+ develocity-conventions-maven-extension
+ 0.0.19
+
+
diff --git a/README.adoc b/README.adoc
index 9ce0564f8..43d26ddbd 100644
--- a/README.adoc
+++ b/README.adoc
@@ -1,4 +1,4 @@
-= Spring Data Examples
+= Spring Data Examples 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 - Examples"]
image:https://travis-ci.org/spring-projects/spring-data-examples.svg?branch=main[Build Status,link=https://travis-ci.org/spring-projects/spring-data-examples]
@@ -23,8 +23,14 @@ Local Elasticsearch instance must be running to run the tests.
== Spring Data JDBC
* `basic` - Basic usage of Spring Data JDBC.
+* `graalvm-native` - This example compiles a basic Spring Data JDBC application into a GraalVM native image.
+* `howto` - A collection of projects to go with the https://spring.io/blog/2021/09/09/spring-data-jdbc-how-to-use-custom-id-generation[Spring Data JDBC - How to blog posts].
* `immutables` - Showing Spring Data JDBC usage
with https://immutables.github.io/[Immutables]
+* `jmolecules` - Demonstrates the interaction of jMolecules with Spring Data JDBC.
+* `jooq` - Demonstrates how to use jOOQ and Spring Data JDBC together.
+* `mybatis` - Demonstrate how to use MyBatis to generate SQL for Spring Data JDBC.
+* `singlequeryloading` - Demonstrates how to enable Single Query Loading.
== Spring Data JPA
@@ -106,8 +112,8 @@ WARNING: If you're done using it, don't forget to shut it down!
== Miscellaneous
-* `bom` - Example project how to use the Spring Data release train bom in non-Spring-Boot
- scenarios.
+* `mongodb/fragment-spi` - Example project how to use Spring Data Fragment SPI to provide reusable custom extensions.
+* `bom` - Example project how to use the Spring Data release train bom in non-Spring-Boot scenarios.
* `map` - Example project to show how to use `Map`-backed repositories.
* `multi-store` - Example project to use both Spring Data MongoDB and Spring Data JPA in
one project.
diff --git a/bom/pom.xml b/bom/pom.xml
index 73e301f98..507efd36c 100644
--- a/bom/pom.xml
+++ b/bom/pom.xml
@@ -50,4 +50,27 @@
+
+
+
+
+ com.gradle
+ develocity-maven-extension
+
+
+
+
+ maven-surefire-plugin
+
+ these tests showcase Spring Data features and should always rerun
+
+
+
+
+
+
+
+
+
+
diff --git a/cassandra/example/src/main/java/example/springdata/cassandra/basic/BasicUserRepository.java b/cassandra/example/src/main/java/example/springdata/cassandra/basic/BasicUserRepository.java
index 842610747..a3fc0601b 100644
--- a/cassandra/example/src/main/java/example/springdata/cassandra/basic/BasicUserRepository.java
+++ b/cassandra/example/src/main/java/example/springdata/cassandra/basic/BasicUserRepository.java
@@ -18,6 +18,7 @@
import java.util.List;
import org.springframework.data.cassandra.repository.Query;
+import org.springframework.data.domain.Limit;
import org.springframework.data.repository.CrudRepository;
/**
@@ -55,4 +56,13 @@ public interface BasicUserRepository extends CrudRepository {
* @return
*/
List findUsersByLastnameStartsWith(String lastnamePrefix);
+
+ /**
+ * Same as {@link #findUsersByLastnameStartsWith(String)} but reducing the result size to a given {@link Limit}.
+ *
+ * @param lastnamePrefix
+ * @param maxResults the maximum number of results returned.
+ * @return
+ */
+ List findUsersByLastnameStartsWith(String lastnamePrefix, Limit maxResults);
}
diff --git a/cassandra/example/src/main/java/example/springdata/cassandra/basic/User.java b/cassandra/example/src/main/java/example/springdata/cassandra/basic/User.java
index 25df51a0a..96c05b186 100644
--- a/cassandra/example/src/main/java/example/springdata/cassandra/basic/User.java
+++ b/cassandra/example/src/main/java/example/springdata/cassandra/basic/User.java
@@ -43,4 +43,10 @@ public class User {
public User(Long id) {
this.setId(id);
}
+
+ public User(Long id, String firstname, String lastname) {
+ this.id = id;
+ this.firstname = firstname;
+ this.lastname = lastname;
+ }
}
diff --git a/cassandra/example/src/test/java/example/springdata/cassandra/basic/BasicUserRepositoryTests.java b/cassandra/example/src/test/java/example/springdata/cassandra/basic/BasicUserRepositoryTests.java
index 436b37680..20832d068 100644
--- a/cassandra/example/src/test/java/example/springdata/cassandra/basic/BasicUserRepositoryTests.java
+++ b/cassandra/example/src/test/java/example/springdata/cassandra/basic/BasicUserRepositoryTests.java
@@ -1,11 +1,11 @@
/*
- * Copyright 2013-2021 the original author or authors.
+ * Copyright 2023 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
+ * http://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,
@@ -21,11 +21,14 @@
import example.springdata.cassandra.util.CassandraKeyspace;
import example.springdata.cassandra.util.CassandraVersion;
+import java.util.stream.LongStream;
+
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.data.domain.Limit;
import org.springframework.data.util.Version;
import com.datastax.oss.driver.api.core.CqlSession;
@@ -109,7 +112,8 @@ void findByDerivedQueryMethodWithSASI() throws InterruptedException {
assumeThat(CassandraVersion.getReleaseVersion(session).isGreaterThanOrEqualTo(CASSANDRA_3_4)).isTrue();
- session.execute("CREATE CUSTOM INDEX ON users (lname) USING 'org.apache.cassandra.index.sasi.SASIIndex';");
+ session.execute(
+ "CREATE CUSTOM INDEX IF NOT EXISTS users_lname_idx_1 ON users (lname) USING 'org.apache.cassandra.index.sasi.SASIIndex';");
/*
Cassandra secondary indexes are created in the background without the possibility to check
whether they are available or not. So we are forced to just wait. *sigh*
@@ -120,4 +124,25 @@ void findByDerivedQueryMethodWithSASI() throws InterruptedException {
assertThat(repository.findUsersByLastnameStartsWith("last")).contains(user);
}
+
+ /**
+ * Spring Data Cassandra supports {@code Limit} to reduce the number of returned results.
+ */
+ @Test
+ void limitResultSize() throws InterruptedException {
+
+ assumeThat(CassandraVersion.getReleaseVersion(session).isGreaterThanOrEqualTo(CASSANDRA_3_4)).isTrue();
+
+ session.execute(
+ "CREATE CUSTOM INDEX IF NOT EXISTS users_lname_idx_1 ON users (lname) USING 'org.apache.cassandra.index.sasi.SASIIndex';");
+ /*
+ Cassandra secondary indexes are created in the background without the possibility to check
+ whether they are available or not. So we are forced to just wait. *sigh*
+ */
+ Thread.sleep(1000);
+
+ LongStream.range(0, 10).forEach(id -> repository.save(new User(id, user.getFirstname(), user.getLastname())));
+
+ assertThat(repository.findUsersByLastnameStartsWith("last", Limit.of(5))).hasSize(5);
+ }
}
diff --git a/cassandra/reactive/src/main/java/example/springdata/cassandra/people/ReactivePersonRepository.java b/cassandra/reactive/src/main/java/example/springdata/cassandra/people/ReactivePersonRepository.java
index 6c8a38402..130781be3 100644
--- a/cassandra/reactive/src/main/java/example/springdata/cassandra/people/ReactivePersonRepository.java
+++ b/cassandra/reactive/src/main/java/example/springdata/cassandra/people/ReactivePersonRepository.java
@@ -15,6 +15,7 @@
*/
package example.springdata.cassandra.people;
+import org.springframework.data.domain.Limit;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
@@ -36,6 +37,15 @@ public interface ReactivePersonRepository extends ReactiveCrudRepository findByLastname(String lastname);
+ /**
+ * Derived query selecting by {@code lastname} reducing the result size to a given {@link Limit}.
+ *
+ * @param lastname
+ * @param maxResults the maximum number of results returned.
+ * @return
+ */
+ Flux findByLastname(String lastname, Limit maxResults);
+
/**
* String query selecting one entity.
*
diff --git a/cassandra/reactive/src/test/java/example/springdata/cassandra/people/ReactiveCassandraTemplateIntegrationTest.java b/cassandra/reactive/src/test/java/example/springdata/cassandra/people/ReactiveCassandraTemplateIntegrationTest.java
index af1858b8b..62cfc5148 100644
--- a/cassandra/reactive/src/test/java/example/springdata/cassandra/people/ReactiveCassandraTemplateIntegrationTest.java
+++ b/cassandra/reactive/src/test/java/example/springdata/cassandra/people/ReactiveCassandraTemplateIntegrationTest.java
@@ -48,7 +48,9 @@ void setUp() {
new Person("Jesse", "Pinkman", 27))) //
.flatMap(template::insert);
- StepVerifier.create(truncateAndInsert).expectNextCount(4).verifyComplete();
+ truncateAndInsert.as(StepVerifier::create) //
+ .expectNextCount(4) //
+ .verifyComplete();
}
/**
@@ -67,6 +69,8 @@ void shouldInsertAndCountData() {
.flatMap(v -> template.count(Person.class)) //
.doOnNext(System.out::println);
- StepVerifier.create(saveAndCount).expectNext(6L).verifyComplete();
+ saveAndCount.as(StepVerifier::create) //
+ .expectNext(6L) //
+ .verifyComplete();
}
}
diff --git a/cassandra/reactive/src/test/java/example/springdata/cassandra/people/ReactivePersonRepositoryIntegrationTest.java b/cassandra/reactive/src/test/java/example/springdata/cassandra/people/ReactivePersonRepositoryIntegrationTest.java
index bf17e8897..ca9f8a3b4 100644
--- a/cassandra/reactive/src/test/java/example/springdata/cassandra/people/ReactivePersonRepositoryIntegrationTest.java
+++ b/cassandra/reactive/src/test/java/example/springdata/cassandra/people/ReactivePersonRepositoryIntegrationTest.java
@@ -16,6 +16,7 @@
package example.springdata.cassandra.people;
import example.springdata.cassandra.util.CassandraKeyspace;
+import org.springframework.data.domain.Limit;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
@@ -49,7 +50,9 @@ void setUp() {
new Person("Saul", "Goodman", 42), //
new Person("Jesse", "Pinkman", 27))));
- StepVerifier.create(deleteAndInsert).expectNextCount(4).verifyComplete();
+ deleteAndInsert.as(StepVerifier::create) //
+ .expectNextCount(4) //
+ .verifyComplete();
}
/**
@@ -66,7 +69,9 @@ void shouldInsertAndCountData() {
.flatMap(v -> repository.count()) //
.doOnNext(System.out::println);
- StepVerifier.create(saveAndCount).expectNext(6L).verifyComplete();
+ saveAndCount.as(StepVerifier::create) //
+ .expectNext(6L) //
+ .verifyComplete();
}
/**
@@ -76,7 +81,7 @@ void shouldInsertAndCountData() {
@Test
void shouldPerformConversionBeforeResultProcessing() {
- StepVerifier.create(repository.findAll().doOnNext(System.out::println)) //
+ repository.findAll().doOnNext(System.out::println).as(StepVerifier::create) //
.expectNextCount(4) //
.verifyComplete();
}
@@ -86,7 +91,21 @@ void shouldPerformConversionBeforeResultProcessing() {
*/
@Test
void shouldQueryDataWithQueryDerivation() {
- StepVerifier.create(repository.findByLastname("White")).expectNextCount(2).verifyComplete();
+
+ repository.findByLastname("White").as(StepVerifier::create) //
+ .expectNextCount(2) //
+ .verifyComplete();
+ }
+
+ /**
+ * Fetch data limiting result size.
+ */
+ @Test
+ void limitResultSize() {
+
+ repository.findByLastname("White", Limit.of(1)).as(StepVerifier::create) //
+ .expectNextCount(1) //
+ .verifyComplete();
}
/**
@@ -94,7 +113,10 @@ void shouldQueryDataWithQueryDerivation() {
*/
@Test
void shouldQueryDataWithStringQuery() {
- StepVerifier.create(repository.findByFirstnameInAndLastname("Walter", "White")).expectNextCount(1).verifyComplete();
+
+ repository.findByFirstnameInAndLastname("Walter", "White").as(StepVerifier::create) //
+ .expectNextCount(1) //
+ .verifyComplete();
}
/**
@@ -102,7 +124,10 @@ void shouldQueryDataWithStringQuery() {
*/
@Test
void shouldQueryDataWithDeferredQueryDerivation() {
- StepVerifier.create(repository.findByLastname(Mono.just("White"))).expectNextCount(2).verifyComplete();
+
+ repository.findByLastname(Mono.just("White")).as(StepVerifier::create) //
+ .expectNextCount(2) //
+ .verifyComplete();
}
/**
@@ -111,7 +136,7 @@ void shouldQueryDataWithDeferredQueryDerivation() {
@Test
void shouldQueryDataWithMixedDeferredQueryDerivation() {
- StepVerifier.create(repository.findByFirstnameAndLastname(Mono.just("Walter"), "White")) //
+ repository.findByFirstnameAndLastname(Mono.just("Walter"), "White").as(StepVerifier::create) //
.expectNextCount(1) //
.verifyComplete();
}
diff --git a/cassandra/util/pom.xml b/cassandra/util/pom.xml
index adf66b173..3b72a57ef 100644
--- a/cassandra/util/pom.xml
+++ b/cassandra/util/pom.xml
@@ -31,7 +31,7 @@
- com.datastax.oss
+ org.apache.cassandrajava-driver-core
diff --git a/elasticsearch/pom.xml b/elasticsearch/pom.xml
index 4de2433e7..bc4fd91c6 100644
--- a/elasticsearch/pom.xml
+++ b/elasticsearch/pom.xml
@@ -57,4 +57,27 @@
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+
+
+ org.projectlombok
+ lombok
+ ${lombok.version}
+
+
+ org.apache.logging.log4j
+ log4j-core
+ ${log4j2.version}
+
+
+
+
+
+
+
diff --git a/jdbc/graalvm-native/README.adoc b/jdbc/graalvm-native/README.adoc
index be3f31b46..3aa10ed9b 100644
--- a/jdbc/graalvm-native/README.adoc
+++ b/jdbc/graalvm-native/README.adoc
@@ -1,6 +1,6 @@
== Spring Data JDBC - GraalVM native image
-This example compiles a basic Spring Data JDBC appication into a GraalVM native image.
+This example compiles a basic Spring Data JDBC application into a GraalVM native image.
=== Install GraalVM & native image tooling
diff --git a/jdbc/howto/schema-generation/pom.xml b/jdbc/howto/schema-generation/pom.xml
index 29f5c92eb..0b065532d 100644
--- a/jdbc/howto/schema-generation/pom.xml
+++ b/jdbc/howto/schema-generation/pom.xml
@@ -18,10 +18,6 @@
https://projects.spring.io/spring-data-jdbc2023
-
- 2023.1.0-M2
-
-
diff --git a/jdbc/howto/selectiveupdate/src/main/java/example.springdata/jdbc/howto/selectiveupdate/PartyHatRepositoryImpl.java b/jdbc/howto/selectiveupdate/src/main/java/example.springdata/jdbc/howto/selectiveupdate/PartyHatRepositoryImpl.java
index 3513338f6..b0f822c50 100644
--- a/jdbc/howto/selectiveupdate/src/main/java/example.springdata/jdbc/howto/selectiveupdate/PartyHatRepositoryImpl.java
+++ b/jdbc/howto/selectiveupdate/src/main/java/example.springdata/jdbc/howto/selectiveupdate/PartyHatRepositoryImpl.java
@@ -17,6 +17,7 @@
import org.springframework.dao.OptimisticLockingFailureException;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations;
+import org.springframework.transaction.annotation.Transactional;
import java.util.HashMap;
import java.util.Map;
@@ -35,6 +36,7 @@ public PartyHatRepositoryImpl(NamedParameterJdbcOperations template) {
this.template = template;
}
+ @Transactional
@Override
public void addPartyHat(Minion minion) {
diff --git a/jdbc/howto/selectiveupdate/src/main/java/example.springdata/jdbc/howto/selectiveupdate/SelectiveUpdateApplication.java b/jdbc/howto/selectiveupdate/src/main/java/example.springdata/jdbc/howto/selectiveupdate/SelectiveUpdateApplication.java
index 9e1b01dd8..d1e005f01 100644
--- a/jdbc/howto/selectiveupdate/src/main/java/example.springdata/jdbc/howto/selectiveupdate/SelectiveUpdateApplication.java
+++ b/jdbc/howto/selectiveupdate/src/main/java/example.springdata/jdbc/howto/selectiveupdate/SelectiveUpdateApplication.java
@@ -17,7 +17,6 @@
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
-import org.springframework.cache.annotation.EnableCaching;
@SpringBootApplication
class SelectiveUpdateApplication {
diff --git a/jdbc/howto/selectiveupdate/src/test/java/example/springdata/jdbc/howto/selectiveupdate/SelectiveUpdateApplicationTests.java b/jdbc/howto/selectiveupdate/src/test/java/example/springdata/jdbc/howto/selectiveupdate/SelectiveUpdateApplicationTests.java
index 5d6943496..041679138 100644
--- a/jdbc/howto/selectiveupdate/src/test/java/example/springdata/jdbc/howto/selectiveupdate/SelectiveUpdateApplicationTests.java
+++ b/jdbc/howto/selectiveupdate/src/test/java/example/springdata/jdbc/howto/selectiveupdate/SelectiveUpdateApplicationTests.java
@@ -58,7 +58,7 @@ void turnPurpleByDirectUpdate() {
Minion bob2 = minions.findById(bob.id).orElseThrow();
- assertThat(bob2.toys).containsExactly(bob.toys.toArray(new Toy[] {}));
+ assertThat(bob2.toys).containsExactlyElementsOf(bob.toys);
assertThat(bob2.name).isEqualTo("Bob");
assertThat(bob2.color).isEqualTo(Color.PURPLE);
}
@@ -81,4 +81,23 @@ void grantPartyHat() {
assertThatExceptionOfType(OptimisticLockingFailureException.class).isThrownBy(() -> minions.addPartyHat(bob));
}
+ @Test
+ void cannotGrantPartyHatWhenOutOfSync() {
+
+ Minion bob = new Minion("Bob").addToy(new Toy("Tiger Duck")).addToy(new Toy("Security blanket"));
+ minions.save(bob);
+ minions.turnPurple(bob.id);
+
+ assertThat(bob.color).isEqualTo(Color.YELLOW);
+ assertThat(bob.version).isOne();
+ assertThatExceptionOfType(OptimisticLockingFailureException.class).isThrownBy(() -> minions.addPartyHat(bob));
+
+ Minion bob2 = minions.findById(bob.id).orElseThrow();
+
+ assertThat(bob2.name).isEqualTo("Bob");
+ assertThat(bob2.color).isEqualTo(Color.PURPLE);
+ assertThat(bob2.version).isEqualTo(bob.version + 1);
+ assertThat(bob2.toys).extracting("name").containsExactlyInAnyOrder("Tiger Duck", "Security blanket");
+ }
+
}
diff --git a/jdbc/immutables/pom.xml b/jdbc/immutables/pom.xml
index 8527b31dc..8d8b00297 100644
--- a/jdbc/immutables/pom.xml
+++ b/jdbc/immutables/pom.xml
@@ -15,14 +15,36 @@
Spring Data JDBC - Usage with ImmutablesSample project demonstrating Spring Data JDBC features
+
+ 2.8.8
+
+
org.immutablesvalue
- 2.8.8
+ ${immutables.version}provided
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+
+
+ org.immutables
+ value
+ ${immutables.version}
+
+
+
+
+
+
+
diff --git a/jdbc/pom.xml b/jdbc/pom.xml
index 742457e16..81dcfbe55 100644
--- a/jdbc/pom.xml
+++ b/jdbc/pom.xml
@@ -22,6 +22,7 @@
immutablesjmoleculesjooq
+ singlequeryloadinggraalvm-native
diff --git a/jdbc/singlequeryloading/pom.xml b/jdbc/singlequeryloading/pom.xml
new file mode 100644
index 000000000..c24bd8e13
--- /dev/null
+++ b/jdbc/singlequeryloading/pom.xml
@@ -0,0 +1,33 @@
+
+ 4.0.0
+
+ singlequeryloading
+
+
+ org.springframework.data.examples
+ spring-data-jdbc-examples
+ 2.0.0.BUILD-SNAPSHOT
+ ../pom.xml
+
+
+ Spring Data JDBC - Demonstration of Single Query Loading
+ Sample project demonstrating Single Query Loading with Spring Data JDBC
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-data-jdbc
+
+
+ org.testcontainers
+ postgresql
+
+
+ org.postgresql
+ postgresql
+
+
+
diff --git a/jdbc/singlequeryloading/src/main/java/example/springdata/jdbc/singlequeryloading/Cat.java b/jdbc/singlequeryloading/src/main/java/example/springdata/jdbc/singlequeryloading/Cat.java
new file mode 100644
index 000000000..48e3e1f23
--- /dev/null
+++ b/jdbc/singlequeryloading/src/main/java/example/springdata/jdbc/singlequeryloading/Cat.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2023 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 example.springdata.jdbc.singlequeryloading;
+
+/**
+ * A simple entity for use in a collection of {@link PetOwner}.
+ *
+ * @author Jens Schauder
+ */
+record Cat(String name) {
+}
diff --git a/jdbc/singlequeryloading/src/main/java/example/springdata/jdbc/singlequeryloading/Config.java b/jdbc/singlequeryloading/src/main/java/example/springdata/jdbc/singlequeryloading/Config.java
new file mode 100644
index 000000000..b23133d84
--- /dev/null
+++ b/jdbc/singlequeryloading/src/main/java/example/springdata/jdbc/singlequeryloading/Config.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2023 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
+ *
+ * http://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 example.springdata.jdbc.singlequeryloading;
+
+import java.util.Optional;
+
+import org.springframework.boot.SpringBootConfiguration;
+import org.springframework.data.jdbc.core.convert.JdbcCustomConversions;
+import org.springframework.data.jdbc.core.mapping.JdbcMappingContext;
+import org.springframework.data.jdbc.repository.config.AbstractJdbcConfiguration;
+import org.springframework.data.jdbc.repository.config.EnableJdbcRepositories;
+import org.springframework.data.relational.RelationalManagedTypes;
+import org.springframework.data.relational.core.mapping.NamingStrategy;
+
+/**
+ * Spring application context configuration that enables Single Query Loading.
+ *
+ * @author Jens Schauder
+ */
+@SpringBootConfiguration
+@EnableJdbcRepositories
+public class Config extends AbstractJdbcConfiguration {
+
+ @Override
+ public JdbcMappingContext jdbcMappingContext(Optional namingStrategy,
+ JdbcCustomConversions customConversions, RelationalManagedTypes jdbcManagedTypes) {
+
+ JdbcMappingContext jdbcMappingContext = super.jdbcMappingContext(namingStrategy, customConversions,
+ jdbcManagedTypes);
+ jdbcMappingContext.setSingleQueryLoadingEnabled(true);
+ return jdbcMappingContext;
+ }
+}
diff --git a/jdbc/singlequeryloading/src/main/java/example/springdata/jdbc/singlequeryloading/Dog.java b/jdbc/singlequeryloading/src/main/java/example/springdata/jdbc/singlequeryloading/Dog.java
new file mode 100644
index 000000000..450efe97e
--- /dev/null
+++ b/jdbc/singlequeryloading/src/main/java/example/springdata/jdbc/singlequeryloading/Dog.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2023 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 example.springdata.jdbc.singlequeryloading;
+
+/**
+ * A simple entity for use in a collection of {@link PetOwner}.
+ *
+ * @author Jens Schauder
+ */
+record Dog(String name) {
+}
\ No newline at end of file
diff --git a/jdbc/singlequeryloading/src/main/java/example/springdata/jdbc/singlequeryloading/Fish.java b/jdbc/singlequeryloading/src/main/java/example/springdata/jdbc/singlequeryloading/Fish.java
new file mode 100644
index 000000000..a2f41f651
--- /dev/null
+++ b/jdbc/singlequeryloading/src/main/java/example/springdata/jdbc/singlequeryloading/Fish.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2023 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 example.springdata.jdbc.singlequeryloading;
+
+/**
+ * A simple entity for use in a collection of {@link PetOwner}.
+ *
+ * @author Jens Schauder
+ */
+record Fish(String name) {
+}
diff --git a/jdbc/singlequeryloading/src/main/java/example/springdata/jdbc/singlequeryloading/PetOwner.java b/jdbc/singlequeryloading/src/main/java/example/springdata/jdbc/singlequeryloading/PetOwner.java
new file mode 100644
index 000000000..bad882cc0
--- /dev/null
+++ b/jdbc/singlequeryloading/src/main/java/example/springdata/jdbc/singlequeryloading/PetOwner.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2023 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
+ *
+ * http://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 example.springdata.jdbc.singlequeryloading;
+
+import java.util.List;
+import java.util.Objects;
+
+import org.springframework.data.annotation.Id;
+
+/**
+ * An aggregate with mutliple collections.
+ *
+ * @author Jens Schauder
+ */
+class PetOwner {
+
+ @Id Long Id;
+
+ String name;
+
+ List dogs;
+
+ List cats;
+
+ List fish;
+
+ public PetOwner(String name, List cats, List dogs, List fish) {
+
+ this.name = name;
+ this.cats = cats;
+ this.dogs = dogs;
+ this.fish = fish;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o)
+ return true;
+ if (o == null || getClass() != o.getClass())
+ return false;
+ PetOwner petOwner = (PetOwner) o;
+ return Objects.equals(Id, petOwner.Id) && Objects.equals(name, petOwner.name) && Objects.equals(dogs, petOwner.dogs)
+ && Objects.equals(cats, petOwner.cats) && Objects.equals(fish, petOwner.fish);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(Id, name, dogs, cats, fish);
+ }
+}
diff --git a/jdbc/singlequeryloading/src/main/java/example/springdata/jdbc/singlequeryloading/PetOwnerRepository.java b/jdbc/singlequeryloading/src/main/java/example/springdata/jdbc/singlequeryloading/PetOwnerRepository.java
new file mode 100644
index 000000000..00f2084f4
--- /dev/null
+++ b/jdbc/singlequeryloading/src/main/java/example/springdata/jdbc/singlequeryloading/PetOwnerRepository.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2023 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
+ *
+ * http://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 example.springdata.jdbc.singlequeryloading;
+
+import org.springframework.data.repository.CrudRepository;
+
+/**
+ * Repository to access {@link PetOwner} instances.
+ *
+ * @author Jens Schauder
+ */
+interface PetOwnerRepository extends CrudRepository {}
diff --git a/jdbc/singlequeryloading/src/main/resources/application.properties b/jdbc/singlequeryloading/src/main/resources/application.properties
new file mode 100644
index 000000000..d467e12d4
--- /dev/null
+++ b/jdbc/singlequeryloading/src/main/resources/application.properties
@@ -0,0 +1,3 @@
+spring.datasource.url=jdbc:tc:postgresql:16.0:///test
+logging.level.org.springframework.jdbc.core.JdbcTemplate=DEBUG
+spring.sql.init.mode=always
\ No newline at end of file
diff --git a/jdbc/singlequeryloading/src/main/resources/schema.sql b/jdbc/singlequeryloading/src/main/resources/schema.sql
new file mode 100644
index 000000000..b398d1147
--- /dev/null
+++ b/jdbc/singlequeryloading/src/main/resources/schema.sql
@@ -0,0 +1,27 @@
+CREATE TABLE PET_OWNER
+(
+ ID SERIAL PRIMARY KEY,
+ NAME VARCHAR(255)
+);
+
+CREATE TABLE CAT
+(
+ PET_OWNER INT,
+ PET_OWNER_KEY INT,
+ NAME VARCHAR(255)
+);
+
+CREATE TABLE DOG
+(
+ PET_OWNER INT,
+ PET_OWNER_KEY INT,
+ NAME VARCHAR(255)
+);
+
+CREATE TABLE FISH
+(
+ PET_OWNER INT,
+ PET_OWNER_KEY INT,
+ NAME VARCHAR(255)
+);
+
diff --git a/jdbc/singlequeryloading/src/test/java/example/springdata/jdbc/singlequeryloading/SingleQueryLoadingApplicationTests.java b/jdbc/singlequeryloading/src/test/java/example/springdata/jdbc/singlequeryloading/SingleQueryLoadingApplicationTests.java
new file mode 100644
index 000000000..6733eca4e
--- /dev/null
+++ b/jdbc/singlequeryloading/src/test/java/example/springdata/jdbc/singlequeryloading/SingleQueryLoadingApplicationTests.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2023 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
+ *
+ * http://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 example.springdata.jdbc.singlequeryloading;
+
+import static org.assertj.core.api.Assertions.*;
+import static org.springframework.data.relational.core.query.Criteria.*;
+import static org.springframework.data.relational.core.query.Query.*;
+
+import java.util.List;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase;
+import org.springframework.boot.test.autoconfigure.jdbc.JdbcTest;
+import org.springframework.data.jdbc.core.JdbcAggregateTemplate;
+
+/**
+ * Run tests demonstrating the use of Single Query Loading. You'll have to observe the executed queries.
+ *
+ * @author Jens Schauder
+ */
+@JdbcTest
+@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
+class SingleQueryLoadingApplicationTests {
+
+ @Autowired PetOwnerRepository petOwners;
+ @Autowired JdbcAggregateTemplate template;
+
+ private PetOwner emil;
+ private PetOwner marry;
+
+ @BeforeEach
+ void setup() {
+
+ petOwners.deleteAll();
+
+ emil = petOwners.save(new PetOwner("Emil", //
+ List.of(new Cat("Edgar"), new Cat("Einstein"), new Cat("Elliot"), new Cat("Elton"), new Cat("Evan")), //
+ List.of(new Dog("Eric"), new Dog("Eddie"), new Dog("Eke"), new Dog("Echo")), //
+ List.of(new Fish("Floaty Mc Floatface")) //
+ ));
+
+ marry = petOwners.save(new PetOwner("Marry", List.of(new Cat("Mars"), new Cat("Maverick"), new Cat("Max")), //
+ List.of(new Dog("Molly"), new Dog("Murphy"), new Dog("Madison"), new Dog("Macie")), //
+ List.of(new Fish("Mahi Mahi"), new Fish("Mr. Limpet")) //
+ ));
+ }
+
+ @Test
+ void loadById() {
+
+ PetOwner emilReloaded = petOwners.findById(emil.Id).orElseThrow();
+
+ assertThat(emilReloaded).isEqualTo(emil);
+ }
+
+ @Test
+ void loadByNameUsingTemplate() {
+
+ List marries = (List) template.findAll(query(where("name").is("Marry")), PetOwner.class);
+
+ assertThat(marries).containsExactly(marry);
+ }
+
+}
diff --git a/jpa/aot-optimization/pom.xml b/jpa/aot-optimization/pom.xml
new file mode 100644
index 000000000..67ea47db1
--- /dev/null
+++ b/jpa/aot-optimization/pom.xml
@@ -0,0 +1,97 @@
+
+
+ 4.0.0
+
+ org.springframework.data.examples
+ spring-data-jpa-examples
+ 2.0.0.BUILD-SNAPSHOT
+
+
+ org.example
+ spring-data-jpa-aot-optimization
+
+
+ UTF-8
+ 7.0.0.CR2
+ 2025.1.0-M3
+
+
+
+
+ org.jspecify
+ jspecify
+ 1.0.0
+
+
+
+ jakarta.persistence
+ jakarta.persistence-api
+ 3.2.0
+
+
+
+ com.querydsl
+ querydsl-jpa
+ 5.1.0
+ jakarta
+
+
+
+ com.querydsl
+ querydsl-apt
+ 5.1.0
+ jakarta
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+
+
+ com.querydsl
+ querydsl-jpa
+ 5.1.0
+ jakarta
+
+
+ com.querydsl
+ querydsl-apt
+ 5.1.0
+ jakarta
+
+
+ jakarta.persistence
+ jakarta.persistence-api
+ 3.2.0
+
+
+
+
+ target/generated-test-sources
+
+ target/generated-sources
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+ process-aot
+
+ process-aot
+
+
+
+
+
+
+
+
diff --git a/jpa/aot-optimization/src/main/java/example/springdata/aot/AotJpaApp.java b/jpa/aot-optimization/src/main/java/example/springdata/aot/AotJpaApp.java
new file mode 100644
index 000000000..66235ab8c
--- /dev/null
+++ b/jpa/aot-optimization/src/main/java/example/springdata/aot/AotJpaApp.java
@@ -0,0 +1,31 @@
+/*
+ * 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
+ *
+ * http://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 example.springdata.aot;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+/**
+ * @author Christoph Strobl
+ */
+@SpringBootApplication
+public class AotJpaApp {
+
+ public static void main(String[] args) {
+ SpringApplication.run(AotJpaApp.class, args);
+ }
+
+}
diff --git a/jpa/aot-optimization/src/main/java/example/springdata/aot/CLR.java b/jpa/aot-optimization/src/main/java/example/springdata/aot/CLR.java
new file mode 100644
index 000000000..8272939a9
--- /dev/null
+++ b/jpa/aot-optimization/src/main/java/example/springdata/aot/CLR.java
@@ -0,0 +1,125 @@
+/*
+ * 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
+ *
+ * http://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 example.springdata.aot;
+
+import java.util.List;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.CommandLineRunner;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.PageRequest;
+import org.springframework.data.domain.Slice;
+import org.springframework.stereotype.Component;
+
+/**
+ * @author Christoph Strobl
+ * @since 2025/01
+ */
+@Component
+public class CLR implements CommandLineRunner {
+
+ @Autowired UserRepository repository;
+
+ @Override
+ public void run(String... args) throws Exception {
+
+ User luke = new User("id-1", "luke");
+ luke.setFirstname("Luke");
+ luke.setLastname("Skywalker");
+ // Post lukeP1 = new Post("I have a bad feeling about this.");
+ // em.persist(lukeP1);
+ // luke.setPosts(List.of(lukeP1));
+
+ User leia = new User("id-2", "leia");
+ leia.setFirstname("Leia");
+ leia.setLastname("Organa");
+
+ User han = new User("id-3", "han");
+ han.setFirstname("Han");
+ han.setLastname("Solo");
+ // Post hanP1 = new Post("It's the ship that made the Kessel Run in less than 12 Parsecs.");
+ // em.persist(hanP1);
+ // han.setPosts(List.of(hanP1));
+
+ User chewbacca = new User("id-4", "chewbacca");
+ User yoda = new User("id-5", "yoda");
+ Post yodaP1 = new Post("Do. Or do not. There is no try.");
+ Post yodaP2 = new Post(
+ "Decide you must, how to serve them best. If you leave now, help them you could; but you would destroy all for which they have fought, and suffered.");
+ // em.persist(yodaP1);
+ // em.persist(yodaP2);
+ // yoda.setPosts(List.of(yodaP1, yodaP2));
+
+ User vader = new User("id-6", "vader");
+ vader.setFirstname("Anakin");
+ vader.setLastname("Skywalker");
+ // Post vaderP1 = new Post("I am your father");
+ // em.persist(vaderP1);
+ // vader.setPosts(List.of(vaderP1));
+
+ User kylo = new User("id-7", "kylo");
+ kylo.setFirstname("Ben");
+ kylo.setLastname("Solo");
+
+ repository.saveAll(List.of(luke, leia, han, chewbacca, yoda, vader, kylo));
+
+ System.out.println("------- annotated multi -------");
+ System.out.println(repository.usersWithUsernamesStartingWith("l"));
+
+ System.out.println("------- derived single -------");
+ System.out.println(repository.findUserByUsername("yoda"));
+
+ // System.out.println("------- derived nested.path -------");
+ // System.out.println(repository.findUserByPostsMessageLike("father"));
+
+ System.out.println("------- derived optional -------");
+ System.out.println(repository.findOptionalUserByUsername("yoda"));
+
+ System.out.println("------- derived count -------");
+ Long count = repository.countUsersByLastnameLike("Sky");
+ System.out.println("user count " + count);
+
+ System.out.println("------- derived exists -------");
+ Boolean exists = repository.existsByUsername("vader");
+ System.out.println("user exists " + exists);
+
+ System.out.println("------- derived multi -------");
+ System.out.println(repository.findUserByLastnameStartingWith("Sky"));
+
+ System.out.println("------- derived sorted -------");
+ System.out.println(repository.findUserByLastnameStartingWithOrderByFirstname("Sky"));
+
+ System.out.println("------- derived page -------");
+ Page page0 = repository.findUserByLastnameStartingWith("S", PageRequest.of(0, 2));
+ System.out.println("page0: " + page0);
+ System.out.println("page0.content: " + page0.getContent());
+
+ Page page1 = repository.findUserByLastnameStartingWith("S", PageRequest.of(1, 2));
+ System.out.println("page1: " + page1);
+ System.out.println("page1.content: " + page1.getContent());
+
+ System.out.println("------- derived slice -------");
+ Slice slice0 = repository.findUserByUsernameAfter("luke", PageRequest.of(0, 2));
+ System.out.println("slice0: " + slice0);
+ System.out.println("slice0.content: " + slice0.getContent());
+
+ System.out.println("------- derived top -------");
+ System.out.println(repository.findTop2UsersByLastnameStartingWith("S"));
+
+ // System.out.println("------- derived with fields -------");
+ // System.out.println(repository.findJustUsernameBy());
+ }
+}
diff --git a/jpa/aot-optimization/src/main/java/example/springdata/aot/Post.java b/jpa/aot-optimization/src/main/java/example/springdata/aot/Post.java
new file mode 100644
index 000000000..6c6e3e82d
--- /dev/null
+++ b/jpa/aot-optimization/src/main/java/example/springdata/aot/Post.java
@@ -0,0 +1,66 @@
+/*
+ * 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
+ *
+ * http://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 example.springdata.aot;
+
+import jakarta.persistence.Entity;
+import jakarta.persistence.GeneratedValue;
+import jakarta.persistence.Id;
+
+import java.time.Instant;
+import java.time.temporal.ChronoUnit;
+import java.util.Random;
+
+/**
+ * @author Christoph Strobl
+ * @since 2025/01
+ */
+@Entity
+public class Post {
+
+ @Id
+ @GeneratedValue private Long id;
+
+ private String message;
+ private Instant date;
+
+ public Post() {}
+
+ public Post(String message) {
+ this.message = message;
+ this.date = Instant.now().minus(new Random().nextLong(100), ChronoUnit.MINUTES);
+ }
+
+ public String getMessage() {
+ return message;
+ }
+
+ public void setMessage(String message) {
+ this.message = message;
+ }
+
+ public Instant getDate() {
+ return date;
+ }
+
+ public void setDate(Instant date) {
+ this.date = date;
+ }
+
+ @Override
+ public String toString() {
+ return message;
+ }
+}
diff --git a/jpa/aot-optimization/src/main/java/example/springdata/aot/User.java b/jpa/aot-optimization/src/main/java/example/springdata/aot/User.java
new file mode 100644
index 000000000..b9c43ff05
--- /dev/null
+++ b/jpa/aot-optimization/src/main/java/example/springdata/aot/User.java
@@ -0,0 +1,105 @@
+/*
+ * 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
+ *
+ * http://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 example.springdata.aot;
+
+import jakarta.persistence.Column;
+import jakarta.persistence.Entity;
+import jakarta.persistence.Id;
+
+import java.time.Instant;
+
+/**
+ * @author Christoph Strobl
+ * @since 2025/01
+ */
+@Entity(name = "users")
+public class User {
+
+ @Id private String id;
+ private String username;
+
+ @Column(name = "first_name") String firstname;
+ @Column(name = "last_name") String lastname;
+
+ // @OneToMany
+ // private List posts;
+
+ Instant registrationDate;
+ Instant lastSeen;
+
+ public User() {}
+
+ public User(String id, String username) {
+ this.id = id;
+ this.username = username;
+ }
+
+ public String getId() {
+ return id;
+ }
+
+ public String getUsername() {
+ return username;
+ }
+
+ public String getFirstname() {
+ return firstname;
+ }
+
+ public void setFirstname(String firstname) {
+ this.firstname = firstname;
+ }
+
+ public String getLastname() {
+ return lastname;
+ }
+
+ public void setLastname(String lastname) {
+ this.lastname = lastname;
+ }
+
+ public Instant getRegistrationDate() {
+ return registrationDate;
+ }
+
+ public void setRegistrationDate(Instant registrationDate) {
+ this.registrationDate = registrationDate;
+ }
+
+ public Instant getLastSeen() {
+ return lastSeen;
+ }
+
+ public void setLastSeen(Instant lastSeen) {
+ this.lastSeen = lastSeen;
+ }
+
+ // public List getPosts() {
+ // return posts;
+ // }
+ //
+ // public void setPosts(List posts) {
+ // this.posts = posts;
+ // }
+
+ @Override
+ public String toString() {
+ return "User{" + "id='" + id + '\'' + ", username='" + username + '\'' + ", firstname='" + firstname + '\''
+ + ", lastname='" + lastname + '\'' + ", registrationDate=" + registrationDate + ", lastSeen=" + lastSeen +
+ // ", posts=" + posts +
+ '}';
+ }
+}
diff --git a/jpa/aot-optimization/src/main/java/example/springdata/aot/UserProjection.java b/jpa/aot-optimization/src/main/java/example/springdata/aot/UserProjection.java
new file mode 100644
index 000000000..8ac144a12
--- /dev/null
+++ b/jpa/aot-optimization/src/main/java/example/springdata/aot/UserProjection.java
@@ -0,0 +1,26 @@
+/*
+ * 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
+ *
+ * http://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 example.springdata.aot;
+
+import java.time.Instant;
+
+/**
+ * @author Christoph Strobl
+ * @since 2025/01
+ */
+public record UserProjection(String username, Instant registrationDate) {
+
+}
diff --git a/jpa/aot-optimization/src/main/java/example/springdata/aot/UserRepository.java b/jpa/aot-optimization/src/main/java/example/springdata/aot/UserRepository.java
new file mode 100644
index 000000000..1da15b360
--- /dev/null
+++ b/jpa/aot-optimization/src/main/java/example/springdata/aot/UserRepository.java
@@ -0,0 +1,57 @@
+/*
+ * 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
+ *
+ * http://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 example.springdata.aot;
+
+import java.util.List;
+import java.util.Optional;
+
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.domain.Slice;
+import org.springframework.data.jpa.repository.Query;
+import org.springframework.data.querydsl.QuerydslPredicateExecutor;
+import org.springframework.data.repository.CrudRepository;
+
+/**
+ * @author Christoph Strobl
+ * @since 2025/01
+ */
+public interface UserRepository extends CrudRepository, QuerydslPredicateExecutor {
+
+ User findUserByUsername(String username);
+
+ Optional findOptionalUserByUsername(String username);
+
+ Long countUsersByLastnameLike(String lastname);
+
+ Boolean existsByUsername(String username);
+
+ List findUserByLastnameLike(String lastname);
+
+ List findUserByLastnameStartingWithOrderByFirstname(String lastname);
+
+ List findTop2UsersByLastnameStartingWith(String lastname);
+
+ Slice findUserByUsernameAfter(String username, Pageable pageable);
+
+ List findUserByLastnameStartingWith(String lastname);
+
+ Page findUserByLastnameStartingWith(String lastname, Pageable page);
+
+ @Query("SELECT u FROM example.springdata.aot.User u WHERE u.username LIKE ?1%")
+ List usersWithUsernamesStartingWith(String username);
+
+}
diff --git a/jpa/aot-optimization/src/main/resources/application.properties b/jpa/aot-optimization/src/main/resources/application.properties
new file mode 100644
index 000000000..ee6779653
--- /dev/null
+++ b/jpa/aot-optimization/src/main/resources/application.properties
@@ -0,0 +1,5 @@
+spring.jpa.defer-datasource-initialization=true
+spring.aot.repositories.enabled=true
+#spring.aot.jpa.repositories.use-entitymanager=true
+#logging.level.org.springframework.data.repository.aot.generate.RepositoryContributor=trace
+
diff --git a/jpa/example/src/main/java/example/springdata/jpa/simple/SimpleUserRepository.java b/jpa/example/src/main/java/example/springdata/jpa/simple/SimpleUserRepository.java
index 75bfcc659..9f5c77a85 100644
--- a/jpa/example/src/main/java/example/springdata/jpa/simple/SimpleUserRepository.java
+++ b/jpa/example/src/main/java/example/springdata/jpa/simple/SimpleUserRepository.java
@@ -20,6 +20,7 @@
import java.util.concurrent.CompletableFuture;
import java.util.stream.Stream;
+import org.springframework.data.domain.Limit;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Slice;
import org.springframework.data.domain.Sort;
@@ -63,6 +64,17 @@ public interface SimpleUserRepository extends ListCrudRepository {
*/
List findByLastname(String lastname);
+ /**
+ * Find at most the number of users defined via maxResults with the given lastname.
+ * This method will be translated into a query by constructing it directly from the method name as there is no other
+ * query declared.
+ *
+ * @param lastname
+ * @param maxResults the maximum number of results returned.
+ * @return
+ */
+ List findByLastname(String lastname, Limit maxResults);
+
/**
* Returns all users with the given firstname. This method will be translated into a query using the one declared in
* the {@link Query} annotation declared one.
@@ -73,6 +85,17 @@ public interface SimpleUserRepository extends ListCrudRepository {
@Query("select u from User u where u.firstname = :firstname")
List findByFirstname(String firstname);
+ /**
+ * Returns at most the number of users defined via {@link Limit} with the given firstname. This method will be
+ * translated into a query using the one declared in the {@link Query} annotation declared one.
+ *
+ * @param firstname
+ * @param maxResults the maximum number of results returned.
+ * @return
+ */
+ @Query("select u from User u where u.firstname = :firstname")
+ List findByFirstname(String firstname, Limit maxResults);
+
/**
* Returns all users with the given name as first- or lastname. This makes the query to method relation much more
* refactoring-safe as the order of the method parameters is completely irrelevant.
diff --git a/jpa/example/src/test/java/example/springdata/jpa/simple/SimpleUserRepositoryTests.java b/jpa/example/src/test/java/example/springdata/jpa/simple/SimpleUserRepositoryTests.java
index 9c4da22d6..5fcfdc773 100644
--- a/jpa/example/src/test/java/example/springdata/jpa/simple/SimpleUserRepositoryTests.java
+++ b/jpa/example/src/test/java/example/springdata/jpa/simple/SimpleUserRepositoryTests.java
@@ -27,6 +27,7 @@
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
+import java.util.stream.IntStream;
import java.util.stream.Stream;
import org.junit.jupiter.api.Assertions;
@@ -36,6 +37,7 @@
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.dao.InvalidDataAccessApiUsageException;
+import org.springframework.data.domain.Limit;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.transaction.annotation.Propagation;
@@ -83,6 +85,22 @@ void findSavedUserByLastname() {
assertThat(repository.findByLastname("lastname")).contains(user);
}
+ @Test
+ void findLimitedNumberOfUsersViaDerivedQuery() {
+
+ IntStream.range(0, 10).forEach($ -> repository.save(new User(user.getFirstname(), user.getLastname())));
+
+ assertThat(repository.findByLastname("lastname", Limit.of(5))).hasSize(5);
+ }
+
+ @Test
+ void findLimitedNumberOfUsersViaAnnotatedQuery() {
+
+ IntStream.range(0, 10).forEach($ -> repository.save(new User(user.getFirstname(), user.getLastname())));
+
+ assertThat(repository.findByFirstname(user.getFirstname(), Limit.of(5))).hasSize(5);
+ }
+
@Test
void findByFirstnameOrLastname() {
diff --git a/jpa/example/src/test/java/example/springdata/jpa/simple/VirtualThreadsTests.java b/jpa/example/src/test/java/example/springdata/jpa/simple/VirtualThreadsTests.java
index af581aa3f..915d2131d 100644
--- a/jpa/example/src/test/java/example/springdata/jpa/simple/VirtualThreadsTests.java
+++ b/jpa/example/src/test/java/example/springdata/jpa/simple/VirtualThreadsTests.java
@@ -25,7 +25,7 @@
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
-import org.junit.jupiter.api.condition.EnabledOnJre;
+import org.junit.jupiter.api.condition.EnabledForJreRange;
import org.junit.jupiter.api.condition.JRE;
import org.springframework.beans.factory.annotation.Autowired;
@@ -41,7 +41,7 @@
*/
@Transactional
@SpringBootTest(properties = "spring.threads.virtual.enabled=true")
-@EnabledOnJre(JRE.JAVA_21)
+@EnabledForJreRange(min = JRE.JAVA_21)
class VirtualThreadsTests {
@Autowired SimpleUserRepository repository;
diff --git a/jpa/graalvm-native/pom.xml b/jpa/graalvm-native/pom.xml
index a91305c6c..839326d50 100644
--- a/jpa/graalvm-native/pom.xml
+++ b/jpa/graalvm-native/pom.xml
@@ -37,7 +37,7 @@
org.hibernate.orm.toolinghibernate-enhance-maven-plugin
- 6.1.4.Final
+ ${hibernate.version}
diff --git a/jpa/interceptors/src/main/java/example/springdata/jpa/interceptors/Customer.java b/jpa/interceptors/src/main/java/example/springdata/jpa/interceptors/Customer.java
index e3591c461..02b026b30 100644
--- a/jpa/interceptors/src/main/java/example/springdata/jpa/interceptors/Customer.java
+++ b/jpa/interceptors/src/main/java/example/springdata/jpa/interceptors/Customer.java
@@ -22,9 +22,19 @@
@Entity
public class Customer {
- @Id @GeneratedValue Long id;
+ @Id
+ @GeneratedValue
+ Long id;
String firstname;
String lastname;
+ @Override
+ public String toString() {
+ return "Customer{" +
+ "id=" + id +
+ ", firstname='" + firstname + '\'' +
+ ", lastname='" + lastname + '\'' +
+ '}';
+ }
}
diff --git a/jpa/interceptors/src/test/java/example/springdata/jpa/interceptors/InterceptorIntegrationTest.java b/jpa/interceptors/src/test/java/example/springdata/jpa/interceptors/InterceptorIntegrationTest.java
index 6ccace00c..976c07f74 100644
--- a/jpa/interceptors/src/test/java/example/springdata/jpa/interceptors/InterceptorIntegrationTest.java
+++ b/jpa/interceptors/src/test/java/example/springdata/jpa/interceptors/InterceptorIntegrationTest.java
@@ -30,12 +30,13 @@ class InterceptorIntegrationTest {
@Autowired CustomerRepository repository;
@Test
- void foo() {
+ void demonstrateInterceptor() {
var customer = new Customer();
customer.firstname = "Dave";
customer.lastname = "Matthews";
+ // observer Log output from ApplicationConfiguration.interceptor
repository.save(customer);
}
}
diff --git a/jpa/multitenant/README.adoc b/jpa/multitenant/README.adoc
index 0c7511491..d4f21f927 100644
--- a/jpa/multitenant/README.adoc
+++ b/jpa/multitenant/README.adoc
@@ -8,3 +8,5 @@ Each uses a different strategy to separate data by tenant:
2. Use a separate schema per tenant
3. Use a separate database per tenant.
+_The contained projects are only examples how to use Hibernates Multitenant feature with Spring Data JPA.
+For any real application a decision has to be made how to scope a tenant. Storing it in a singleton as in the examples is for most cases not an appropriate solution._
diff --git a/jpa/multitenant/db/src/main/java/example/springdata/jpa/hibernatemultitenant/db/NoOpConnectionProvider.java b/jpa/multitenant/db/src/main/java/example/springdata/jpa/hibernatemultitenant/db/NoOpConnectionProvider.java
index 7e3a48b09..d4e4548ae 100644
--- a/jpa/multitenant/db/src/main/java/example/springdata/jpa/hibernatemultitenant/db/NoOpConnectionProvider.java
+++ b/jpa/multitenant/db/src/main/java/example/springdata/jpa/hibernatemultitenant/db/NoOpConnectionProvider.java
@@ -42,17 +42,6 @@ public void releaseAnyConnection(Connection connection) throws SQLException {
connection.close();
}
- @Override
- public Connection getConnection(String schema) throws SQLException {
-
- return dataSource.getConnection();
- }
-
- @Override
- public void releaseConnection(String s, Connection connection) throws SQLException {
- connection.close();
- }
-
@Override
public boolean supportsAggressiveRelease() {
return false;
@@ -72,4 +61,18 @@ public T unwrap(Class aClass) {
public void customize(Map hibernateProperties) {
hibernateProperties.put(AvailableSettings.MULTI_TENANT_CONNECTION_PROVIDER, this);
}
+
+ @Override
+ public Connection getConnection(Object tenantIdentifier) throws SQLException {
+ return dataSource.getConnection();
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.hibernate.engine.jdbc.connections.spi.MultiTenantConnectionProvider#releaseConnection(java.lang.Object, java.sql.Connection)
+ */
+ @Override
+ public void releaseConnection(Object tenantIdentifier, Connection connection) throws SQLException {
+ connection.close();
+ }
}
diff --git a/jpa/multitenant/schema/src/main/java/example/springdata/jpa/hibernatemultitenant/schema/ExampleConnectionProvider.java b/jpa/multitenant/schema/src/main/java/example/springdata/jpa/hibernatemultitenant/schema/ExampleConnectionProvider.java
index c482c5f8c..cacd066d8 100644
--- a/jpa/multitenant/schema/src/main/java/example/springdata/jpa/hibernatemultitenant/schema/ExampleConnectionProvider.java
+++ b/jpa/multitenant/schema/src/main/java/example/springdata/jpa/hibernatemultitenant/schema/ExampleConnectionProvider.java
@@ -43,14 +43,20 @@ public void releaseAnyConnection(Connection connection) throws SQLException {
}
@Override
- public Connection getConnection(String schema) throws SQLException {
+ public Connection getConnection(Object tenantIdentifier) throws SQLException {
+
final Connection connection = dataSource.getConnection();
- connection.setSchema(schema);
+ connection.setSchema(tenantIdentifier.toString());
return connection;
}
+ /*
+ * (non-Javadoc)
+ * @see org.hibernate.engine.jdbc.connections.spi.MultiTenantConnectionProvider#releaseConnection(java.lang.Object, java.sql.Connection)
+ */
@Override
- public void releaseConnection(String s, Connection connection) throws SQLException {
+ public void releaseConnection(Object tenantIdentifier, Connection connection) throws SQLException {
+
connection.setSchema("PUBLIC");
connection.close();
}
diff --git a/jpa/pom.xml b/jpa/pom.xml
index a70186076..9b94d5869 100644
--- a/jpa/pom.xml
+++ b/jpa/pom.xml
@@ -17,6 +17,7 @@
2011
+ aot-optimizationdeferredenversexample
@@ -31,26 +32,6 @@
graalvm-native
-
-
- hibernate-53
-
- 5.3.0.Final
-
-
-
- java-next
-
-
- [8,14]
-
-
-
- eclipselink
-
-
-
-
@@ -58,6 +39,21 @@
spring-boot-starter-data-jpa
+
+ org.springframework.data
+ spring-data-commons
+
+
+
+ org.springframework.data
+ spring-data-jpa
+
+
+
+ jakarta.persistence
+ jakarta.persistence-api
+
+
org.hsqldbhsqldb
diff --git a/mongodb/aot-optimization/pom.xml b/mongodb/aot-optimization/pom.xml
new file mode 100644
index 000000000..fb918c289
--- /dev/null
+++ b/mongodb/aot-optimization/pom.xml
@@ -0,0 +1,47 @@
+
+
+ 4.0.0
+
+ org.springframework.data.examples
+ spring-data-mongodb-examples
+ 2.0.0.BUILD-SNAPSHOT
+
+
+ org.example
+ spring-data-mongodb-aot-optimization
+
+
+ 21
+ 21
+ UTF-8
+ 2025.1.0-M3
+
+
+
+
+ org.jspecify
+ jspecify
+ 1.0.0
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+ process-aot
+
+ process-aot
+
+
+
+
+
+
+
+
diff --git a/mongodb/aot-optimization/src/main/java/example/springdata/aot/App.java b/mongodb/aot-optimization/src/main/java/example/springdata/aot/App.java
new file mode 100644
index 000000000..1f3c478af
--- /dev/null
+++ b/mongodb/aot-optimization/src/main/java/example/springdata/aot/App.java
@@ -0,0 +1,30 @@
+/*
+ * 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
+ *
+ * http://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 example.springdata.aot;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+/**
+ * @author Christoph Strobl
+ */
+@SpringBootApplication
+public class App {
+
+ public static void main(String[] args) {
+ SpringApplication.run(App.class, args);
+ }
+}
diff --git a/mongodb/aot-optimization/src/main/java/example/springdata/aot/CLR.java b/mongodb/aot-optimization/src/main/java/example/springdata/aot/CLR.java
new file mode 100644
index 000000000..1ccacfaa2
--- /dev/null
+++ b/mongodb/aot-optimization/src/main/java/example/springdata/aot/CLR.java
@@ -0,0 +1,114 @@
+/*
+ * 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
+ *
+ * http://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 example.springdata.aot;
+
+import java.util.List;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.CommandLineRunner;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.PageRequest;
+import org.springframework.data.domain.Slice;
+import org.springframework.stereotype.Component;
+
+/**
+ * @author Christoph Strobl
+ * @since 2025/01
+ */
+@Component
+public class CLR implements CommandLineRunner {
+
+ @Autowired UserRepository repository;
+
+ @Override
+ public void run(String... args) throws Exception {
+
+ User luke = new User("id-1", "luke");
+ luke.setFirstname("Luke");
+ luke.setLastname("Skywalker");
+ luke.setPosts(List.of(new Post("I have a bad feeling about this.")));
+
+ User leia = new User("id-2", "leia");
+ leia.setFirstname("Leia");
+ leia.setLastname("Organa");
+
+ User han = new User("id-3", "han");
+ han.setFirstname("Han");
+ han.setLastname("Solo");
+ han.setPosts(List.of(new Post("It's the ship that made the Kessel Run in less than 12 Parsecs.")));
+
+ User chewbacca = new User("id-4", "chewbacca");
+ User yoda = new User("id-5", "yoda");
+ yoda.setPosts(List.of(new Post("Do. Or do not. There is no try."), new Post("Decide you must, how to serve them best. If you leave now, help them you could; but you would destroy all for which they have fought, and suffered.")));
+
+ User vader = new User("id-6", "vader");
+ vader.setFirstname("Anakin");
+ vader.setLastname("Skywalker");
+ vader.setPosts(List.of(new Post("I am your father")));
+
+ User kylo = new User("id-7", "kylo");
+ kylo.setFirstname("Ben");
+ kylo.setLastname("Solo");
+
+ repository.saveAll(List.of(luke, leia, han, chewbacca, yoda, vader, kylo));
+
+ System.out.println("------- annotated multi -------");
+ System.out.println(repository.usersWithUsernamesStartingWith("l"));
+
+ System.out.println("------- derived single -------");
+ System.out.println(repository.findUserByUsername("yoda"));
+
+ System.out.println("------- derived nested.path -------");
+ System.out.println(repository.findUserByPostsMessageLike("father"));
+
+ System.out.println("------- derived optional -------");
+ System.out.println(repository.findOptionalUserByUsername("yoda"));
+
+ System.out.println("------- derived count -------");
+ Long count = repository.countUsersByLastnameLike("Sky");
+ System.out.println("user count " + count);
+
+ System.out.println("------- derived exists -------");
+ Boolean exists = repository.existsByUsername("vader");
+ System.out.println("user exists " + exists);
+
+ System.out.println("------- derived multi -------");
+ System.out.println(repository.findUserByLastnameLike("Sky"));
+
+ System.out.println("------- derived sorted -------");
+ System.out.println(repository.findUserByLastnameLikeOrderByFirstname("Sky"));
+
+ System.out.println("------- derived page -------");
+ Page page0 = repository.findUserByLastnameStartingWith("S", PageRequest.of(0, 2));
+ System.out.println("page0: " + page0);
+ System.out.println("page0.content: " + page0.getContent());
+
+ Page page1 = repository.findUserByLastnameStartingWith("S", PageRequest.of(1, 2));
+ System.out.println("page1: " + page1);
+ System.out.println("page1.content: " + page1.getContent());
+
+ System.out.println("------- derived slice -------");
+ Slice slice0 = repository.findUserByUsernameAfter("luke", PageRequest.of(0, 2));
+ System.out.println("slice0: " + slice0);
+ System.out.println("slice0.content: " + slice0.getContent());
+
+ System.out.println("------- derived top -------");
+ System.out.println(repository.findTop2UsersByLastnameLike("S"));
+
+ System.out.println("------- derived with fields -------");
+ System.out.println(repository.findJustUsernameBy());
+ }
+}
diff --git a/mongodb/aot-optimization/src/main/java/example/springdata/aot/Post.java b/mongodb/aot-optimization/src/main/java/example/springdata/aot/Post.java
new file mode 100644
index 000000000..10f5068ba
--- /dev/null
+++ b/mongodb/aot-optimization/src/main/java/example/springdata/aot/Post.java
@@ -0,0 +1,56 @@
+/*
+ * 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
+ *
+ * http://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 example.springdata.aot;
+
+import java.time.Instant;
+import java.time.temporal.ChronoUnit;
+import java.util.Random;
+
+/**
+ * @author Christoph Strobl
+ * @since 2025/01
+ */
+public class Post {
+
+ private String message;
+ private Instant date;
+
+ public Post(String message) {
+ this.message = message;
+ this.date = Instant.now().minus(new Random().nextLong(100), ChronoUnit.MINUTES);
+ }
+
+ public String getMessage() {
+ return message;
+ }
+
+ public void setMessage(String message) {
+ this.message = message;
+ }
+
+ public Instant getDate() {
+ return date;
+ }
+
+ public void setDate(Instant date) {
+ this.date = date;
+ }
+
+ @Override
+ public String toString() {
+ return message;
+ }
+}
diff --git a/mongodb/aot-optimization/src/main/java/example/springdata/aot/User.java b/mongodb/aot-optimization/src/main/java/example/springdata/aot/User.java
new file mode 100644
index 000000000..56317490e
--- /dev/null
+++ b/mongodb/aot-optimization/src/main/java/example/springdata/aot/User.java
@@ -0,0 +1,105 @@
+/*
+ * 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
+ *
+ * http://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 example.springdata.aot;
+
+import java.time.Instant;
+import java.util.List;
+
+import org.springframework.data.mongodb.core.mapping.Field;
+
+/**
+ * @author Christoph Strobl
+ * @since 2025/01
+ */
+public class User {
+
+ private final String id;
+ private final String username;
+
+ @Field("first_name") String firstname;
+ @Field("last_name") String lastname;
+
+ private List posts;
+
+ Instant registrationDate;
+ Instant lastSeen;
+
+ public User(String id, String username) {
+ this.id = id;
+ this.username = username;
+ }
+
+ public String getId() {
+ return id;
+ }
+
+ public String getUsername() {
+ return username;
+ }
+
+ public String getFirstname() {
+ return firstname;
+ }
+
+ public void setFirstname(String firstname) {
+ this.firstname = firstname;
+ }
+
+ public String getLastname() {
+ return lastname;
+ }
+
+ public void setLastname(String lastname) {
+ this.lastname = lastname;
+ }
+
+ public Instant getRegistrationDate() {
+ return registrationDate;
+ }
+
+ public void setRegistrationDate(Instant registrationDate) {
+ this.registrationDate = registrationDate;
+ }
+
+ public Instant getLastSeen() {
+ return lastSeen;
+ }
+
+ public void setLastSeen(Instant lastSeen) {
+ this.lastSeen = lastSeen;
+ }
+
+ public List getPosts() {
+ return posts;
+ }
+
+ public void setPosts(List posts) {
+ this.posts = posts;
+ }
+
+ @Override
+ public String toString() {
+ return "User{" +
+ "id='" + id + '\'' +
+ ", username='" + username + '\'' +
+ ", firstname='" + firstname + '\'' +
+ ", lastname='" + lastname + '\'' +
+ ", registrationDate=" + registrationDate +
+ ", lastSeen=" + lastSeen +
+ ", posts=" + posts +
+ '}';
+ }
+}
diff --git a/mongodb/aot-optimization/src/main/java/example/springdata/aot/UserProjection.java b/mongodb/aot-optimization/src/main/java/example/springdata/aot/UserProjection.java
new file mode 100644
index 000000000..d243ba3a8
--- /dev/null
+++ b/mongodb/aot-optimization/src/main/java/example/springdata/aot/UserProjection.java
@@ -0,0 +1,28 @@
+/*
+ * 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
+ *
+ * http://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 example.springdata.aot;
+
+import java.time.Instant;
+
+/**
+ * @author Christoph Strobl
+ * @since 2025/01
+ */
+public interface UserProjection {
+
+ String getUsername();
+ Instant getRegistrationDate();
+}
diff --git a/mongodb/aot-optimization/src/main/java/example/springdata/aot/UserRepository.java b/mongodb/aot-optimization/src/main/java/example/springdata/aot/UserRepository.java
new file mode 100644
index 000000000..6a935551e
--- /dev/null
+++ b/mongodb/aot-optimization/src/main/java/example/springdata/aot/UserRepository.java
@@ -0,0 +1,59 @@
+/*
+ * 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
+ *
+ * http://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 example.springdata.aot;
+
+import java.util.List;
+import java.util.Optional;
+
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.domain.Slice;
+import org.springframework.data.mongodb.repository.Query;
+import org.springframework.data.repository.CrudRepository;
+
+/**
+ * @author Christoph Strobl
+ * @since 2025/01
+ */
+public interface UserRepository extends CrudRepository {
+
+ User findUserByUsername(String username);
+
+ Optional findOptionalUserByUsername(String username);
+
+ Long countUsersByLastnameLike(String lastname);
+
+ Boolean existsByUsername(String username);
+
+ List findUserByLastnameLike(String lastname);
+
+ List findUserByPostsMessageLike(String part);
+
+ List findUserByLastnameLikeOrderByFirstname(String lastname);
+
+ List findTop2UsersByLastnameLike(String lastname);
+
+ Slice findUserByUsernameAfter(String username, Pageable pageable);
+
+ Page findUserByLastnameStartingWith(String lastname, Pageable page);
+
+ @Query("{ 'username' : { $regex: '?0.*', $options: 'i' } }")
+ List usersWithUsernamesStartingWith(String username);
+
+ @Query(fields = "{ 'username' : 1 }", sort = "{ 'username' : -1 }")
+ List findJustUsernameBy();
+
+}
diff --git a/mongodb/aot-optimization/src/main/resources/application.properties b/mongodb/aot-optimization/src/main/resources/application.properties
new file mode 100644
index 000000000..fb68fc02e
--- /dev/null
+++ b/mongodb/aot-optimization/src/main/resources/application.properties
@@ -0,0 +1 @@
+spring.aot.repositories.enabled=true
diff --git a/mongodb/aot-optimization/src/main/resources/logback.xml b/mongodb/aot-optimization/src/main/resources/logback.xml
new file mode 100644
index 000000000..1be6becb6
--- /dev/null
+++ b/mongodb/aot-optimization/src/main/resources/logback.xml
@@ -0,0 +1,28 @@
+
+
+
+
+
+ %d %5p %40.40c:%4L - %m%n
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/mongodb/change-streams/src/test/java/example/springdata/mongodb/ChangeStreamsTests.java b/mongodb/change-streams/src/test/java/example/springdata/mongodb/ChangeStreamsTests.java
index dd8456c45..9714658a7 100644
--- a/mongodb/change-streams/src/test/java/example/springdata/mongodb/ChangeStreamsTests.java
+++ b/mongodb/change-streams/src/test/java/example/springdata/mongodb/ChangeStreamsTests.java
@@ -200,28 +200,31 @@ public void reactiveChangeEvents() {
ChangeStreamOptions.builder().filter(newAggregation(match(where("operationType").is("insert")))).build(),
Person.class);
- StepVerifier.create(changeStream) //
+ changeStream.as(StepVerifier::create) //
.expectSubscription() //
.expectNoEvent(Duration.ofMillis(200)) // wait till change streams becomes active
// Save documents and await their change events
.then(() -> {
- StepVerifier.create(reactiveTemplate.save(gabriel)).expectNextCount(1).verifyComplete();
- StepVerifier.create(reactiveTemplate.save(ash)).expectNextCount(1).verifyComplete();
+ reactiveTemplate.save(gabriel).as(StepVerifier::create).expectNextCount(1).verifyComplete();
+ reactiveTemplate.save(ash).as(StepVerifier::create).expectNextCount(1).verifyComplete();
}).expectNextCount(2) //
// Update a document
.then(() -> {
- StepVerifier.create(reactiveTemplate.update(Person.class) //
+ reactiveTemplate.update(Person.class) //
.matching(query(where("id").is(ash.id()))) //
.apply(update("age", 40)) //
- .first()).expectNextCount(1).verifyComplete();
+ .first() //
+ .as(StepVerifier::create) //
+ .expectNextCount(1) //
+ .verifyComplete();
}).expectNoEvent(Duration.ofMillis(200)) // updates are skipped
// Save another document and await its change event
.then(() -> {
- StepVerifier.create(reactiveTemplate.save(michael)).expectNextCount(1).verifyComplete();
+ reactiveTemplate.save(michael).as(StepVerifier::create).expectNextCount(1).verifyComplete();
}).expectNextCount(1) // there we go, all events received.
.thenCancel() // change streams are infinite streams, at some point we need to unsubscribe
diff --git a/mongodb/example/src/main/java/example/springdata/mongodb/customer/CustomerRepository.java b/mongodb/example/src/main/java/example/springdata/mongodb/customer/CustomerRepository.java
index ef239160b..8a94fa5ef 100644
--- a/mongodb/example/src/main/java/example/springdata/mongodb/customer/CustomerRepository.java
+++ b/mongodb/example/src/main/java/example/springdata/mongodb/customer/CustomerRepository.java
@@ -18,6 +18,7 @@
import java.util.List;
import java.util.stream.Stream;
+import org.springframework.data.domain.Limit;
import org.springframework.data.domain.Sort;
import org.springframework.data.geo.Distance;
import org.springframework.data.geo.GeoResults;
@@ -41,6 +42,15 @@ public interface CustomerRepository extends CrudRepository {
*/
List findByLastname(String lastname, Sort sort);
+ /**
+ * Derived query reducing result size to a given {@link Limit}.
+ *
+ * @param lastname
+ * @param maxResults the maximum number of results returned.
+ * @return
+ */
+ List findByLastname(String lastname, Limit maxResults);
+
/**
* Showcase for a repository query using geospatial functionality.
*
diff --git a/mongodb/example/src/test/java/example/springdata/mongodb/advanced/ServersideScriptTests.java b/mongodb/example/src/test/java/example/springdata/mongodb/advanced/ServersideScriptTests.java
deleted file mode 100644
index c11cbf441..000000000
--- a/mongodb/example/src/test/java/example/springdata/mongodb/advanced/ServersideScriptTests.java
+++ /dev/null
@@ -1,124 +0,0 @@
-/*
- * Copyright 2015-2021 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 example.springdata.mongodb.advanced;
-
-import static org.assertj.core.api.Assertions.*;
-
-import example.springdata.mongodb.customer.Customer;
-
-import java.util.Map;
-
-import org.bson.Document;
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Disabled;
-import org.junit.jupiter.api.Test;
-
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.boot.test.autoconfigure.data.mongo.DataMongoTest;
-import org.springframework.data.mongodb.core.MongoOperations;
-import org.springframework.data.mongodb.core.script.ExecutableMongoScript;
-import org.springframework.data.mongodb.core.script.NamedMongoScript;
-import org.springframework.test.context.DynamicPropertyRegistry;
-import org.springframework.test.context.DynamicPropertySource;
-import org.testcontainers.containers.MongoDBContainer;
-import org.testcontainers.junit.jupiter.Container;
-import org.testcontainers.junit.jupiter.Testcontainers;
-import org.testcontainers.utility.DockerImageName;
-
-/**
- * @author Christoph Strobl
- * @author Oliver Gierke
- */
-@Testcontainers
-@DataMongoTest
-class ServersideScriptTests {
-
- @Container //
- private static MongoDBContainer mongoDBContainer = new MongoDBContainer(
- DockerImageName.parse("mongo:3.6"));
-
- @DynamicPropertySource
- static void setProperties(DynamicPropertyRegistry registry) {
- registry.add("spring.data.mongodb.uri", mongoDBContainer::getReplicaSetUrl);
- }
-
- @Autowired AdvancedRepository repository;
- @Autowired MongoOperations operations;
-
- @BeforeEach
- void setUp() {
-
- if (!operations.collectionExists(Customer.class)) {
- operations.createCollection(Customer.class);
- }
-
- // just make sure we remove everything properly
- operations.getCollection("system.js").deleteMany(new Document());
- repository.deleteAll();
- }
-
- /**
- * Store and call an arbitrary JavaScript function (in this case a simple echo script) via its name.
- */
- @Test
- void saveAndCallScriptViaName() {
-
- operations.scriptOps()
- .register(new NamedMongoScript("echoScript", new ExecutableMongoScript("function(x) { return x; }")));
-
- assertThat(operations.scriptOps().call("echoScript", "Hello echo...!")).isEqualTo("Hello echo...!");
- }
-
- /**
- * Use a script execution to create an atomic put-if-absent operation that fulfills the contract of
- * {@link Map#putIfAbsent(Object, Object)}
- */
- @Test
- @Disabled
- void complexScriptExecutionSimulatingPutIfAbsent() {
-
- var ned = new Customer("Ned", "Stark");
- ned.setId("ned-stark");
-
- // #1: on first insert null has to be returned
- assertThat(operations.scriptOps().execute(createExecutablePutIfAbsentScript(ned))).isNotNull();
-
- // #2: change the firstname and put the object again, we expect a return value.
- ned.setFirstname("Eddard");
- assertThat(operations.scriptOps().execute(createExecutablePutIfAbsentScript(ned))).isNotNull();
-
- // #3: make sure the entity has not been altered by #2
- assertThat(repository.findById(ned.getId()))
- .hasValueSatisfying(it -> assertThat(it.getFirstname()).isEqualTo("Ned"));
- assertThat(repository.count()).isEqualTo(1L);
- }
-
- private ExecutableMongoScript createExecutablePutIfAbsentScript(Customer customer) {
-
- var collectionName = operations.getCollectionName(Customer.class);
- var id = operations.getConverter().getMappingContext().getRequiredPersistentEntity(Customer.class)
- .getIdentifierAccessor(customer).getIdentifier();
-
- var document = new Document();
- operations.getConverter().write(customer, document);
-
- var scriptString = String.format(
- "object = db.%1$s.findOne({\"_id\": \"%2$s\"}); if (object == null) { db.%1s.insert(%3$s); return null; } else { return object; }",
- collectionName, id, document);
-
- return new ExecutableMongoScript(scriptString);
- }
-}
diff --git a/mongodb/example/src/test/java/example/springdata/mongodb/customer/CustomerRepositoryIntegrationTest.java b/mongodb/example/src/test/java/example/springdata/mongodb/customer/CustomerRepositoryIntegrationTest.java
index 2c2352d3f..5a3e76dd6 100644
--- a/mongodb/example/src/test/java/example/springdata/mongodb/customer/CustomerRepositoryIntegrationTest.java
+++ b/mongodb/example/src/test/java/example/springdata/mongodb/customer/CustomerRepositoryIntegrationTest.java
@@ -27,6 +27,7 @@
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.data.mongo.DataMongoTest;
+import org.springframework.data.domain.Limit;
import org.springframework.data.geo.Distance;
import org.springframework.data.geo.Metrics;
import org.springframework.data.geo.Point;
@@ -96,6 +97,17 @@ void findCustomersUsingQuerydslSort() {
assertThat(result.get(1)).isEqualTo(oliver);
}
+ /**
+ * Test case to show how to reduce result size with dynamic {@link Limit}.
+ */
+ @Test
+ void limitResultSize() {
+
+ var result = repository.findByLastname("Matthews", Limit.of(1));
+
+ assertThat(result).hasSize(1);
+ }
+
/**
* Test case to show the usage of Java {@link Stream}.
*/
diff --git a/mongodb/fragment-spi/README.adoc b/mongodb/fragment-spi/README.adoc
new file mode 100644
index 000000000..95eb32063
--- /dev/null
+++ b/mongodb/fragment-spi/README.adoc
@@ -0,0 +1,40 @@
+= Spring Data - Fragment SPI Example
+
+This project contains a sample using `spring.factories` to register implementation details for a repository extension for MongoDB Vector Search that lives outside the project namespace.
+
+The project is divided into the `atlas-api`, providing the extension, and the `sample` using it.
+
+== atlas-api
+
+The `AtlasRepository` is the base interface containing a `vectorSearch` method that is implemented in `AtlasRepositoryFragment`. The configuration in `src/main/resources/META-INF/spring.factories` makes sure it is picked up by the spring data infrastructure.
+
+The implementation leverages `RepositoryMethodContext` to get hold of method invocation metadata to determine the collection name derived from the repositories domain type ``.
+Since providing the metadata needs to be explicitly activated the `AtlasRepositoryFragment` uses the additional marker interface `RepositoryMetadataAccess` enabling the features for repositories extending the `AtlasRepository`.
+
+== sample
+
+The `MovieRepository` extends the `AtlasRepository` from the api project using a `Movie` type targeting the `movies` collection. No further configuration is needed to use the provided `vectorSearch` within the `MovieRepositoryTests`.
+
+The `Movies` class in `src/main/test` takes care of setting up required test data and indexes.
+
+== Running the sample
+
+The is using a local MongoDB Atlas instance bootstrapped by Testcontainers.
+Running the `MovieRepositoryTests` the `test/movies` collection will be populated with about 400 entries from the `mflix.embedded_movies.json.gz` file.
+Please be patient while data is loaded into the database and the index created afterward.
+Progress information will be printed to the log.
+
+[source,log]
+----
+INFO - com.example.data.mongodb.Movies: 73 - Loading movies from class path resource [mflix.embedded_movies.json.gz]
+INFO - com.example.data.mongodb.Movies: 90 - Created 420 movies in test.movies
+INFO - com.example.data.mongodb.Movies: 65 - creating vector index
+INFO - com.example.data.mongodb.Movies: 68 - index 'plot_vector_index' created
+----
+
+Once data and index are available search result will be printed:
+
+[source,log]
+----
+INFO - ...mongodb.MovieRepositoryTests: 183 - Movie{id='66d6ee0937e07b74aa2939cc', ...
+----
diff --git a/mongodb/fragment-spi/atlas-api/pom.xml b/mongodb/fragment-spi/atlas-api/pom.xml
new file mode 100644
index 000000000..d7b01df81
--- /dev/null
+++ b/mongodb/fragment-spi/atlas-api/pom.xml
@@ -0,0 +1,14 @@
+
+ 4.0.0
+
+
+ org.springframework.data.examples
+ spring-data-mongodb-fragment-spi
+ 2.0.0.BUILD-SNAPSHOT
+
+
+ spring-data-mongodb-fragment-spi-atlas
+ Spring Data MongoDB - Reusable Fragments - Vector Search Fragment
+
+
diff --git a/mongodb/fragment-spi/atlas-api/src/main/java/com/example/spi/mongodb/atlas/AtlasRepository.java b/mongodb/fragment-spi/atlas-api/src/main/java/com/example/spi/mongodb/atlas/AtlasRepository.java
new file mode 100644
index 000000000..1dc0f8e47
--- /dev/null
+++ b/mongodb/fragment-spi/atlas-api/src/main/java/com/example/spi/mongodb/atlas/AtlasRepository.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2024 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 com.example.spi.mongodb.atlas;
+
+import java.util.List;
+
+
+/**
+ * @author Christoph Strobl
+ */
+public interface AtlasRepository {
+
+ List vectorSearch(String index, String path, List vector);
+}
diff --git a/mongodb/fragment-spi/atlas-api/src/main/java/com/example/spi/mongodb/atlas/AtlasRepositoryFragment.java b/mongodb/fragment-spi/atlas-api/src/main/java/com/example/spi/mongodb/atlas/AtlasRepositoryFragment.java
new file mode 100644
index 000000000..f8f45c865
--- /dev/null
+++ b/mongodb/fragment-spi/atlas-api/src/main/java/com/example/spi/mongodb/atlas/AtlasRepositoryFragment.java
@@ -0,0 +1,64 @@
+/*
+ * 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
+ *
+ * http://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 com.example.spi.mongodb.atlas;
+
+import java.util.List;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.core.ResolvableType;
+import org.springframework.data.domain.Limit;
+import org.springframework.data.mongodb.core.MongoOperations;
+import org.springframework.data.mongodb.core.aggregation.Aggregation;
+import org.springframework.data.mongodb.core.aggregation.VectorSearchOperation;
+import org.springframework.data.repository.core.RepositoryMetadata;
+import org.springframework.data.repository.core.RepositoryMethodContext;
+import org.springframework.data.repository.core.support.RepositoryMetadataAccess;
+
+class AtlasRepositoryFragment implements AtlasRepository, RepositoryMetadataAccess {
+
+ private final MongoOperations mongoOperations;
+
+ public AtlasRepositoryFragment(@Autowired MongoOperations mongoOperations) {
+ this.mongoOperations = mongoOperations;
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public List vectorSearch(String index, String path, List vector) {
+
+ RepositoryMethodContext methodContext = RepositoryMethodContext.getContext();
+
+ Class> domainType = resolveDomainType(methodContext.getMetadata());
+
+ VectorSearchOperation $vectorSearch = VectorSearchOperation.search(index).path(path).vector(vector)
+ .limit(Limit.of(10)).numCandidates(150);
+
+ Aggregation aggregation = Aggregation.newAggregation($vectorSearch);
+
+ return (List) mongoOperations.aggregate(aggregation, mongoOperations.getCollectionName(domainType), domainType).getMappedResults();
+ }
+
+ @SuppressWarnings("unchecked")
+ private static Class resolveDomainType(RepositoryMetadata metadata) {
+
+ // resolve the actual generic type argument of the AtlasRepository.
+ return (Class) ResolvableType.forClass(metadata.getRepositoryInterface())
+ .as(AtlasRepository.class)
+ .getGeneric(0)
+ .resolve();
+ }
+
+}
diff --git a/mongodb/fragment-spi/atlas-api/src/main/resources/META-INF/spring.factories b/mongodb/fragment-spi/atlas-api/src/main/resources/META-INF/spring.factories
new file mode 100644
index 000000000..cddeae58c
--- /dev/null
+++ b/mongodb/fragment-spi/atlas-api/src/main/resources/META-INF/spring.factories
@@ -0,0 +1 @@
+com.example.spi.mongodb.atlas.AtlasRepository=com.example.spi.mongodb.atlas.AtlasRepositoryFragment
diff --git a/mongodb/fragment-spi/pom.xml b/mongodb/fragment-spi/pom.xml
new file mode 100644
index 000000000..e6d1b8675
--- /dev/null
+++ b/mongodb/fragment-spi/pom.xml
@@ -0,0 +1,19 @@
+
+ 4.0.0
+
+
+ org.springframework.data.examples
+ spring-data-mongodb-examples
+ 2.0.0.BUILD-SNAPSHOT
+
+
+ spring-data-mongodb-fragment-spi
+ Spring Data MongoDB - Reusable Fragments
+ pom
+
+
+ atlas-api
+ sample
+
+
diff --git a/mongodb/fragment-spi/sample/pom.xml b/mongodb/fragment-spi/sample/pom.xml
new file mode 100644
index 000000000..0246d9fe2
--- /dev/null
+++ b/mongodb/fragment-spi/sample/pom.xml
@@ -0,0 +1,26 @@
+
+ 4.0.0
+
+
+ org.springframework.data.examples
+ spring-data-mongodb-fragment-spi
+ 2.0.0.BUILD-SNAPSHOT
+
+
+ spring-data-mongodb-fragment-spi-usage
+ Spring Data MongoDB - Reusable Fragments - Fragment Usage
+
+
+
+ org.springframework.data.examples
+ spring-data-mongodb-fragment-spi-atlas
+ ${project.version}
+
+
+ org.springframework.data.examples
+ spring-data-mongodb-example-utils
+ test
+
+
+
diff --git a/mongodb/fragment-spi/sample/src/main/java/com/example/data/mongodb/ApplicationConfiguration.java b/mongodb/fragment-spi/sample/src/main/java/com/example/data/mongodb/ApplicationConfiguration.java
new file mode 100644
index 000000000..0d553155d
--- /dev/null
+++ b/mongodb/fragment-spi/sample/src/main/java/com/example/data/mongodb/ApplicationConfiguration.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2024 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 com.example.data.mongodb;
+
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+/**
+ * @author Christoph Strobl
+ */
+@SpringBootApplication
+public class ApplicationConfiguration {
+
+}
diff --git a/mongodb/fragment-spi/sample/src/main/java/com/example/data/mongodb/Movie.java b/mongodb/fragment-spi/sample/src/main/java/com/example/data/mongodb/Movie.java
new file mode 100644
index 000000000..6af282b00
--- /dev/null
+++ b/mongodb/fragment-spi/sample/src/main/java/com/example/data/mongodb/Movie.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2024 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 com.example.data.mongodb;
+
+import org.springframework.data.mongodb.core.mapping.Document;
+
+/**
+ * @author Christoph Strobl
+ */
+@Document("movies")
+public class Movie {
+
+ private String id;
+ private String title;
+ private String plot;
+
+ public String getPlot() {
+ return plot;
+ }
+
+ public void setPlot(String plot) {
+ this.plot = plot;
+ }
+
+ public String getId() {
+ return id;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ public String getTitle() {
+ return title;
+ }
+
+ public void setTitle(String title) {
+ this.title = title;
+ }
+
+ @Override
+ public String toString() {
+ return "Movie{" +
+ "id='" + id + '\'' +
+ ", title='" + title + '\'' +
+ ", plot='" + plot + '\'' +
+ '}';
+ }
+}
diff --git a/mongodb/fragment-spi/sample/src/main/java/com/example/data/mongodb/MovieRepository.java b/mongodb/fragment-spi/sample/src/main/java/com/example/data/mongodb/MovieRepository.java
new file mode 100644
index 000000000..4ef73c58f
--- /dev/null
+++ b/mongodb/fragment-spi/sample/src/main/java/com/example/data/mongodb/MovieRepository.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2024 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 com.example.data.mongodb;
+
+import com.example.spi.mongodb.atlas.AtlasRepository;
+import org.springframework.data.repository.CrudRepository;
+
+/**
+ * @author Christoph Strobl
+ */
+public interface MovieRepository extends CrudRepository, AtlasRepository {
+
+}
diff --git a/mongodb/fragment-spi/sample/src/main/resources/application.properties b/mongodb/fragment-spi/sample/src/main/resources/application.properties
new file mode 100644
index 000000000..8b1378917
--- /dev/null
+++ b/mongodb/fragment-spi/sample/src/main/resources/application.properties
@@ -0,0 +1 @@
+
diff --git a/mongodb/fragment-spi/sample/src/test/java/com/example/data/mongodb/MovieRepositoryTests.java b/mongodb/fragment-spi/sample/src/test/java/com/example/data/mongodb/MovieRepositoryTests.java
new file mode 100644
index 000000000..747cbcbe0
--- /dev/null
+++ b/mongodb/fragment-spi/sample/src/test/java/com/example/data/mongodb/MovieRepositoryTests.java
@@ -0,0 +1,273 @@
+/*
+ * Copyright 2024 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
+ *
+ * http://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 com.example.data.mongodb;
+
+import example.springdata.mongodb.util.AtlasContainer;
+import example.springdata.mongodb.util.MongoContainers;
+
+import java.util.List;
+import java.util.Objects;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.slf4j.event.Level;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.core.io.Resource;
+import org.springframework.test.context.DynamicPropertyRegistry;
+import org.springframework.test.context.DynamicPropertySource;
+
+import org.testcontainers.junit.jupiter.Container;
+import org.testcontainers.junit.jupiter.Testcontainers;
+
+import com.mongodb.client.MongoClient;
+
+/**
+ * Integration test for MongoDB Atlas Vector Search.
+ *
+ * @author Christoph Strobl
+ */
+@SpringBootTest
+@Testcontainers
+class MovieRepositoryTests {
+
+ private static final Logger log = LoggerFactory.getLogger(MovieRepositoryTests.class);
+
+ private static @Container AtlasContainer atlasLocal = MongoContainers.getAtlasContainer();
+
+ @DynamicPropertySource
+ static void setProperties(DynamicPropertyRegistry registry) {
+ registry.add("spring.data.mongodb.uri", atlasLocal::getConnectionString);
+ }
+
+ @Value("classpath:/mflix.embedded_movies.json.gz") Resource moviesResource;
+ @Autowired MovieRepository repository;
+ @Autowired MongoClient client;
+
+ @BeforeEach
+ void setUp() {
+
+ Movies movies = new Movies(client);
+ if (!movies.alreadyInitialized()) {
+ movies.initialize(moviesResource);
+ }
+ }
+
+ @Test
+ void testVectorSearch() {
+
+ List result = repository.vectorSearch("plot_vector_index", "plot_embedding", List.of(vectors));
+ result.stream().map(Objects::toString).forEach(log.atLevel(Level.INFO)::log);
+ }
+
+ private static final Double[] vectors = { -0.0016261312, -0.028070757, -0.011342932, -0.012775794, -0.0027440966,
+ 0.008683807, -0.02575152, -0.02020668, -0.010283281, -0.0041719596, 0.021392956, 0.028657231, -0.006634482,
+ 0.007490867, 0.018593878, 0.0038187427, 0.029590257, -0.01451522, 0.016061379, 0.00008528442, -0.008943722,
+ 0.01627464, 0.024311995, -0.025911469, 0.00022596726, -0.008863748, 0.008823762, -0.034921836, 0.007910728,
+ -0.01515501, 0.035801545, -0.0035688248, -0.020299982, -0.03145631, -0.032256044, -0.028763862, -0.0071576433,
+ -0.012769129, 0.012322609, -0.006621153, 0.010583182, 0.024085402, -0.001623632, 0.007864078, -0.021406285,
+ 0.002554159, 0.012229307, -0.011762793, 0.0051682983, 0.0048484034, 0.018087378, 0.024325324, -0.037694257,
+ -0.026537929, -0.008803768, -0.017767483, -0.012642504, -0.0062712682, 0.0009771782, -0.010409906, 0.017754154,
+ -0.004671795, -0.030469967, 0.008477209, -0.005218282, -0.0058480743, -0.020153364, -0.0032805866, 0.004248601,
+ 0.0051449724, 0.006791097, 0.007650814, 0.003458861, -0.0031223053, -0.01932697, -0.033615597, 0.00745088,
+ 0.006321252, -0.0038154104, 0.014555207, 0.027697546, -0.02828402, 0.0066711367, 0.0077107945, 0.01794076,
+ 0.011349596, -0.0052715978, 0.014755142, -0.019753495, -0.011156326, 0.011202978, 0.022126047, 0.00846388,
+ 0.030549942, -0.0041386373, 0.018847128, -0.00033655585, 0.024925126, -0.003555496, -0.019300312, 0.010749794,
+ 0.0075308536, -0.018287312, -0.016567878, -0.012869096, -0.015528221, 0.0078107617, -0.011156326, 0.013522214,
+ -0.020646535, -0.01211601, 0.055928253, 0.011596181, -0.017247654, 0.0005939711, -0.026977783, -0.003942035,
+ -0.009583511, -0.0055248477, -0.028737204, 0.023179034, 0.003995351, 0.0219661, -0.008470545, 0.023392297,
+ 0.010469886, -0.015874773, 0.007890735, -0.009690142, -0.00024970944, 0.012775794, 0.0114762215, 0.013422247,
+ 0.010429899, -0.03686786, -0.006717788, -0.027484283, 0.011556195, -0.036068123, -0.013915418, -0.0016327957,
+ 0.0151016945, -0.020473259, 0.004671795, -0.012555866, 0.0209531, 0.01982014, 0.024485271, 0.0105431955,
+ -0.005178295, 0.033162415, -0.013795458, 0.007150979, 0.010243294, 0.005644808, 0.017260984, -0.0045618312,
+ 0.0024725192, 0.004305249, -0.008197301, 0.0014203656, 0.0018460588, 0.005015015, -0.011142998, 0.01439526,
+ 0.022965772, 0.02552493, 0.007757446, -0.0019726837, 0.009503538, -0.032042783, 0.008403899, -0.04609149,
+ 0.013808787, 0.011749465, 0.036388017, 0.016314628, 0.021939443, -0.0250051, -0.017354285, -0.012962398,
+ 0.00006107364, 0.019113706, 0.03081652, -0.018114036, -0.0084572155, 0.009643491, -0.0034721901, 0.0072642746,
+ -0.0090636825, 0.01642126, 0.013428912, 0.027724205, 0.0071243206, -0.6858542, -0.031029783, -0.014595194,
+ -0.011449563, 0.017514233, 0.01743426, 0.009950057, 0.0029706885, -0.015714826, -0.001806072, 0.011856096,
+ 0.026444625, -0.0010663156, -0.006474535, 0.0016161345, -0.020313311, 0.0148351155, -0.0018393943, 0.0057347785,
+ 0.018300641, -0.018647194, 0.03345565, -0.008070676, 0.0071443142, 0.014301958, 0.0044818576, 0.003838736,
+ -0.007350913, -0.024525259, -0.001142124, -0.018620536, 0.017247654, 0.007037683, 0.010236629, 0.06046009,
+ 0.0138887605, -0.012122675, 0.037694257, 0.0055081863, 0.042492677, 0.00021784494, -0.011656162, 0.010276617,
+ 0.022325981, 0.005984696, -0.009496873, 0.013382261, -0.0010563189, 0.0026507939, -0.041639622, 0.008637156,
+ 0.026471283, -0.008403899, 0.024858482, -0.00066686375, -0.0016252982, 0.027590916, 0.0051449724, 0.0058647357,
+ -0.008743787, -0.014968405, 0.027724205, -0.011596181, 0.0047650975, -0.015381602, 0.0043718936, 0.002159289,
+ 0.035908177, -0.008243952, -0.030443309, 0.027564257, 0.042625964, -0.0033688906, 0.01843393, 0.019087048,
+ 0.024578573, 0.03268257, -0.015608194, -0.014128681, -0.0033538956, -0.0028757197, -0.004121976, -0.032389335,
+ 0.0034322033, 0.058807302, 0.010943064, -0.030523283, 0.008903735, 0.017500903, 0.00871713, -0.0029406983,
+ 0.013995391, -0.03132302, -0.019660193, -0.00770413, -0.0038853872, 0.0015894766, -0.0015294964, -0.006251275,
+ -0.021099718, -0.010256623, -0.008863748, 0.028550599, 0.02020668, -0.0012962399, -0.003415542, -0.0022509254,
+ 0.0119360695, 0.027590916, -0.046971202, -0.0015194997, -0.022405956, 0.0016677842, -0.00018535563, -0.015421589,
+ -0.031802863, 0.03814744, 0.0065411795, 0.016567878, -0.015621523, 0.022899127, -0.011076353, 0.02841731,
+ -0.002679118, -0.002342562, 0.015341615, 0.01804739, -0.020566562, -0.012989056, -0.002990682, 0.01643459,
+ 0.00042527664, 0.008243952, -0.013715484, -0.004835075, -0.009803439, 0.03129636, -0.021432944, 0.0012087687,
+ -0.015741484, -0.0052016205, 0.00080890034, -0.01755422, 0.004811749, -0.017967418, -0.026684547, -0.014128681,
+ 0.0041386373, -0.013742141, -0.010056688, -0.013268964, -0.0110630235, -0.028337335, 0.015981404, -0.00997005,
+ -0.02424535, -0.013968734, -0.028310679, -0.027750863, -0.020699851, 0.02235264, 0.001057985, 0.00081639783,
+ -0.0099367285, 0.013522214, -0.012016043, -0.00086471526, 0.013568865, 0.0019376953, -0.019020405, 0.017460918,
+ -0.023045745, 0.008503866, 0.0064678704, -0.011509543, 0.018727167, -0.003372223, -0.0028690554, -0.0027024434,
+ -0.011902748, -0.012182655, -0.015714826, -0.0098634185, 0.00593138, 0.018753825, 0.0010146659, 0.013029044,
+ 0.0003521757, -0.017620865, 0.04102649, 0.00552818, 0.024485271, -0.009630162, -0.015608194, 0.0006718621,
+ -0.0008418062, 0.012395918, 0.0057980907, 0.016221326, 0.010616505, 0.004838407, -0.012402583, 0.019900113,
+ -0.0034521967, 0.000247002, -0.03153628, 0.0011038032, -0.020819811, 0.016234655, -0.00330058, -0.0032289368,
+ 0.00078973995, -0.021952773, -0.022459272, 0.03118973, 0.03673457, -0.021472929, 0.0072109587, -0.015075036,
+ 0.004855068, -0.0008151483, 0.0069643734, 0.010023367, -0.010276617, -0.023019087, 0.0068244194, -0.0012520878,
+ -0.0015086699, 0.022046074, -0.034148756, -0.0022192693, 0.002427534, -0.0027124402, 0.0060346797, 0.015461575,
+ 0.0137554705, 0.009230294, -0.009583511, 0.032629255, 0.015994733, -0.019167023, -0.009203636, 0.03393549,
+ -0.017274313, -0.012042701, -0.0009930064, 0.026777849, -0.013582194, -0.0027590916, -0.017594207, -0.026804507,
+ -0.0014236979, -0.022032745, 0.0091236625, -0.0042419364, -0.00858384, -0.0033905501, -0.020739838, 0.016821127,
+ 0.022539245, 0.015381602, 0.015141681, 0.028817179, -0.019726837, -0.0051283115, -0.011489551, -0.013208984,
+ -0.0047017853, -0.0072309524, 0.01767418, 0.0025658219, -0.010323267, 0.012609182, -0.028097415, 0.026871152,
+ -0.010276617, 0.021912785, 0.0022542577, 0.005124979, -0.0019710176, 0.004518512, -0.040360045, 0.010969722,
+ -0.0031539614, -0.020366628, -0.025778178, -0.0110030435, -0.016221326, 0.0036587953, 0.016207997, 0.003007343,
+ -0.0032555948, 0.0044052163, -0.022046074, -0.0008822095, -0.009363583, 0.028230704, -0.024538586, 0.0029840174,
+ 0.0016044717, -0.014181997, 0.031349678, -0.014381931, -0.027750863, 0.02613806, 0.0004136138, -0.005748107,
+ -0.01868718, -0.0010138329, 0.0054348772, 0.010703143, -0.003682121, 0.0030856507, -0.004275259, -0.010403241,
+ 0.021113047, -0.022685863, -0.023032416, 0.031429652, 0.001792743, -0.005644808, -0.011842767, -0.04078657,
+ -0.0026874484, 0.06915057, -0.00056939584, -0.013995391, 0.010703143, -0.013728813, -0.022939114, -0.015261642,
+ -0.022485929, 0.016807798, 0.007964044, 0.0144219175, 0.016821127, 0.0076241563, 0.005461535, -0.013248971,
+ 0.015301628, 0.0085171955, -0.004318578, 0.011136333, -0.0059047225, -0.010249958, -0.018207338, 0.024645219,
+ 0.021752838, 0.0007614159, -0.013648839, 0.01111634, -0.010503208, -0.0038487327, -0.008203966, -0.00397869,
+ 0.0029740208, 0.008530525, 0.005261601, 0.01642126, -0.0038753906, -0.013222313, 0.026537929, 0.024671877,
+ -0.043505676, 0.014195326, 0.024778508, 0.0056914594, -0.025951454, 0.017620865, -0.0021359634, 0.008643821,
+ 0.021299653, 0.0041686273, -0.009017031, 0.04044002, 0.024378639, -0.027777521, -0.014208655, 0.0028623908,
+ 0.042119466, 0.005801423, -0.028124074, -0.03129636, 0.022139376, -0.022179363, -0.04067994, 0.013688826,
+ 0.013328944, 0.0046184794, -0.02828402, -0.0063412455, -0.0046184794, -0.011756129, -0.010383247, -0.0018543894,
+ -0.0018593877, -0.00052024535, 0.004815081, 0.014781799, 0.018007403, 0.01306903, -0.020433271, 0.009043689,
+ 0.033189073, -0.006844413, -0.019766824, -0.018767154, 0.00533491, -0.0024575242, 0.018727167, 0.0058080875,
+ -0.013835444, 0.0040719924, 0.004881726, 0.012029372, 0.005664801, 0.03193615, 0.0058047553, 0.002695779,
+ 0.009290274, 0.02361889, 0.017834127, 0.0049017193, -0.0036388019, 0.010776452, -0.019793482, 0.0067777685,
+ -0.014208655, -0.024911797, 0.002385881, 0.0034988478, 0.020899786, -0.0025858153, -0.011849431, 0.033189073,
+ -0.021312982, 0.024965113, -0.014635181, 0.014048708, -0.0035921505, -0.003347231, 0.030869836, -0.0017161017,
+ -0.0061346465, 0.009203636, -0.025165047, 0.0068510775, 0.021499587, 0.013782129, -0.0024475274, -0.0051149824,
+ -0.024445284, 0.006167969, 0.0068844, -0.00076183246, 0.030150073, -0.0055948244, -0.011162991, -0.02057989,
+ -0.009703471, -0.020646535, 0.008004031, 0.0066378145, -0.019900113, -0.012169327, -0.01439526, 0.0044252095,
+ -0.004018677, 0.014621852, -0.025085073, -0.013715484, -0.017980747, 0.0071043274, 0.011456228, -0.01010334,
+ -0.0035321703, -0.03801415, -0.012036037, -0.0028990454, -0.05419549, -0.024058744, -0.024272008, 0.015221654,
+ 0.027964126, 0.03182952, -0.015354944, 0.004855068, 0.011522872, 0.004771762, 0.0027874154, 0.023405626,
+ 0.0004242353, -0.03132302, 0.007057676, 0.008763781, -0.0027057757, 0.023005757, -0.0071176565, -0.005238275,
+ 0.029110415, -0.010989714, 0.013728813, -0.009630162, -0.029137073, -0.0049317093, -0.0008630492, -0.015248313,
+ 0.0043219104, -0.0055681667, -0.013175662, 0.029723546, 0.025098402, 0.012849103, -0.0009996708, 0.03118973,
+ -0.0021709518, 0.0260181, -0.020526575, 0.028097415, -0.016141351, 0.010509873, -0.022965772, 0.002865723,
+ 0.0020493253, 0.0020509914, -0.0041419696, -0.00039695262, 0.017287642, 0.0038987163, 0.014795128, -0.014661839,
+ -0.008950386, 0.004431874, -0.009383577, 0.0012604183, -0.023019087, 0.0029273694, -0.033135757, 0.009176978,
+ -0.011023037, -0.002102641, 0.02663123, -0.03849399, -0.0044152127, 0.0004527676, -0.0026924468, 0.02828402,
+ 0.017727496, 0.035135098, 0.02728435, -0.005348239, -0.001467017, -0.019766824, 0.014715155, 0.011982721,
+ 0.0045651635, 0.023458943, -0.0010046692, -0.0031373003, -0.0006972704, 0.0019043729, -0.018967088, -0.024311995,
+ 0.0011546199, 0.007977373, -0.004755101, -0.010016702, -0.02780418, -0.004688456, 0.013022379, -0.005484861,
+ 0.0017227661, -0.015394931, -0.028763862, -0.026684547, 0.0030589928, -0.018513903, 0.028363993, 0.0044818576,
+ -0.009270281, 0.038920518, -0.016008062, 0.0093902415, 0.004815081, -0.021059733, 0.01451522, -0.0051583014,
+ 0.023765508, -0.017874114, -0.016821127, -0.012522544, -0.0028390652, 0.0040886537, 0.020259995, -0.031216389,
+ -0.014115352, -0.009176978, 0.010303274, 0.020313311, 0.0064112223, -0.02235264, -0.022872468, 0.0052449396,
+ 0.0005723116, 0.0037321046, 0.016807798, -0.018527232, -0.009303603, 0.0024858483, -0.0012662497, -0.007110992,
+ 0.011976057, -0.007790768, -0.042999174, -0.006727785, -0.011829439, 0.007024354, 0.005278262, -0.017740825,
+ -0.0041519664, 0.0085905045, 0.027750863, -0.038387362, 0.024391968, 0.00087721116, 0.010509873, -0.00038508154,
+ -0.006857742, 0.0183273, -0.0037054466, 0.015461575, 0.0017394272, -0.0017944091, 0.014181997, -0.0052682655,
+ 0.009023695, 0.00719763, -0.013522214, 0.0034422, 0.014941746, -0.0016711164, -0.025298337, -0.017634194,
+ 0.0058714002, -0.005321581, 0.017834127, 0.0110630235, -0.03369557, 0.029190388, -0.008943722, 0.009363583,
+ -0.0034222065, -0.026111402, -0.007037683, -0.006561173, 0.02473852, -0.007084334, -0.010110005, -0.008577175,
+ 0.0030439978, -0.022712521, 0.0054582027, -0.0012620845, -0.0011954397, -0.015741484, 0.0129557345,
+ -0.00042111133, 0.00846388, 0.008930393, 0.016487904, 0.010469886, -0.007917393, -0.011762793, -0.0214596,
+ 0.000917198, 0.021672864, 0.010269952, -0.007737452, -0.010243294, -0.0067244526, -0.015488233, -0.021552904,
+ 0.017127695, 0.011109675, 0.038067464, 0.00871713, -0.0025591573, 0.021312982, -0.006237946, 0.034628596,
+ -0.0045251767, 0.008357248, 0.020686522, 0.0010696478, 0.0076708077, 0.03772091, -0.018700508, -0.0020676525,
+ -0.008923728, -0.023298996, 0.018233996, -0.010256623, 0.0017860786, 0.009796774, -0.00897038, -0.01269582,
+ -0.018527232, 0.009190307, -0.02372552, -0.042119466, 0.008097334, -0.0066778013, -0.021046404, 0.0019593548,
+ 0.011083017, -0.0016028056, 0.012662497, -0.000059095124, 0.0071043274, -0.014675168, 0.024831824, -0.053582355,
+ 0.038387362, 0.0005698124, 0.015954746, 0.021552904, 0.031589597, -0.009230294, -0.0006147976, 0.002625802,
+ -0.011749465, -0.034362018, -0.0067844326, -0.018793812, 0.011442899, -0.008743787, 0.017474247, -0.021619547,
+ 0.01831397, -0.009037024, -0.0057247817, -0.02728435, 0.010363255, 0.034415334, -0.024032086, -0.0020126705,
+ -0.0045518344, -0.019353628, -0.018340627, -0.03129636, -0.0034038792, -0.006321252, -0.0016161345, 0.033642255,
+ -0.000056075285, -0.005005019, 0.004571828, -0.0024075406, -0.00010215386, 0.0098634185, 0.1980148, -0.003825407,
+ -0.025191706, 0.035161756, 0.005358236, 0.025111731, 0.023485601, 0.0023342315, -0.011882754, 0.018287312,
+ -0.0068910643, 0.003912045, 0.009243623, -0.001355387, -0.028603915, -0.012802451, -0.030150073, -0.014795128,
+ -0.028630573, -0.0013487226, 0.002667455, 0.00985009, -0.0033972147, -0.021486258, 0.009503538, -0.017847456,
+ 0.013062365, -0.014341944, 0.005078328, 0.025165047, -0.015594865, -0.025924796, -0.0018177348, 0.010996379,
+ -0.02993681, 0.007324255, 0.014475234, -0.028577257, 0.005494857, 0.00011725306, -0.013315615, 0.015941417,
+ 0.009376912, 0.0025158382, 0.008743787, 0.023832154, -0.008084005, -0.014195326, -0.008823762, 0.0033455652,
+ -0.032362677, -0.021552904, -0.0056081535, 0.023298996, -0.025444955, 0.0097301295, 0.009736794, 0.015274971,
+ -0.0012937407, -0.018087378, -0.0039387033, 0.008637156, -0.011189649, -0.00023846315, -0.011582852, 0.0066411467,
+ -0.018220667, 0.0060846633, 0.0376676, -0.002709108, 0.0072776037, 0.0034188742, -0.010249958, -0.0007747449,
+ -0.00795738, -0.022192692, 0.03910712, 0.032122757, 0.023898797, 0.0076241563, -0.007397564, -0.003655463,
+ 0.011442899, -0.014115352, -0.00505167, -0.031163072, 0.030336678, -0.006857742, -0.022259338, 0.004048667,
+ 0.02072651, 0.0030156737, -0.0042119464, 0.00041861215, -0.005731446, 0.011103011, 0.013822115, 0.021512916,
+ 0.009216965, -0.006537847, -0.027057758, -0.04054665, 0.010403241, -0.0056281467, -0.005701456, -0.002709108,
+ -0.00745088, -0.0024841821, 0.009356919, -0.022659205, 0.004061996, -0.013175662, 0.017074378, -0.006141311,
+ -0.014541878, 0.02993681, -0.00028448965, -0.025271678, 0.011689484, -0.014528549, 0.004398552, -0.017274313,
+ 0.0045751603, 0.012455898, 0.004121976, -0.025458284, -0.006744446, 0.011822774, -0.015035049, -0.03257594,
+ 0.014675168, -0.0039187097, 0.019726837, -0.0047251107, 0.0022825818, 0.011829439, 0.005391558, -0.016781142,
+ -0.0058747325, 0.010309938, -0.013049036, 0.01186276, -0.0011246296, 0.0062112883, 0.0028190718, -0.021739509,
+ 0.009883412, -0.0073175905, -0.012715813, -0.017181009, -0.016607866, -0.042492677, -0.0014478565, -0.01794076,
+ 0.012302616, -0.015194997, -0.04433207, -0.020606548, 0.009696807, 0.010303274, -0.01694109, -0.004018677,
+ 0.019353628, -0.001991011, 0.000058938927, 0.010536531, -0.17274313, 0.010143327, 0.014235313, -0.024152048,
+ 0.025684876, -0.0012504216, 0.036601283, -0.003698782, 0.0007310093, 0.004165295, -0.0029157067, 0.017101036,
+ -0.046891227, -0.017460918, 0.022965772, 0.020233337, -0.024072073, 0.017220996, 0.009370248, 0.0010363255,
+ 0.0194336, -0.019606877, 0.01818068, -0.020819811, 0.007410893, 0.0019326969, 0.017887443, 0.006651143,
+ 0.00067394477, -0.011889419, -0.025058415, -0.008543854, 0.021579562, 0.0047484366, 0.014062037, 0.0075508473,
+ -0.009510202, -0.009143656, 0.0046817916, 0.013982063, -0.0027990784, 0.011782787, 0.014541878, -0.015701497,
+ -0.029350337, 0.021979429, 0.01332228, -0.026244693, -0.0123492675, -0.003895384, 0.0071576433, -0.035454992,
+ -0.00046984528, 0.0033522295, 0.039347045, 0.0005119148, 0.00476843, -0.012995721, 0.0024042083, -0.006931051,
+ -0.014461905, -0.0127558, 0.0034555288, -0.0074842023, -0.030256703, -0.007057676, -0.00807734, 0.007804097,
+ -0.006957709, 0.017181009, -0.034575284, -0.008603834, -0.005008351, -0.015834786, 0.02943031, 0.016861115,
+ -0.0050849924, 0.014235313, 0.0051449724, 0.0025924798, -0.0025741523, 0.04289254, -0.002104307, 0.012969063,
+ -0.008310596, 0.00423194, 0.0074975314, 0.0018810473, -0.014248641, -0.024725191, 0.0151016945, -0.017527562,
+ 0.0018727167, 0.0002830318, 0.015168339, 0.0144219175, -0.004048667, -0.004358565, 0.011836103, -0.010343261,
+ -0.005911387, 0.0022825818, 0.0073175905, 0.00403867, 0.013188991, 0.03334902, 0.006111321, 0.008597169,
+ 0.030123414, -0.015474904, 0.0017877447, -0.024551915, 0.013155668, 0.023525586, -0.0255116, 0.017220996,
+ 0.004358565, -0.00934359, 0.0099967085, 0.011162991, 0.03092315, -0.021046404, -0.015514892, 0.0011946067,
+ -0.01816735, 0.010876419, -0.10124666, -0.03550831, 0.0056348112, 0.013942076, 0.005951374, 0.020419942,
+ -0.006857742, -0.020873128, -0.021259667, 0.0137554705, 0.0057880944, -0.029163731, -0.018767154, -0.021392956,
+ 0.030896494, -0.005494857, -0.0027307675, -0.006801094, -0.014821786, 0.021392956, -0.0018110704, -0.0018843795,
+ -0.012362596, -0.0072176233, -0.017194338, -0.018713837, -0.024272008, 0.03801415, 0.00015880188, 0.0044951867,
+ -0.028630573, -0.0014070367, -0.00916365, -0.026537929, -0.009576847, -0.013995391, -0.0077107945, 0.0050016865,
+ 0.00578143, -0.04467862, 0.008363913, 0.010136662, -0.0006268769, -0.006591163, 0.015341615, -0.027377652,
+ -0.00093136, 0.029243704, -0.020886457, -0.01041657, -0.02424535, 0.005291591, -0.02980352, -0.009190307,
+ 0.019460259, -0.0041286405, 0.004801752, 0.0011787785, -0.001257086, -0.011216307, -0.013395589, 0.00088137644,
+ -0.0051616337, 0.03876057, -0.0033455652, 0.00075850025, -0.006951045, -0.0062112883, 0.018140694, -0.006351242,
+ -0.008263946, 0.018154023, -0.012189319, 0.0075508473, -0.044358727, -0.0040153447, 0.0093302615, -0.010636497,
+ 0.032789204, -0.005264933, -0.014235313, -0.018393943, 0.007297597, -0.016114693, 0.015021721, 0.020033404,
+ 0.0137688, 0.0011046362, 0.010616505, -0.0039453674, 0.012109346, 0.021099718, -0.0072842683, -0.019153694,
+ -0.003768759, 0.039320387, -0.006747778, -0.0016852784, 0.018154023, 0.0010963057, -0.015035049, -0.021033075,
+ -0.04345236, 0.017287642, 0.016341286, -0.008610498, 0.00236922, 0.009290274, 0.028950468, -0.014475234,
+ -0.0035654926, 0.015434918, -0.03372223, 0.004501851, -0.012929076, -0.008483873, -0.0044685286, -0.0102233,
+ 0.01615468, 0.0022792495, 0.010876419, -0.0059647025, 0.01895376, -0.0069976957, -0.0042952523, 0.017207667,
+ -0.00036133936, 0.0085905045, 0.008084005, 0.03129636, -0.016994404, -0.014915089, 0.020100048, -0.012009379,
+ -0.006684466, 0.01306903, 0.00015765642, -0.00530492, 0.0005277429, 0.015421589, 0.015528221, 0.032202728,
+ -0.003485519, -0.0014286962, 0.033908837, 0.001367883, 0.010509873, 0.025271678, -0.020993087, 0.019846799,
+ 0.006897729, -0.010216636, -0.00725761, 0.01818068, -0.028443968, -0.011242964, -0.014435247, -0.013688826,
+ 0.006101324, -0.0022509254, 0.013848773, -0.0019077052, 0.017181009, 0.03422873, 0.005324913, -0.0035188415,
+ 0.014128681, -0.004898387, 0.005038341, 0.0012320944, -0.005561502, -0.017847456, 0.0008538855, -0.0047884234,
+ 0.011849431, 0.015421589, -0.013942076, 0.0029790192, -0.013702155, 0.0001199605, -0.024431955, 0.019926772,
+ 0.022179363, -0.016487904, -0.03964028, 0.0050849924, 0.017487574, 0.022792496, 0.0012504216, 0.004048667,
+ -0.00997005, 0.0076041627, -0.014328616, -0.020259995, 0.0005598157, -0.010469886, 0.0016852784, 0.01716768,
+ -0.008990373, -0.001987679, 0.026417969, 0.023792166, 0.0046917885, -0.0071909656, -0.00032051947, -0.023259008,
+ -0.009170313, 0.02071318, -0.03156294, -0.030869836, -0.006324584, 0.013795458, -0.00047151142, 0.016874444,
+ 0.00947688, 0.00985009, -0.029883493, 0.024205362, -0.013522214, -0.015075036, -0.030603256, 0.029270362,
+ 0.010503208, 0.021539574, 0.01743426, -0.023898797, 0.022019416, -0.0068777353, 0.027857494, -0.021259667,
+ 0.0025758184, 0.006197959, 0.006447877, -0.00025200035, -0.004941706, -0.021246338, -0.005504854, -0.008390571,
+ -0.0097301295, 0.027244363, -0.04446536, 0.05216949, 0.010243294, -0.016008062, 0.0122493, -0.0199401,
+ 0.009077012, 0.019753495, 0.006431216, -0.037960835, -0.027377652, 0.016381273, -0.0038620618, 0.022512587,
+ -0.010996379, -0.0015211658, -0.0102233, 0.007071005, 0.008230623, -0.009490209, -0.010083347, 0.024431955,
+ 0.002427534, 0.02828402, 0.0035721571, -0.022192692, -0.011882754, 0.010056688, 0.0011904413, -0.01426197,
+ -0.017500903, -0.00010985966, 0.005591492, -0.0077707744, -0.012049366, 0.011869425, 0.00858384, -0.024698535,
+ -0.030283362, 0.020140035, 0.011949399, -0.013968734, 0.042732596, -0.011649498, -0.011982721, -0.016967745,
+ -0.0060913274, -0.007130985, -0.013109017, -0.009710136 };
+}
diff --git a/mongodb/fragment-spi/sample/src/test/java/com/example/data/mongodb/Movies.java b/mongodb/fragment-spi/sample/src/test/java/com/example/data/mongodb/Movies.java
new file mode 100644
index 000000000..f0ac5f150
--- /dev/null
+++ b/mongodb/fragment-spi/sample/src/test/java/com/example/data/mongodb/Movies.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright 2024 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
+ *
+ * http://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 com.example.data.mongodb;
+
+import lombok.SneakyThrows;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.zip.GZIPInputStream;
+
+import org.bson.Document;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.slf4j.event.Level;
+
+import org.springframework.core.io.Resource;
+
+import org.testcontainers.shaded.com.fasterxml.jackson.databind.JsonNode;
+import org.testcontainers.shaded.com.fasterxml.jackson.databind.ObjectMapper;
+
+import com.mongodb.client.MongoClient;
+import com.mongodb.client.model.InsertOneModel;
+
+/**
+ * @author Christoph Strobl
+ */
+class Movies {
+
+ private static final Logger log = LoggerFactory.getLogger(Movies.class);
+
+ private final MongoClient client;
+ static final String DATABASE = "test";
+ static final String COLLECTION = "movies";
+
+ public Movies(MongoClient client) {
+ this.client = client;
+ }
+
+ boolean alreadyInitialized() {
+ return client.getDatabase(DATABASE).getCollection(COLLECTION).estimatedDocumentCount() > 0;
+ }
+
+ @SneakyThrows
+ void initialize(Resource resource) {
+
+ loadSampleData(resource);
+ createVectorIndex();
+ }
+
+ @SneakyThrows
+ private void createVectorIndex() {
+
+ log.atLevel(Level.INFO).log("creating vector index");
+ client.getDatabase(DATABASE).runCommand(createSearchIndexDefinition());
+ Thread.sleep(5000); // this takes time
+ log.atLevel(Level.INFO).log("index 'plot_vector_index' created");
+ }
+
+ private void loadSampleData(Resource resource) throws IOException, InterruptedException {
+
+ log.atLevel(Level.INFO).log("Loading movies from {}", resource);
+
+ InputStream stream = new GZIPInputStream(resource.getInputStream());
+ ObjectMapper mapper = new ObjectMapper();
+ JsonNode node = mapper.readerFor(JsonNode.class).readTree(stream);
+
+ if (node.isArray()) {
+
+ Iterator elements = node.elements();
+ List> bulk = new ArrayList<>(node.size());
+
+ while (elements.hasNext()) {
+ bulk.add(new InsertOneModel<>(Document.parse(elements.next().toString())));
+ }
+
+ client.getDatabase(DATABASE).getCollection(COLLECTION).bulkWrite(bulk);
+ log.atLevel(Level.INFO).log("Created {} movies in {}.{}", node.size(), DATABASE, COLLECTION);
+ }
+
+ Thread.sleep(2000); // give writes a little time to complete'
+ }
+
+ private org.bson.Document createSearchIndexDefinition() {
+
+ List vectorFields = List.of(new org.bson.Document().append("type", "vector")
+ .append("path", "plot_embedding").append("numDimensions", 1536).append("similarity", "cosine"));
+
+ return new org.bson.Document().append("createSearchIndexes", COLLECTION).append("indexes",
+ List.of(new org.bson.Document().append("name", "plot_vector_index").append("type", "vectorSearch")
+ .append("definition", new org.bson.Document("fields", vectorFields))));
+ }
+}
diff --git a/mongodb/fragment-spi/sample/src/test/resources/mflix.embedded_movies.json.gz b/mongodb/fragment-spi/sample/src/test/resources/mflix.embedded_movies.json.gz
new file mode 100644
index 000000000..7c52d2b1d
Binary files /dev/null and b/mongodb/fragment-spi/sample/src/test/resources/mflix.embedded_movies.json.gz differ
diff --git a/mongodb/pom.xml b/mongodb/pom.xml
index e74e61223..04d2c282c 100644
--- a/mongodb/pom.xml
+++ b/mongodb/pom.xml
@@ -17,7 +17,8 @@
2011
- aggregation
+ aot-optimization
+ aggregationexamplefluent-api
@@ -35,7 +36,8 @@
querydsllinkingutil
-
+ fragment-spi
+
diff --git a/mongodb/querydsl/src/test/java/example/springdata/mongodb/imperative/CustomerRepositoryTests.java b/mongodb/querydsl/src/test/java/example/springdata/mongodb/imperative/CustomerRepositoryTests.java
index aad71680f..04ffd7017 100644
--- a/mongodb/querydsl/src/test/java/example/springdata/mongodb/imperative/CustomerRepositoryTests.java
+++ b/mongodb/querydsl/src/test/java/example/springdata/mongodb/imperative/CustomerRepositoryTests.java
@@ -19,10 +19,9 @@
import example.springdata.mongodb.Customer;
import example.springdata.mongodb.QCustomer;
-
+import example.springdata.mongodb.util.MongoContainers;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
-
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.data.mongo.DataMongoTest;
import org.springframework.data.mongodb.core.MongoOperations;
@@ -31,7 +30,6 @@
import org.testcontainers.containers.MongoDBContainer;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;
-import org.testcontainers.utility.DockerImageName;
/**
* @author Christoph Strobl
@@ -41,8 +39,7 @@
class CustomerRepositoryTests {
@Container //
- private static MongoDBContainer mongoDBContainer = new MongoDBContainer(
- DockerImageName.parse("mongo:5.0"));
+ private static MongoDBContainer mongoDBContainer = MongoContainers.getDefaultContainer();
@DynamicPropertySource
static void setProperties(DynamicPropertyRegistry registry) {
diff --git a/mongodb/reactive/src/main/java/example/springdata/mongodb/people/ReactivePersonRepository.java b/mongodb/reactive/src/main/java/example/springdata/mongodb/people/ReactivePersonRepository.java
index adc63a317..1004a90e0 100644
--- a/mongodb/reactive/src/main/java/example/springdata/mongodb/people/ReactivePersonRepository.java
+++ b/mongodb/reactive/src/main/java/example/springdata/mongodb/people/ReactivePersonRepository.java
@@ -15,6 +15,9 @@
*/
package example.springdata.mongodb.people;
+import java.util.List;
+
+import org.springframework.data.domain.Limit;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
@@ -37,6 +40,17 @@ public interface ReactivePersonRepository extends ReactiveCrudRepository findByLastname(String lastname);
+ /**
+ * Find at most the number of users defined via maxResults with the given lastname.
+ * This method will be translated into a query by constructing it directly from the method name as there is no other
+ * query declared.
+ *
+ * @param lastname
+ * @param maxResults the maximum number of results returned.
+ * @return
+ */
+ Flux findByLastname(String lastname, Limit maxResults);
+
/**
* String query selecting one entity.
*
diff --git a/mongodb/reactive/src/test/java/example/springdata/mongodb/people/ReactiveMongoTemplateIntegrationTest.java b/mongodb/reactive/src/test/java/example/springdata/mongodb/people/ReactiveMongoTemplateIntegrationTest.java
index a42748612..bda03aa17 100644
--- a/mongodb/reactive/src/test/java/example/springdata/mongodb/people/ReactiveMongoTemplateIntegrationTest.java
+++ b/mongodb/reactive/src/test/java/example/springdata/mongodb/people/ReactiveMongoTemplateIntegrationTest.java
@@ -60,7 +60,8 @@ static void setProperties(DynamicPropertyRegistry registry) {
@BeforeEach
void setUp() {
- StepVerifier.create(template.dropCollection(Person.class)).verifyComplete();
+ template.dropCollection(Person.class).as(StepVerifier::create) //
+ .verifyComplete();
var insertAll = template
.insertAll(Flux.just(new Person("Walter", "White", 50), //
diff --git a/mongodb/reactive/src/test/java/example/springdata/mongodb/people/ReactivePersonRepositoryIntegrationTest.java b/mongodb/reactive/src/test/java/example/springdata/mongodb/people/ReactivePersonRepositoryIntegrationTest.java
index 7224a1f1d..194659b47 100644
--- a/mongodb/reactive/src/test/java/example/springdata/mongodb/people/ReactivePersonRepositoryIntegrationTest.java
+++ b/mongodb/reactive/src/test/java/example/springdata/mongodb/people/ReactivePersonRepositoryIntegrationTest.java
@@ -18,6 +18,7 @@
import static org.assertj.core.api.Assertions.*;
import example.springdata.mongodb.util.MongoContainers;
+import org.springframework.data.domain.Limit;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
@@ -157,6 +158,14 @@ void shouldQueryDataWithQueryDerivation() {
repository.findByLastname("White").as(StepVerifier::create).expectNextCount(2).verifyComplete();
}
+ /**
+ * Limit result size.
+ */
+ @Test
+ void shouldLimitResultSize() {
+ repository.findByLastname("White", Limit.of(1)).as(StepVerifier::create).expectNextCount(1).verifyComplete();
+ }
+
/**
* Fetch data using a string query.
*/
diff --git a/mongodb/util/src/main/java/example/springdata/mongodb/util/AtlasContainer.java b/mongodb/util/src/main/java/example/springdata/mongodb/util/AtlasContainer.java
new file mode 100644
index 000000000..f74158fc7
--- /dev/null
+++ b/mongodb/util/src/main/java/example/springdata/mongodb/util/AtlasContainer.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2024 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 example.springdata.mongodb.util;
+
+import java.util.List;
+
+import org.springframework.util.StringUtils;
+import org.testcontainers.containers.GenericContainer;
+import org.testcontainers.utility.DockerImageName;
+
+/**
+ * @author Christoph Strobl
+ */
+public class AtlasContainer extends GenericContainer {
+
+ private static final DockerImageName DEFAULT_IMAGE_NAME = DockerImageName.parse("mongodb/mongodb-atlas-local");
+ private static final String DEFAULT_TAG = "latest";
+ private static final String MONGODB_DATABASE_NAME_DEFAULT = "test";
+
+ public AtlasContainer() {
+ this(DEFAULT_IMAGE_NAME.withTag(DEFAULT_TAG));
+ }
+
+ public AtlasContainer(String dockerImageName) {
+ this(DockerImageName.parse(dockerImageName));
+ }
+
+ public AtlasContainer(DockerImageName dockerImageName) {
+ super(dockerImageName);
+ dockerImageName.assertCompatibleWith(DEFAULT_IMAGE_NAME);
+ setExposedPorts(List.of(27017));
+ }
+
+ public String getConnectionString() {
+ return getConnectionString(MONGODB_DATABASE_NAME_DEFAULT);
+ }
+
+ /**
+ * Gets a connection string url.
+ *
+ * @return a connection url pointing to a mongodb instance
+ */
+ public String getConnectionString(String database) {
+ return String.format("mongodb://%s:%d/%s?directConnection=true", getHost(), getMappedPort(27017), StringUtils.hasText(database) ? database : MONGODB_DATABASE_NAME_DEFAULT);
+ }
+}
diff --git a/mongodb/util/src/main/java/example/springdata/mongodb/util/MongoContainers.java b/mongodb/util/src/main/java/example/springdata/mongodb/util/MongoContainers.java
index d45922037..4019c3e0e 100644
--- a/mongodb/util/src/main/java/example/springdata/mongodb/util/MongoContainers.java
+++ b/mongodb/util/src/main/java/example/springdata/mongodb/util/MongoContainers.java
@@ -22,14 +22,22 @@
* Utility methods to create a {@link MongoDBContainer}.
*
* @author Mark Paluch
+ * @author Christoph Strobl
*/
public class MongoContainers {
- private static final String IMAGE_NAME = "mongo:5.0";
- private static final String IMAGE_NAME_PROPERTY = "mongo.default.image.name";
+ private static final String IMAGE_NAME = "mongo:8.0";
+ private static final String IMAGE_NAME_PROPERTY = "mongo.default.image.name";
- public static MongoDBContainer getDefaultContainer() {
- return new MongoDBContainer(DockerImageName.parse(System.getProperty(IMAGE_NAME_PROPERTY, IMAGE_NAME)))
- .withReuse(true);
- }
+ private static final String ATLAS_IMAGE_NAME = "mongodb/mongodb-atlas-local:latest";
+ private static final String ATLAS_IMAGE_NAME_PROPERTY = "mongo.atlas.image.name";
+
+ public static MongoDBContainer getDefaultContainer() {
+ return new MongoDBContainer(DockerImageName.parse(System.getProperty(IMAGE_NAME_PROPERTY, IMAGE_NAME)))
+ .withReuse(true);
+ }
+
+ public static AtlasContainer getAtlasContainer() {
+ return new AtlasContainer(System.getProperty(ATLAS_IMAGE_NAME_PROPERTY, ATLAS_IMAGE_NAME)).withReuse(true);
+ }
}
diff --git a/multi-store/pom.xml b/multi-store/pom.xml
index 5c53decb3..a91dd33b0 100644
--- a/multi-store/pom.xml
+++ b/multi-store/pom.xml
@@ -30,6 +30,20 @@
test
+
+
+
+ org.springframework.boot
+ spring-boot-testcontainers
+ test
+
+
+
+ org.testcontainers
+ mongodb
+ test
+
+
\ No newline at end of file
diff --git a/multi-store/src/test/java/example/springdata/multistore/ApplicationConfigurationTest.java b/multi-store/src/test/java/example/springdata/multistore/ApplicationConfigurationTest.java
index 7eac97ffe..e05a1afd3 100644
--- a/multi-store/src/test/java/example/springdata/multistore/ApplicationConfigurationTest.java
+++ b/multi-store/src/test/java/example/springdata/multistore/ApplicationConfigurationTest.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2014-2021 the original author or authors.
+ * Copyright 2014-2024 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,34 +15,46 @@
*/
package example.springdata.multistore;
+import static org.assertj.core.api.Assertions.*;
+
import example.springdata.multistore.customer.Customer;
import example.springdata.multistore.shop.Order;
-import org.junit.Test;
-import org.junit.runner.RunWith;
+import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.boot.test.context.TestConfiguration;
+import org.springframework.boot.testcontainers.service.connection.ServiceConnection;
import org.springframework.context.ApplicationContext;
+import org.springframework.context.annotation.Bean;
import org.springframework.data.jpa.repository.support.JpaEntityInformation;
import org.springframework.data.mongodb.repository.query.MongoEntityInformation;
import org.springframework.data.repository.support.Repositories;
-import org.springframework.test.context.junit4.SpringRunner;
-
-import static org.assertj.core.api.Assertions.assertThat;
+import org.testcontainers.containers.MongoDBContainer;
+import org.testcontainers.utility.DockerImageName;
/**
* Integration test to check repository interfaces are assigned to the correct store modules.
*
* @author Oliver Gierke
*/
-@RunWith(SpringRunner.class)
@SpringBootTest
-public class ApplicationConfigurationTest {
+class ApplicationConfigurationTest {
@Autowired ApplicationContext context;
+ @TestConfiguration
+ static class Infrastructure {
+
+ @Bean
+ @ServiceConnection
+ MongoDBContainer mongoDBContainer() {
+ return new MongoDBContainer(DockerImageName.parse("mongodb/mongodb-community-server"));
+ }
+ }
+
@Test
- public void repositoriesAreAssignedToAppropriateStores() {
+ void repositoriesAreAssignedToAppropriateStores() {
var repositories = new Repositories(context);
diff --git a/multi-store/src/test/resources/logback.xml b/multi-store/src/test/resources/logback.xml
index 0478c08d7..5a76a8fe3 100644
--- a/multi-store/src/test/resources/logback.xml
+++ b/multi-store/src/test/resources/logback.xml
@@ -13,4 +13,4 @@
-
\ No newline at end of file
+
diff --git a/pom.xml b/pom.xml
index c69fc17ec..9924b6c58 100644
--- a/pom.xml
+++ b/pom.xml
@@ -13,7 +13,7 @@
org.springframework.bootspring-boot-starter-parent
- 3.2.0-RC1
+ 3.5.0-RC1
@@ -37,22 +37,23 @@
1.1.3
+ 21
+ 21UTF-8
- 2023.1.0-SNAPSHOTspring-data-next-releasetrain
- 2024.0.0-SNAPSHOT
+ 2025.1.0-SNAPSHOTspring-data-next
- 2023.1.0-SNAPSHOT
+ 2025.0.0-SNAPSHOT
@@ -160,17 +161,23 @@
centralMaven Centralhttps://repo1.maven.org/maven2/
+
+ false
+ spring-milestonehttps://repo.spring.io/milestone
+
+ false
+ spring-snapshothttps://repo.spring.io/snapshot
-
- true
-
+
+ false
+
@@ -179,11 +186,55 @@
centralMaven Centralhttps://repo1.maven.org/maven2/
+
+ false
+ spring-milestonehttps://repo.spring.io/milestone/
+
+ false
+
+
+
+
+
+ com.gradle
+ develocity-maven-extension
+
+
+
+
+ maven-surefire-plugin
+
+ these tests showcase Spring Data features and should always rerun
+
+
+
+
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+
+
+ org.projectlombok
+ lombok
+ ${lombok.version}
+
+
+
+
+
+
+
diff --git a/r2dbc/example/src/main/java/example/springdata/r2dbc/basics/CustomerRepository.java b/r2dbc/example/src/main/java/example/springdata/r2dbc/basics/CustomerRepository.java
index bbf002b6b..5358fb499 100644
--- a/r2dbc/example/src/main/java/example/springdata/r2dbc/basics/CustomerRepository.java
+++ b/r2dbc/example/src/main/java/example/springdata/r2dbc/basics/CustomerRepository.java
@@ -15,6 +15,7 @@
*/
package example.springdata.r2dbc.basics;
+import org.springframework.data.domain.Limit;
import reactor.core.publisher.Flux;
import org.springframework.data.r2dbc.repository.Query;
@@ -28,4 +29,6 @@ interface CustomerRepository extends ReactiveCrudRepository {
@Query("select id, firstname, lastname from customer c where c.lastname = :lastname")
Flux findByLastname(String lastname);
+
+ Flux findByLastname(String lastname, Limit limit);
}
diff --git a/r2dbc/example/src/test/java/example/springdata/r2dbc/basics/TransactionalServiceIntegrationTests.java b/r2dbc/example/src/test/java/example/springdata/r2dbc/basics/TransactionalServiceIntegrationTests.java
index f3fe9869a..6b788a337 100644
--- a/r2dbc/example/src/test/java/example/springdata/r2dbc/basics/TransactionalServiceIntegrationTests.java
+++ b/r2dbc/example/src/test/java/example/springdata/r2dbc/basics/TransactionalServiceIntegrationTests.java
@@ -15,6 +15,7 @@
*/
package example.springdata.r2dbc.basics;
+import org.springframework.data.domain.Limit;
import reactor.core.publisher.Hooks;
import reactor.test.StepVerifier;
@@ -72,6 +73,25 @@ void exceptionTriggersRollback() {
.verifyComplete();
}
+ @Test
+ void limitResultSize() {
+
+ service.save(new Customer(null, "Carter", "Matthews")) //
+ .as(StepVerifier::create) //
+ .expectNextMatches(Customer::hasId) //
+ .verifyComplete();
+
+ service.save(new Customer(null, "Evad", "Matthews")) //
+ .as(StepVerifier::create) //
+ .expectNextMatches(Customer::hasId) //
+ .verifyComplete();
+
+ repository.findByLastname("Matthews", Limit.of(1)) //
+ .as(StepVerifier::create) //
+ .expectNextCount(1)
+ .verifyComplete();
+ }
+
@Test // #500
void insertsDataTransactionally() {
diff --git a/redis/example/pom.xml b/redis/example/pom.xml
index 2dc044e7a..6437e84e2 100644
--- a/redis/example/pom.xml
+++ b/redis/example/pom.xml
@@ -15,9 +15,13 @@
- ${project.groupId}
- spring-data-redis-example-utils
- ${project.version}
+ org.springframework.boot
+ spring-boot-testcontainers
+ test
+
+
+ org.springframework
+ spring-testtest
diff --git a/redis/example/src/test/java/example/springdata/redis/RedisTestConfiguration.java b/redis/example/src/test/java/example/springdata/redis/RedisTestConfiguration.java
index 94cd96c31..c7eff4316 100644
--- a/redis/example/src/test/java/example/springdata/redis/RedisTestConfiguration.java
+++ b/redis/example/src/test/java/example/springdata/redis/RedisTestConfiguration.java
@@ -15,11 +15,11 @@
*/
package example.springdata.redis;
-import jakarta.annotation.PreDestroy;
-
-import org.springframework.beans.factory.annotation.Autowired;
+import com.redis.testcontainers.RedisContainer;
import org.springframework.boot.autoconfigure.SpringBootApplication;
-import org.springframework.data.redis.connection.RedisConnectionFactory;
+import org.springframework.boot.testcontainers.service.connection.ServiceConnection;
+import org.springframework.context.annotation.Bean;
+import org.testcontainers.utility.DockerImageName;
/**
* @author Christoph Strobl
@@ -27,12 +27,9 @@
@SpringBootApplication
public class RedisTestConfiguration {
- @Autowired RedisConnectionFactory factory;
-
- /**
- * Clear database before shut down.
- */
- public @PreDestroy void flushTestDb() {
- factory.getConnection().flushDb();
+ @Bean
+ @ServiceConnection(name = "redis")
+ RedisContainer redisContainer() {
+ return new RedisContainer(DockerImageName.parse("redis:7"));
}
}
diff --git a/redis/example/src/test/java/example/springdata/redis/commands/GeoOperationsTests.java b/redis/example/src/test/java/example/springdata/redis/commands/GeoOperationsTests.java
index d70f46100..613ff6f79 100644
--- a/redis/example/src/test/java/example/springdata/redis/commands/GeoOperationsTests.java
+++ b/redis/example/src/test/java/example/springdata/redis/commands/GeoOperationsTests.java
@@ -15,13 +15,10 @@
*/
package example.springdata.redis.commands;
-import static org.assertj.core.api.Assertions.*;
-
-import example.springdata.redis.test.condition.EnabledOnCommand;
+import static org.assertj.core.api.Assertions.assertThat;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
-
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.geo.Circle;
@@ -37,7 +34,6 @@
* @author Mark Paluch
*/
@SpringBootTest
-@EnabledOnCommand("GEOADD")
class GeoOperationsTests {
@Autowired RedisOperations operations;
diff --git a/redis/example/src/test/java/example/springdata/redis/commands/KeyOperationsTests.java b/redis/example/src/test/java/example/springdata/redis/commands/KeyOperationsTests.java
index 16ee33b3d..6fc637c17 100644
--- a/redis/example/src/test/java/example/springdata/redis/commands/KeyOperationsTests.java
+++ b/redis/example/src/test/java/example/springdata/redis/commands/KeyOperationsTests.java
@@ -15,13 +15,10 @@
*/
package example.springdata.redis.commands;
-import example.springdata.redis.test.condition.EnabledOnRedisAvailable;
-
import java.util.UUID;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
-
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.data.redis.DataRedisTest;
import org.springframework.data.redis.connection.RedisConnection;
@@ -36,7 +33,6 @@
* @author Christoph Strobl
*/
@DataRedisTest
-@EnabledOnRedisAvailable
class KeyOperationsTests {
private static final String PREFIX = KeyOperationsTests.class.getSimpleName();
diff --git a/redis/pom.xml b/redis/pom.xml
index 8d99e58e7..4a8501731 100644
--- a/redis/pom.xml
+++ b/redis/pom.xml
@@ -33,19 +33,19 @@
spring-boot-starter-data-redis
-
-
-
-
-
+
+ org.testcontainers
+ junit-jupiter
+ test
+
-
- com.github.kstyrc
- embedded-redis
- 0.6
-
-
+
+ com.redis
+ testcontainers-redis
+ 2.2.2
+ test
+
-
+
diff --git a/redis/pubsub/pom.xml b/redis/pubsub/pom.xml
index 9ba42bdf3..761b002ed 100644
--- a/redis/pubsub/pom.xml
+++ b/redis/pubsub/pom.xml
@@ -16,9 +16,13 @@
- ${project.groupId}
- spring-data-redis-example-utils
- ${project.version}
+ org.springframework.boot
+ spring-boot-testcontainers
+ test
+
+
+ org.springframework
+ spring-testtest
diff --git a/redis/pubsub/src/test/java/example/springdata/redis/PubSubVirtualThreadsTests.java b/redis/pubsub/src/test/java/example/springdata/redis/PubSubVirtualThreadsTests.java
index 8c4b7d603..5782868c4 100644
--- a/redis/pubsub/src/test/java/example/springdata/redis/PubSubVirtualThreadsTests.java
+++ b/redis/pubsub/src/test/java/example/springdata/redis/PubSubVirtualThreadsTests.java
@@ -22,9 +22,8 @@
import java.util.concurrent.TimeUnit;
import org.junit.jupiter.api.Test;
-import org.junit.jupiter.api.condition.EnabledOnJre;
+import org.junit.jupiter.api.condition.EnabledForJreRange;
import org.junit.jupiter.api.condition.JRE;
-
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.core.task.AsyncTaskExecutor;
@@ -39,7 +38,7 @@
* @author Mark Paluch
*/
@SpringBootTest(properties = "spring.threads.virtual.enabled=true")
-@EnabledOnJre(JRE.JAVA_21)
+@EnabledForJreRange(min = JRE.JAVA_21)
public class PubSubVirtualThreadsTests {
@Autowired RedisConnectionFactory connectionFactory;
diff --git a/redis/pubsub/src/test/java/example/springdata/redis/RedisTestConfiguration.java b/redis/pubsub/src/test/java/example/springdata/redis/RedisTestConfiguration.java
index cec2d756c..1c38139ce 100644
--- a/redis/pubsub/src/test/java/example/springdata/redis/RedisTestConfiguration.java
+++ b/redis/pubsub/src/test/java/example/springdata/redis/RedisTestConfiguration.java
@@ -15,10 +15,22 @@
*/
package example.springdata.redis;
+import com.redis.testcontainers.RedisContainer;
import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.boot.testcontainers.service.connection.ServiceConnection;
+import org.springframework.context.annotation.Bean;
+import org.testcontainers.utility.DockerImageName;
/**
* @author Mark Paluch
+ * @author Christoph Strobl
*/
@SpringBootApplication
-public class RedisTestConfiguration {}
+public class RedisTestConfiguration {
+
+ @Bean
+ @ServiceConnection(name = "redis")
+ RedisContainer redisContainer() {
+ return new RedisContainer(DockerImageName.parse("redis:7"));
+ }
+}
diff --git a/redis/reactive/pom.xml b/redis/reactive/pom.xml
index b730243d4..241bec0f3 100644
--- a/redis/reactive/pom.xml
+++ b/redis/reactive/pom.xml
@@ -41,9 +41,13 @@
- ${project.groupId}
- spring-data-redis-example-utils
- ${project.version}
+ org.springframework.boot
+ spring-boot-testcontainers
+ test
+
+
+ org.springframework
+ spring-testtest
diff --git a/redis/reactive/src/test/java/example/springdata/redis/RedisTestConfiguration.java b/redis/reactive/src/test/java/example/springdata/redis/RedisTestConfiguration.java
index 2191dcb8d..b790f72c4 100644
--- a/redis/reactive/src/test/java/example/springdata/redis/RedisTestConfiguration.java
+++ b/redis/reactive/src/test/java/example/springdata/redis/RedisTestConfiguration.java
@@ -15,28 +15,30 @@
*/
package example.springdata.redis;
-import jakarta.annotation.PreDestroy;
-
+import com.redis.testcontainers.RedisContainer;
import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.boot.testcontainers.service.connection.ServiceConnection;
import org.springframework.context.annotation.Bean;
import org.springframework.data.redis.connection.ReactiveRedisConnectionFactory;
-import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.ReactiveRedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.RedisSerializationContext.RedisSerializationContextBuilder;
import org.springframework.data.redis.serializer.StringRedisSerializer;
+import org.testcontainers.utility.DockerImageName;
/**
* @author Mark Paluch
+ * @author Christoph Strobl
*/
@SpringBootApplication
public class RedisTestConfiguration {
@Bean
- public LettuceConnectionFactory redisConnectionFactory() {
- return new LettuceConnectionFactory();
+ @ServiceConnection(name = "redis")
+ RedisContainer redisContainer() {
+ return new RedisContainer(DockerImageName.parse("redis:7"));
}
/**
@@ -72,10 +74,4 @@ public ReactiveRedisTemplate reactiveJsonObjectRedisTemplate(
return new ReactiveRedisTemplate<>(connectionFactory, serializationContext);
}
- /**
- * Clear database before shut down.
- */
- public @PreDestroy void flushTestDb() {
- redisConnectionFactory().getConnection().flushDb();
- }
}
diff --git a/redis/reactive/src/test/java/example/springdata/redis/commands/KeyCommandsTests.java b/redis/reactive/src/test/java/example/springdata/redis/commands/KeyCommandsTests.java
index 1ff35d61f..1dc5598e5 100644
--- a/redis/reactive/src/test/java/example/springdata/redis/commands/KeyCommandsTests.java
+++ b/redis/reactive/src/test/java/example/springdata/redis/commands/KeyCommandsTests.java
@@ -15,19 +15,14 @@
*/
package example.springdata.redis.commands;
-import example.springdata.redis.RedisTestConfiguration;
-import example.springdata.redis.test.condition.EnabledOnRedisAvailable;
-import reactor.core.publisher.Flux;
-import reactor.test.StepVerifier;
-
import java.nio.ByteBuffer;
import java.time.Duration;
import java.util.Collections;
import java.util.UUID;
+import example.springdata.redis.RedisTestConfiguration;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
-
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.connection.ReactiveRedisConnection;
@@ -36,6 +31,8 @@
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.data.redis.util.ByteUtils;
+import reactor.core.publisher.Flux;
+import reactor.test.StepVerifier;
/**
* Show usage of reactive operations on Redis keys using low level API provided by
@@ -44,7 +41,6 @@
* @author Mark Paluch
*/
@SpringBootTest(classes = RedisTestConfiguration.class)
-@EnabledOnRedisAvailable
class KeyCommandsTests {
private static final String PREFIX = KeyCommandsTests.class.getSimpleName();
@@ -77,7 +73,7 @@ void iterateOverKeysMatchingPrefixUsingKeysCommand() {
.count() //
.doOnSuccess(count -> System.out.println(String.format("Total No. found: %s", count)));
- StepVerifier.create(keyCount).expectNext(50L).verifyComplete();
+ keyCount.as(StepVerifier::create).expectNext(50L).verifyComplete();
}
/**
@@ -98,7 +94,7 @@ void storeToListAndPop() {
.flatMap(result -> llen) //
.doOnNext(count -> System.out.println(String.format("Total items in list left: %s", count)));//
- StepVerifier.create(popAndLlen).expectNext(0L).verifyComplete();
+ popAndLlen.as(StepVerifier::create).expectNext(0L).verifyComplete();
}
private void generateRandomKeys(int nrKeys) {
@@ -109,7 +105,9 @@ private void generateRandomKeys(int nrKeys) {
.map(key -> SetCommand.set(key) //
.value(ByteBuffer.wrap(UUID.randomUUID().toString().getBytes())));
- StepVerifier.create(connection.stringCommands().set(generator)).expectNextCount(nrKeys).verifyComplete();
+ connection.stringCommands().set(generator).as(StepVerifier::create) //
+ .expectNextCount(nrKeys) //
+ .verifyComplete();
}
diff --git a/redis/reactive/src/test/java/example/springdata/redis/operations/JacksonJsonTests.java b/redis/reactive/src/test/java/example/springdata/redis/operations/JacksonJsonTests.java
index c361dc955..716d775d3 100644
--- a/redis/reactive/src/test/java/example/springdata/redis/operations/JacksonJsonTests.java
+++ b/redis/reactive/src/test/java/example/springdata/redis/operations/JacksonJsonTests.java
@@ -18,7 +18,6 @@
import example.springdata.redis.EmailAddress;
import example.springdata.redis.Person;
import example.springdata.redis.RedisTestConfiguration;
-import example.springdata.redis.test.condition.EnabledOnRedisAvailable;
import lombok.extern.slf4j.Slf4j;
import reactor.test.StepVerifier;
@@ -39,7 +38,6 @@
*/
@Slf4j
@SpringBootTest(classes = RedisTestConfiguration.class)
-@EnabledOnRedisAvailable
class JacksonJsonTests {
@Autowired ReactiveRedisOperations typedOperations;
@@ -56,7 +54,7 @@ class JacksonJsonTests {
@Test
void shouldWriteAndReadPerson() {
- StepVerifier.create(typedOperations.opsForValue().set("homer", new Person("Homer", "Simpson"))) //
+ typedOperations.opsForValue().set("homer", new Person("Homer", "Simpson")).as(StepVerifier::create) //
.expectNext(true) //
.verifyComplete();
diff --git a/redis/reactive/src/test/java/example/springdata/redis/operations/ListOperationsTests.java b/redis/reactive/src/test/java/example/springdata/redis/operations/ListOperationsTests.java
index 553d7534a..46457dc11 100644
--- a/redis/reactive/src/test/java/example/springdata/redis/operations/ListOperationsTests.java
+++ b/redis/reactive/src/test/java/example/springdata/redis/operations/ListOperationsTests.java
@@ -16,7 +16,6 @@
package example.springdata.redis.operations;
import example.springdata.redis.RedisTestConfiguration;
-import example.springdata.redis.test.condition.EnabledOnRedisAvailable;
import lombok.extern.slf4j.Slf4j;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
@@ -38,14 +37,16 @@
*/
@Slf4j
@SpringBootTest(classes = RedisTestConfiguration.class)
-@EnabledOnRedisAvailable
class ListOperationsTests {
@Autowired ReactiveRedisOperations operations;
@BeforeEach
void before() {
- StepVerifier.create(operations.execute(it -> it.serverCommands().flushDb())).expectNext("OK").verifyComplete();
+
+ operations.execute(it -> it.serverCommands().flushDb()).as(StepVerifier::create) //
+ .expectNext("OK") //
+ .verifyComplete();
}
/**
@@ -63,7 +64,7 @@ void shouldPollAndPopulateQueue() {
.log("example.springdata.redis", Level.INFO);
log.info("Blocking pop...waiting for message");
- StepVerifier.create(blpop) //
+ blpop.as(StepVerifier::create) //
.then(() -> {
Mono.delay(Duration.ofSeconds(10)).doOnSuccess(it -> {
diff --git a/redis/reactive/src/test/java/example/springdata/redis/operations/ValueOperationsTests.java b/redis/reactive/src/test/java/example/springdata/redis/operations/ValueOperationsTests.java
index d036285e2..f27cdb823 100644
--- a/redis/reactive/src/test/java/example/springdata/redis/operations/ValueOperationsTests.java
+++ b/redis/reactive/src/test/java/example/springdata/redis/operations/ValueOperationsTests.java
@@ -18,7 +18,6 @@
import static org.assertj.core.api.Assertions.*;
import example.springdata.redis.RedisTestConfiguration;
-import example.springdata.redis.test.condition.EnabledOnRedisAvailable;
import lombok.extern.slf4j.Slf4j;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
@@ -39,14 +38,16 @@
*/
@Slf4j
@SpringBootTest(classes = RedisTestConfiguration.class)
-@EnabledOnRedisAvailable
class ValueOperationsTests {
@Autowired ReactiveRedisOperations operations;
@BeforeEach
void before() {
- StepVerifier.create(operations.execute(it -> it.serverCommands().flushDb())).expectNext("OK").verifyComplete();
+
+ operations.execute(it -> it.serverCommands().flushDb()).as(StepVerifier::create) //
+ .expectNext("OK") //
+ .verifyComplete();
}
/**
@@ -67,14 +68,14 @@ void shouldCacheValue() {
log.info("Initial access (takes a while...)");
- StepVerifier.create(cachedMono).expectSubscription() //
+ cachedMono.as(StepVerifier::create).expectSubscription() //
.expectNoEvent(Duration.ofSeconds(9)) //
.expectNext("Hello, World!") //
.verifyComplete();
log.info("Subsequent access (use cached value)");
- var duration = StepVerifier.create(cachedMono) //
+ var duration = cachedMono.as(StepVerifier::create) //
.expectNext("Hello, World!") //
.verifyComplete();
diff --git a/redis/repositories/pom.xml b/redis/repositories/pom.xml
index fd8a5e133..e8fdc3e12 100644
--- a/redis/repositories/pom.xml
+++ b/redis/repositories/pom.xml
@@ -15,15 +15,13 @@
- ${project.groupId}
- spring-data-redis-example-utils
- ${project.version}
+ org.springframework.boot
+ spring-boot-testcontainerstest
-
- com.github.kstyrc
- embedded-redis
+ org.springframework
+ spring-testtest
diff --git a/redis/repositories/src/test/java/example/springdata/redis/repositories/PersonRepositoryTests.java b/redis/repositories/src/test/java/example/springdata/redis/repositories/PersonRepositoryTests.java
index d169460e7..e325046fa 100644
--- a/redis/repositories/src/test/java/example/springdata/redis/repositories/PersonRepositoryTests.java
+++ b/redis/repositories/src/test/java/example/springdata/redis/repositories/PersonRepositoryTests.java
@@ -17,7 +17,7 @@
import static org.assertj.core.api.Assertions.*;
-import example.springdata.redis.test.condition.EnabledOnRedisAvailable;
+import com.redis.testcontainers.RedisContainer;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
@@ -29,6 +29,7 @@
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.data.redis.DataRedisTest;
+import org.springframework.boot.testcontainers.service.connection.ServiceConnection;
import org.springframework.data.domain.Example;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
@@ -40,16 +41,23 @@
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.data.redis.core.index.GeoIndexed;
import org.springframework.data.redis.core.index.Indexed;
+import org.testcontainers.junit.jupiter.Container;
+import org.testcontainers.junit.jupiter.Testcontainers;
+import org.testcontainers.utility.DockerImageName;
/**
* @author Christoph Strobl
* @author Oliver Gierke
* @author Mark Paluch
*/
+@Testcontainers
@DataRedisTest
-@EnabledOnRedisAvailable
class PersonRepositoryTests {
+ @Container
+ @ServiceConnection
+ static RedisContainer redis = new RedisContainer(DockerImageName.parse("redis:7"));
+
/** {@link Charset} for String conversion **/
private static final Charset CHARSET = StandardCharsets.UTF_8;
diff --git a/redis/streams/pom.xml b/redis/streams/pom.xml
index 73d473363..8cdcce5f2 100644
--- a/redis/streams/pom.xml
+++ b/redis/streams/pom.xml
@@ -22,9 +22,13 @@
- ${project.groupId}
- spring-data-redis-example-utils
- ${project.version}
+ org.springframework.boot
+ spring-boot-testcontainers
+ test
+
+
+ org.springframework
+ spring-testtest
diff --git a/redis/streams/src/test/java/example/springdata/redis/reactive/ReactiveStreamApiTests.java b/redis/streams/src/test/java/example/springdata/redis/reactive/ReactiveStreamApiTests.java
index 2ed3a06c3..ed061f717 100644
--- a/redis/streams/src/test/java/example/springdata/redis/reactive/ReactiveStreamApiTests.java
+++ b/redis/streams/src/test/java/example/springdata/redis/reactive/ReactiveStreamApiTests.java
@@ -18,8 +18,12 @@
import static org.assertj.core.api.Assertions.*;
import static org.springframework.data.redis.connection.stream.StreamOffset.*;
+import com.redis.testcontainers.RedisContainer;
import example.springdata.redis.SensorData;
-import example.springdata.redis.test.condition.EnabledOnCommand;
+import org.springframework.boot.testcontainers.service.connection.ServiceConnection;
+import org.testcontainers.junit.jupiter.Container;
+import org.testcontainers.junit.jupiter.Testcontainers;
+import org.testcontainers.utility.DockerImageName;
import reactor.test.StepVerifier;
import java.time.Duration;
@@ -43,11 +47,15 @@
/**
* @author Christoph Strobl
*/
+@Testcontainers
@DataRedisTest
-@EnabledOnCommand("XADD")
@ImportAutoConfiguration(RedisReactiveAutoConfiguration.class)
class ReactiveStreamApiTests {
+ @Container
+ @ServiceConnection
+ static RedisContainer redis = new RedisContainer(DockerImageName.parse("redis:7"));
+
@Autowired ReactiveStringRedisTemplate template;
@Autowired StreamReceiver> streamReceiver;
diff --git a/redis/streams/src/test/java/example/springdata/redis/sync/SyncStreamApiTests.java b/redis/streams/src/test/java/example/springdata/redis/sync/SyncStreamApiTests.java
index 0cf27940f..b4d7d08a8 100644
--- a/redis/streams/src/test/java/example/springdata/redis/sync/SyncStreamApiTests.java
+++ b/redis/streams/src/test/java/example/springdata/redis/sync/SyncStreamApiTests.java
@@ -18,8 +18,8 @@
import static org.assertj.core.api.Assertions.*;
import static org.springframework.data.redis.connection.stream.StreamOffset.*;
+import com.redis.testcontainers.RedisContainer;
import example.springdata.redis.SensorData;
-import example.springdata.redis.test.condition.EnabledOnCommand;
import java.util.concurrent.TimeUnit;
@@ -28,6 +28,7 @@
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.data.redis.DataRedisTest;
+import org.springframework.boot.testcontainers.service.connection.ServiceConnection;
import org.springframework.data.redis.RedisSystemException;
import org.springframework.data.redis.connection.stream.MapRecord;
import org.springframework.data.redis.connection.stream.ReadOffset;
@@ -36,15 +37,22 @@
import org.springframework.data.redis.core.StreamOperations;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.stream.StreamMessageListenerContainer;
+import org.testcontainers.junit.jupiter.Container;
+import org.testcontainers.junit.jupiter.Testcontainers;
+import org.testcontainers.utility.DockerImageName;
/**
* @author Christoph Strobl
* @author Mark Paluch
*/
+@Testcontainers
@DataRedisTest
-@EnabledOnCommand("XADD")
class SyncStreamApiTests {
+ @Container
+ @ServiceConnection
+ static RedisContainer redis = new RedisContainer(DockerImageName.parse("redis:7"));
+
@Autowired StringRedisTemplate template;
@Autowired StreamMessageListenerContainer> messageListenerContainer;
diff --git a/redis/util/pom.xml b/redis/util/pom.xml
index 09db062aa..8d345c0da 100644
--- a/redis/util/pom.xml
+++ b/redis/util/pom.xml
@@ -25,12 +25,6 @@
lettuce-core
-
- com.github.kstyrc
- embedded-redis
- true
-
-
diff --git a/rest/multi-store/pom.xml b/rest/multi-store/pom.xml
index 85f1dbc69..8163f5671 100644
--- a/rest/multi-store/pom.xml
+++ b/rest/multi-store/pom.xml
@@ -33,10 +33,18 @@
spring-boot-starter-data-mongodb
+
+
+
+ org.springframework.boot
+ spring-boot-testcontainers
+ test
+
+
- de.flapdoodle.embed
- de.flapdoodle.embed.mongo
- runtime
+ org.testcontainers
+ mongodb
+ test
diff --git a/rest/multi-store/src/test/java/example/springdata/multistore/ApplicationIntegrationTests.java b/rest/multi-store/src/test/java/example/springdata/multistore/ApplicationIntegrationTests.java
index 2c3e442e6..3c7363a67 100644
--- a/rest/multi-store/src/test/java/example/springdata/multistore/ApplicationIntegrationTests.java
+++ b/rest/multi-store/src/test/java/example/springdata/multistore/ApplicationIntegrationTests.java
@@ -24,6 +24,11 @@
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.boot.test.context.TestConfiguration;
+import org.springframework.boot.testcontainers.service.connection.ServiceConnection;
+import org.springframework.context.annotation.Bean;
+import org.testcontainers.containers.MongoDBContainer;
+import org.testcontainers.utility.DockerImageName;
/**
* Integration test to show the usage of repositories backed by different stores.
@@ -37,6 +42,16 @@ public class ApplicationIntegrationTests {
@Autowired PersonRepository personRepository;
@Autowired TreasureRepository treasureRepository;
+ @TestConfiguration
+ static class Infrastructure {
+
+ @Bean
+ @ServiceConnection
+ MongoDBContainer mongoDBContainer() {
+ return new MongoDBContainer(DockerImageName.parse("mongodb/mongodb-community-server"));
+ }
+ }
+
@Test
public void useMultipleRepositories() {
diff --git a/rest/multi-store/src/test/resources/application.properties b/rest/multi-store/src/test/resources/application.properties
deleted file mode 100644
index 5239185aa..000000000
--- a/rest/multi-store/src/test/resources/application.properties
+++ /dev/null
@@ -1,3 +0,0 @@
-# Random port for embedded MongoDB
-spring.data.mongodb.port=0
-spring.mongodb.embedded.version=3.6.0
\ No newline at end of file
diff --git a/rest/pom.xml b/rest/pom.xml
index d361a147d..d8aeaedf2 100644
--- a/rest/pom.xml
+++ b/rest/pom.xml
@@ -15,8 +15,8 @@
Sample projects for Spring Data REST
-
-
+ starbucks
+ multi-storeprojectionssecurityheaders
diff --git a/rest/starbucks/pom.xml b/rest/starbucks/pom.xml
index 122cbb8c6..aa0793a15 100644
--- a/rest/starbucks/pom.xml
+++ b/rest/starbucks/pom.xml
@@ -22,6 +22,7 @@
de.flapdoodle.embedde.flapdoodle.embed.mongo
+ 4.16.2runtime
@@ -69,14 +70,14 @@
org.webjarsjquery
- 2.1.3
+ 3.7.1runtimeorg.webjarsbootstrap
- 3.3.4
+ 5.3.3runtime
@@ -93,6 +94,20 @@
runtime
+
+
+
+ org.springframework.boot
+ spring-boot-testcontainers
+ test
+
+
+
+ org.testcontainers
+ mongodb
+ test
+
+
diff --git a/rest/starbucks/src/main/java/example/springdata/rest/stores/StoreInitializer.java b/rest/starbucks/src/main/java/example/springdata/rest/stores/StoreInitializer.java
index 2f27b74ee..85f34688a 100644
--- a/rest/starbucks/src/main/java/example/springdata/rest/stores/StoreInitializer.java
+++ b/rest/starbucks/src/main/java/example/springdata/rest/stores/StoreInitializer.java
@@ -28,7 +28,6 @@
import org.springframework.batch.item.file.mapping.DefaultLineMapper;
import org.springframework.batch.item.file.separator.DefaultRecordSeparatorPolicy;
import org.springframework.batch.item.file.transform.DelimitedLineTokenizer;
-import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.geo.Point;
import org.springframework.data.mongodb.core.MongoOperations;
@@ -45,7 +44,6 @@
@Component
public class StoreInitializer {
- @Autowired
public StoreInitializer(StoreRepository repository, MongoOperations operations) throws Exception {
if (repository.count() != 0) {
@@ -64,8 +62,8 @@ public StoreInitializer(StoreRepository repository, MongoOperations operations)
}
/**
- * Reads a file {@code starbucks.csv} from the class path and parses it into {@link Store} instances about to
- * be persisted.
+ * Reads a file {@code starbucks.csv} from the class path and parses it into {@link Store} instances about to be
+ * persisted.
*
* @return
* @throws Exception
diff --git a/rest/starbucks/src/main/java/example/springdata/rest/stores/StoreRepository.java b/rest/starbucks/src/main/java/example/springdata/rest/stores/StoreRepository.java
index 366bc363d..1a67b3f92 100644
--- a/rest/starbucks/src/main/java/example/springdata/rest/stores/StoreRepository.java
+++ b/rest/starbucks/src/main/java/example/springdata/rest/stores/StoreRepository.java
@@ -24,6 +24,7 @@
import org.springframework.data.querydsl.QuerydslPredicateExecutor;
import org.springframework.data.querydsl.binding.QuerydslBindings;
import org.springframework.data.querydsl.binding.SingleValueBinding;
+import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.PagingAndSortingRepository;
import org.springframework.data.rest.core.annotation.RestResource;
@@ -36,7 +37,8 @@
*
* @author Oliver Gierke
*/
-public interface StoreRepository extends PagingAndSortingRepository, QuerydslPredicateExecutor {
+public interface StoreRepository
+ extends CrudRepository, PagingAndSortingRepository, QuerydslPredicateExecutor {
@RestResource(rel = "by-location")
Page findByAddressLocationNear(Point location, Distance distance, Pageable pageable);
diff --git a/rest/starbucks/src/main/java/example/springdata/rest/stores/web/StoresController.java b/rest/starbucks/src/main/java/example/springdata/rest/stores/web/StoresController.java
index febca76a5..a1dee0fbb 100644
--- a/rest/starbucks/src/main/java/example/springdata/rest/stores/web/StoresController.java
+++ b/rest/starbucks/src/main/java/example/springdata/rest/stores/web/StoresController.java
@@ -34,8 +34,7 @@
import org.springframework.hateoas.LinkRelation;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
/**
@@ -70,7 +69,7 @@ class StoresController {
* @param pageable the pagination information
* @return
*/
- @RequestMapping(value = "/", method = RequestMethod.GET)
+ @GetMapping("/")
String index(Model model, @RequestParam Optional location, @RequestParam Optional distance,
Pageable pageable) {
diff --git a/rest/starbucks/src/main/resources/application.properties b/rest/starbucks/src/main/resources/application.properties
index f644711ab..1e5a96cc0 100644
--- a/rest/starbucks/src/main/resources/application.properties
+++ b/rest/starbucks/src/main/resources/application.properties
@@ -1,5 +1,2 @@
-# Random port for embedded MongoDB
-spring.data.mongodb.port=0
-spring.mongodb.embedded.version=3.6.0
# Spring Data REST
spring.data.rest.base-path=/api
diff --git a/rest/starbucks/src/test/java/example/springdata/rest/stores/StarbucksClient.java b/rest/starbucks/src/test/java/example/springdata/rest/stores/StarbucksClient.java
index 3e6d5ff10..a314c91a4 100644
--- a/rest/starbucks/src/test/java/example/springdata/rest/stores/StarbucksClient.java
+++ b/rest/starbucks/src/test/java/example/springdata/rest/stores/StarbucksClient.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2014-2021 the original author or authors.
+ * Copyright 2014-2024 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.
@@ -26,20 +26,17 @@
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
-import org.springframework.boot.web.server.LocalServerPort;
-import org.springframework.context.annotation.Bean;
+import org.springframework.boot.test.web.client.TestRestTemplate;
+import org.springframework.boot.test.web.server.LocalServerPort;
import org.springframework.hateoas.EntityModel;
import org.springframework.hateoas.MediaTypes;
import org.springframework.hateoas.client.Traverson;
import org.springframework.hateoas.server.core.TypeReferences.CollectionModelType;
import org.springframework.hateoas.server.core.TypeReferences.EntityModelType;
import org.springframework.hateoas.server.core.TypeReferences.PagedModelType;
-import org.springframework.http.RequestEntity;
-import org.springframework.web.client.RestOperations;
-import org.springframework.web.client.RestTemplate;
+import org.springframework.web.client.RestClient;
/**
* A test case to discover the search resource and execute a predefined search with it.
@@ -47,22 +44,12 @@
* @author Oliver Gierke
* @author Divya Srivastava
*/
-@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
@Slf4j
+@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
class StarbucksClient {
- @SpringBootApplication
- static class Config {
-
- @Bean
- public RestTemplate restTemplate() {
- return new RestTemplate();
- }
- }
-
@LocalServerPort int port;
-
- @Autowired RestOperations restOperations;
+ @Autowired TestRestTemplate template;
private static final String SERVICE_URI = "http://localhost:%s/api";
@@ -102,17 +89,23 @@ void accessServiceUsingRestTemplate() {
// Access root resource
- var uri = URI.create(String.format(SERVICE_URI, port));
- var request = RequestEntity.get(uri).accept(HAL_JSON).build();
- var rootLinks = restOperations.exchange(request, new EntityModelType<>() {}).getBody();
- var links = rootLinks.getLinks();
+ var client = RestClient.create(template.getRestTemplate());
+
+ var links = client.get()
+ .uri(URI.create(String.format(SERVICE_URI, port)))
+ .accept(HAL_JSON)
+ .retrieve()
+ .body(new EntityModelType<>() {})
+ .getLinks();
// Follow stores link
var storesLink = links.getRequiredLink("stores").expand();
- request = RequestEntity.get(URI.create(storesLink.getHref())).accept(HAL_JSON).build();
- var stores = restOperations.exchange(request, new CollectionModelType() {}).getBody();
+ var stores = client.get().uri(storesLink.toUri())
+ .accept(HAL_JSON)
+ .retrieve()
+ .body(new CollectionModelType() {});
stores.getContent().forEach(store -> log.info("{} - {}", store.name, store.address));
}
@@ -126,8 +119,7 @@ public String toString() {
return String.format("%s, %s %s - lat: %s, long: %s", street, zip, city, location.y, location.x);
}
- record Location(double x, double y) {
- }
+ record Location(double x, double y) {}
}
}
}
diff --git a/rest/starbucks/src/test/java/example/springdata/rest/stores/StoreRepositoryIntegrationTests.java b/rest/starbucks/src/test/java/example/springdata/rest/stores/StoreRepositoryIntegrationTests.java
index f5de082ee..e3ab2fc4d 100644
--- a/rest/starbucks/src/test/java/example/springdata/rest/stores/StoreRepositoryIntegrationTests.java
+++ b/rest/starbucks/src/test/java/example/springdata/rest/stores/StoreRepositoryIntegrationTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2014-2021 the original author or authors.
+ * Copyright 2014-2024 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.
@@ -24,7 +24,6 @@
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
-
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.domain.PageRequest;
@@ -38,8 +37,8 @@
* @author Oliver Gierke
* @author Mark Paluch
*/
-@SpringBootTest(classes = Application.class)
-public class StoreRepositoryIntegrationTests {
+@SpringBootTest(classes = { Application.class, TestApplication.class })
+class StoreRepositoryIntegrationTests {
@Autowired StoreRepository repository;
@@ -50,7 +49,7 @@ public void clearDb() {
}
@Test
- public void findsStoresByLocation() {
+ void findsStoresByLocation() {
var location = new Point(-73.995146, 40.740337);
var store = new Store(UUID.randomUUID(), "Foo", new Address("street", "city", "zip", location));
diff --git a/rest/starbucks/src/test/java/example/springdata/rest/stores/TestApplication.java b/rest/starbucks/src/test/java/example/springdata/rest/stores/TestApplication.java
new file mode 100644
index 000000000..cb1bfcf18
--- /dev/null
+++ b/rest/starbucks/src/test/java/example/springdata/rest/stores/TestApplication.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2024 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 example.springdata.rest.stores;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.testcontainers.service.connection.ServiceConnection;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.testcontainers.containers.MongoDBContainer;
+import org.testcontainers.utility.DockerImageName;
+
+/**
+ * @author Oliver Drotbohm
+ */
+@Configuration
+public class TestApplication {
+
+ @Bean
+ @ServiceConnection
+ MongoDBContainer mongoDBContainer() {
+ return new MongoDBContainer(DockerImageName.parse("mongodb/mongodb-community-server"));
+ }
+
+ public static void main(String[] args) {
+
+ SpringApplication.from(Application::main)
+ .with(TestApplication.class)
+ .run(args);
+ }
+}
diff --git a/web/pom.xml b/web/pom.xml
index f9899badc..30c94ab95 100644
--- a/web/pom.xml
+++ b/web/pom.xml
@@ -16,7 +16,7 @@
2015
- example
+ examplequerydslprojection
diff --git a/web/querydsl/README.md b/web/querydsl/README.md
index b8aab84c5..91ae105c5 100644
--- a/web/querydsl/README.md
+++ b/web/querydsl/README.md
@@ -4,9 +4,8 @@ This example shows some of the Spring Data Querydsl integration features with Sp
## Quickstart
-1. Install MongoDB (http://www.mongodb.org/downloads, unzip, run `mkdir data`, run `bin/mongod --dbpath=data`)
-2. Build and run the app (`mvn spring-boot:run`)
-4. Access app directly via its UI (`http://localhost:8080/`).
+1. Build and run the app (`mvn spring-boot:test-run`)
+2. Access app directly via its UI (`http://localhost:8080/`).
## Interesting bits
diff --git a/web/querydsl/pom.xml b/web/querydsl/pom.xml
index 36ab136b2..2f66cf7e2 100644
--- a/web/querydsl/pom.xml
+++ b/web/querydsl/pom.xml
@@ -58,20 +58,20 @@
io.github.jpenrenthymeleaf-spring-data-dialect
- 3.3.0
+ 3.6.0org.webjarsjquery
- 2.1.3
+ 3.7.1runtimeorg.webjarsbootstrap
- 3.3.4
+ 5.3.3runtime
@@ -94,6 +94,12 @@
test
+
+ org.springframework.boot
+ spring-boot-testcontainers
+ test
+
+
diff --git a/web/querydsl/src/main/resources/templates/index.html b/web/querydsl/src/main/resources/templates/index.html
index 6e59b3e99..92abc3d0c 100644
--- a/web/querydsl/src/main/resources/templates/index.html
+++ b/web/querydsl/src/main/resources/templates/index.html
@@ -23,23 +23,27 @@
Search
-
+
-
+
-
+
-
+
-
+
@@ -69,13 +73,13 @@
Search
City
Street
Email
-
+
-
1.
+
1.
-
+
Firstname
Lastname
diff --git a/web/querydsl/src/test/java/example/users/ApplicationTests.java b/web/querydsl/src/test/java/example/users/ApplicationTests.java
index 594ea2331..0f83c0706 100644
--- a/web/querydsl/src/test/java/example/users/ApplicationTests.java
+++ b/web/querydsl/src/test/java/example/users/ApplicationTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2017-2021 the original author or authors.
+ * Copyright 2017-2024 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,44 +15,26 @@
*/
package example.users;
-
import org.junit.jupiter.api.Test;
-
import org.springframework.boot.test.context.SpringBootTest;
-import org.springframework.test.context.DynamicPropertyRegistry;
-import org.springframework.test.context.DynamicPropertySource;
+import org.springframework.boot.testcontainers.service.connection.ServiceConnection;
import org.testcontainers.containers.MongoDBContainer;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;
-import org.testcontainers.utility.DockerImageName;
/**
* @author Oliver Gierke
* @author Divya Srivastava
+ * @author Tim Sparg
*/
@Testcontainers
@SpringBootTest
class ApplicationTests {
- @Container //
- private static MongoDBContainer mongoDBContainer = MongoContainers.getDefaultContainer();
-
- @DynamicPropertySource
- static void setProperties(DynamicPropertyRegistry registry) {
- registry.add("spring.data.mongodb.uri", mongoDBContainer::getReplicaSetUrl);
- }
+ @Container
+ @ServiceConnection static MongoDBContainer mongoDBContainer = new MongoDBContainer("mongo:7");
@Test
void contextBootstraps() {}
- static class MongoContainers {
-
- private static final String IMAGE_NAME = "mongo:5.0";
- private static final String IMAGE_NAME_PROPERTY = "mongo.default.image.name";
-
- public static MongoDBContainer getDefaultContainer() {
- return new MongoDBContainer(DockerImageName.parse(System.getProperty(IMAGE_NAME_PROPERTY, IMAGE_NAME)))
- .withReuse(true);
- }
- }
}
diff --git a/web/querydsl/src/test/java/example/users/TestApplication.java b/web/querydsl/src/test/java/example/users/TestApplication.java
new file mode 100644
index 000000000..054772e49
--- /dev/null
+++ b/web/querydsl/src/test/java/example/users/TestApplication.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2024 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 example.users;
+
+import org.springframework.boot.SpringApplication;
+
+/**
+ * @author Tim Sparg
+ * @author Oliver Drotbohm
+ */
+public class TestApplication {
+
+ public static void main(String[] args) {
+ SpringApplication.from(Application::main)
+ .with(TestcontainersConfiguration.class)
+ .run(args);
+ }
+}
diff --git a/web/querydsl/src/test/java/example/users/TestcontainersConfiguration.java b/web/querydsl/src/test/java/example/users/TestcontainersConfiguration.java
new file mode 100644
index 000000000..077a7d4f3
--- /dev/null
+++ b/web/querydsl/src/test/java/example/users/TestcontainersConfiguration.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2024 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 example.users;
+
+import org.springframework.boot.devtools.restart.RestartScope;
+import org.springframework.boot.test.context.TestConfiguration;
+import org.springframework.boot.testcontainers.service.connection.ServiceConnection;
+import org.springframework.context.annotation.Bean;
+import org.testcontainers.containers.MongoDBContainer;
+import org.testcontainers.utility.DockerImageName;
+
+/**
+ * @author Tim Sparg
+ * @author Oliver Drotbohm
+ */
+@TestConfiguration(proxyBeanMethods = false)
+public class TestcontainersConfiguration {
+
+ @Bean
+ @ServiceConnection
+ @RestartScope
+ MongoDBContainer mongoDbContainer() {
+ return new MongoDBContainer(DockerImageName.parse("mongo:latest"));
+ }
+}