diff --git a/README.md b/README.md index 61570b427..9445efd85 100644 --- a/README.md +++ b/README.md @@ -87,34 +87,18 @@ Redis OM Spring provides all Spring Data Redis capabilities, plus: - `@Vectorize` annotation to generate embeddings for text and images for use in Vector Similarity Searches - Vector Similarity Search API (See [Redis Stack Vectors](https://redis.io/docs/stack/search/reference/vectors/)) -### 📋 Version Requirements - -Redis OM Spring has the following version requirements: - -| Dependency | Minimum Version | Recommended Version | Notes | -|------------|----------------|-------------------|--------| -| **Spring Boot** | 3.3.x | 3.4.x or 3.5.x | Built with Spring Boot 3.4.5 | -| **Spring Data Redis** | 3.4.1 | 3.4.5 or later | Aligned with Spring Boot version | -| **Spring Framework** | 6.2.x | Latest 6.x | Transitive via Spring Boot | -| **Jedis** | 5.2.0 | 5.2.0 or later | Redis Java client | -| **Java** | 17 | 17 or 21 | Spring Boot 3.x requires Java 17+ | -| **Redis Stack** | 6.2.x | 7.2.x or later | For JSON and Search modules | - -#### Spring Boot Version Compatibility Policy - -Redis OM Spring follows an **N-2 support policy** for Spring Boot versions: - -- We build with the latest stable Spring Boot version -- We support the current version and two previous minor versions that are still receiving OSS updates -- We upgrade Spring Boot with each Redis OM Spring release - -For example, as of Redis OM Spring 1.0.0-RC4: - -- **Built with**: Spring Boot 3.4.5 -- **Minimum supported**: Spring Boot 3.3.x -- **Recommended**: Spring Boot 3.4.x or 3.5.x - -⚠️ **Note**: Using older Spring Boot versions may work but is not officially tested or supported. For production use, we recommend staying within the supported version range. +> [!IMPORTANT] +> ### 📋 Version Compatibility +> +> Choose the correct Redis OM Spring version for your Spring Boot version: +> +> | Redis OM Spring | Spring Boot | Java | Status | +> |-----------------|-------------|------|--------| +> | **1.0.x** | 3.4.x | 17+ | Maintenance | +> | **1.1.x** | 3.5.x | 17+ | Current Stable | +> | **2.0.x** | 4.0.x | 17+ | Latest | +> +> **Always use the Redis OM Spring version that matches your Spring Boot version.** ## 🏁 Getting Started @@ -492,7 +476,7 @@ This will unlock powerful AI-driven features for your applications, making data For Maven, things normally just work, when you run `./mvnw spring-boot:run`. Some users have experienced this not being the case, in which I recommend to explicitly declaring the `maven-compiler-plugin` in the case below it is paired with -an app created with [`start.spring.io`](https://start.spring.io/) with Spring Boot `v3.3.0` (all other versions can be +an app created with [`start.spring.io`](https://start.spring.io/) with Spring Boot `v4.0.0` (all other versions can be inherited from the parent poms): ```xml @@ -505,7 +489,7 @@ inherited from the parent poms): org.springframework.boot spring-boot-configuration-processor - 3.3.0 + 4.0.0 org.projectlombok @@ -515,7 +499,7 @@ inherited from the parent poms): com.redis.om redis-om-spring - 1.0.0-RC.1 + 2.0.0 diff --git a/demos/build.gradle b/demos/build.gradle index 8f7732fb5..3f068d47a 100644 --- a/demos/build.gradle +++ b/demos/build.gradle @@ -71,7 +71,7 @@ dependencies { testImplementation 'org.assertj:assertj-core' testImplementation 'org.mockito:mockito-core' testImplementation "com.redis:testcontainers-redis:${testcontainersRedisVersion}" - testImplementation "org.testcontainers:junit-jupiter" + testImplementation "org.testcontainers:junit-jupiter:1.20.4" // Optional dependencies used by some demos implementation 'com.github.javafaker:javafaker:1.0.2' diff --git a/demos/roms-amr-entraid/build.gradle b/demos/roms-amr-entraid/build.gradle index f8d5c64e8..6d8c79553 100644 --- a/demos/roms-amr-entraid/build.gradle +++ b/demos/roms-amr-entraid/build.gradle @@ -52,6 +52,8 @@ dependencies { // Test dependencies testImplementation 'org.springframework.boot:spring-boot-starter-test' + testImplementation "com.redis:testcontainers-redis:${testcontainersRedisVersion}" + testImplementation "org.testcontainers:junit-jupiter:1.20.4" } // Use -parameters flag for Spring diff --git a/demos/roms-documents/build.gradle b/demos/roms-documents/build.gradle index 21df7207d..9df1a6bca 100644 --- a/demos/roms-documents/build.gradle +++ b/demos/roms-documents/build.gradle @@ -47,8 +47,9 @@ dependencies { // Test dependencies testImplementation 'org.springframework.boot:spring-boot-starter-test' + testImplementation 'org.springframework.boot:spring-boot-starter-webmvc-test' testImplementation "com.redis:testcontainers-redis:${testcontainersRedisVersion}" - testImplementation "org.testcontainers:junit-jupiter" + testImplementation "org.testcontainers:junit-jupiter:1.20.4" } // Use -parameters flag for Spring diff --git a/demos/roms-documents/src/test/java/com/redis/om/documents/controllers/EventControllerTest.java b/demos/roms-documents/src/test/java/com/redis/om/documents/controllers/EventControllerTest.java index fbd574c94..8e16b20cd 100644 --- a/demos/roms-documents/src/test/java/com/redis/om/documents/controllers/EventControllerTest.java +++ b/demos/roms-documents/src/test/java/com/redis/om/documents/controllers/EventControllerTest.java @@ -13,7 +13,7 @@ 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.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.webmvc.test.autoconfigure.AutoConfigureMockMvc; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; diff --git a/demos/roms-hashes/build.gradle b/demos/roms-hashes/build.gradle index 58d5fdda0..08779d220 100644 --- a/demos/roms-hashes/build.gradle +++ b/demos/roms-hashes/build.gradle @@ -47,6 +47,8 @@ dependencies { // Test dependencies testImplementation 'org.springframework.boot:spring-boot-starter-test' + testImplementation "com.redis:testcontainers-redis:${testcontainersRedisVersion}" + testImplementation "org.testcontainers:junit-jupiter:1.20.4" } // Use -parameters flag for Spring diff --git a/demos/roms-modeling/build.gradle b/demos/roms-modeling/build.gradle index 01f664d09..577b71ccb 100644 --- a/demos/roms-modeling/build.gradle +++ b/demos/roms-modeling/build.gradle @@ -53,8 +53,10 @@ dependencies { // Test dependencies testImplementation 'org.springframework.boot:spring-boot-starter-test' + testImplementation 'org.springframework.boot:spring-boot-resttestclient' + testImplementation 'org.springframework.boot:spring-boot-data-redis' testImplementation "com.redis:testcontainers-redis:${testcontainersRedisVersion}" - testImplementation "org.testcontainers:junit-jupiter" + testImplementation "org.testcontainers:junit-jupiter:1.20.4" testImplementation 'org.mockito:mockito-core' } diff --git a/demos/roms-modeling/src/test/java/com/foogaro/modeling/config/TestRedisConfiguration.java b/demos/roms-modeling/src/test/java/com/foogaro/modeling/config/TestRedisConfiguration.java index 7418721c7..dab591099 100644 --- a/demos/roms-modeling/src/test/java/com/foogaro/modeling/config/TestRedisConfiguration.java +++ b/demos/roms-modeling/src/test/java/com/foogaro/modeling/config/TestRedisConfiguration.java @@ -4,7 +4,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.boot.autoconfigure.AutoConfigureAfter; -import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration; +import org.springframework.boot.data.redis.autoconfigure.DataRedisAutoConfiguration; import org.springframework.boot.test.context.TestConfiguration; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Primary; @@ -24,7 +24,7 @@ @TestConfiguration @AutoConfigureAfter( - RedisAutoConfiguration.class + DataRedisAutoConfiguration.class ) @Testcontainers @Disabled( diff --git a/demos/roms-modeling/src/test/java/com/foogaro/modeling/controller/TestTextDataController.java b/demos/roms-modeling/src/test/java/com/foogaro/modeling/controller/TestTextDataController.java index f9969d86f..c4543473b 100644 --- a/demos/roms-modeling/src/test/java/com/foogaro/modeling/controller/TestTextDataController.java +++ b/demos/roms-modeling/src/test/java/com/foogaro/modeling/controller/TestTextDataController.java @@ -13,7 +13,7 @@ import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.boot.test.web.client.TestRestTemplate; +import org.springframework.boot.resttestclient.TestRestTemplate; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.test.context.ActiveProfiles; diff --git a/demos/roms-multi-acl-account/build.gradle b/demos/roms-multi-acl-account/build.gradle index 1f4ca8102..abbda3ba2 100644 --- a/demos/roms-multi-acl-account/build.gradle +++ b/demos/roms-multi-acl-account/build.gradle @@ -31,6 +31,10 @@ repositories { dependencies { implementation project(':redis-om-spring') + // Spring Boot Data Redis (for auto-configuration classes in Spring Boot 4.0) + implementation 'org.springframework.boot:spring-boot-starter-data-redis' + implementation 'org.springframework.boot:spring-boot-data-redis' + // Important for RedisOM annotation processing! annotationProcessor project(':redis-om-spring') testAnnotationProcessor project(':redis-om-spring') @@ -44,7 +48,7 @@ dependencies { // Test dependencies testImplementation 'org.springframework.boot:spring-boot-starter-test' testImplementation "com.redis:testcontainers-redis:${testcontainersRedisVersion}" - testImplementation "org.testcontainers:junit-jupiter" + testImplementation "org.testcontainers:junit-jupiter:1.20.4" } // Use -parameters flag for Spring diff --git a/demos/roms-multi-acl-account/src/main/java/com/redis/romsmultiaclaccount/RomsMultiAclAccountApplication.java b/demos/roms-multi-acl-account/src/main/java/com/redis/romsmultiaclaccount/RomsMultiAclAccountApplication.java index 2cb03abae..eb946e0f3 100644 --- a/demos/roms-multi-acl-account/src/main/java/com/redis/romsmultiaclaccount/RomsMultiAclAccountApplication.java +++ b/demos/roms-multi-acl-account/src/main/java/com/redis/romsmultiaclaccount/RomsMultiAclAccountApplication.java @@ -2,8 +2,11 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.data.redis.autoconfigure.DataRedisRepositoriesAutoConfiguration; -@SpringBootApplication +@SpringBootApplication( + exclude = DataRedisRepositoriesAutoConfiguration.class +) public class RomsMultiAclAccountApplication { public static void main(String[] args) { diff --git a/demos/roms-multi-acl-account/src/main/java/com/redis/romsmultiaclaccount/config/RedisConnectionFactoryConfig.java b/demos/roms-multi-acl-account/src/main/java/com/redis/romsmultiaclaccount/config/RedisConnectionFactoryConfig.java index d904b0a36..3c54377a0 100644 --- a/demos/roms-multi-acl-account/src/main/java/com/redis/romsmultiaclaccount/config/RedisConnectionFactoryConfig.java +++ b/demos/roms-multi-acl-account/src/main/java/com/redis/romsmultiaclaccount/config/RedisConnectionFactoryConfig.java @@ -6,9 +6,9 @@ import org.apache.commons.logging.LogFactory; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.autoconfigure.condition.ConditionalOnThreading; -import org.springframework.boot.autoconfigure.data.redis.RedisProperties; -import org.springframework.boot.autoconfigure.thread.Threading; import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.boot.data.redis.autoconfigure.DataRedisProperties; +import org.springframework.boot.thread.Threading; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; @@ -32,15 +32,15 @@ @Configuration @EnableConfigurationProperties( - { RedisProperties.class } + { DataRedisProperties.class } ) public class RedisConnectionFactoryConfig { private static final Log logger = LogFactory.getLog(RedisConnectionFactoryConfig.class); - private final RedisProperties redisProperties; + private final DataRedisProperties redisProperties; - public RedisConnectionFactoryConfig(RedisProperties redisProperties) { + public RedisConnectionFactoryConfig(DataRedisProperties redisProperties) { this.redisProperties = redisProperties; } diff --git a/demos/roms-multitenant/build.gradle b/demos/roms-multitenant/build.gradle index d3727f79a..b28ec14ae 100644 --- a/demos/roms-multitenant/build.gradle +++ b/demos/roms-multitenant/build.gradle @@ -48,7 +48,7 @@ dependencies { // Test dependencies testImplementation 'org.springframework.boot:spring-boot-starter-test' testImplementation "com.redis:testcontainers-redis:${testcontainersRedisVersion}" - testImplementation "org.testcontainers:junit-jupiter" + testImplementation "org.testcontainers:junit-jupiter:1.20.4" } // Use -parameters flag for Spring diff --git a/demos/roms-multitenant/src/test/java/com/redis/om/multitenant/RomsMultitenantApplicationTests.java b/demos/roms-multitenant/src/test/java/com/redis/om/multitenant/RomsMultitenantApplicationTests.java index ff27468cc..846657f2b 100644 --- a/demos/roms-multitenant/src/test/java/com/redis/om/multitenant/RomsMultitenantApplicationTests.java +++ b/demos/roms-multitenant/src/test/java/com/redis/om/multitenant/RomsMultitenantApplicationTests.java @@ -21,10 +21,19 @@ import com.redis.om.spring.indexing.RediSearchIndexer; import com.redis.testcontainers.RedisStackContainer; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.Configuration; + @Testcontainers -@SpringBootTest +@SpringBootTest(classes = RomsMultitenantApplicationTests.Config.class, + properties = { "spring.main.allow-bean-definition-overriding=true" }) class RomsMultitenantApplicationTests { + @SpringBootApplication + @Configuration + static class Config extends TestConfig { + } + @Container static RedisStackContainer redis = new RedisStackContainer( RedisStackContainer.DEFAULT_IMAGE_NAME.withTag(RedisStackContainer.DEFAULT_TAG)); diff --git a/demos/roms-multitenant/src/test/java/com/redis/om/multitenant/TestConfig.java b/demos/roms-multitenant/src/test/java/com/redis/om/multitenant/TestConfig.java new file mode 100644 index 000000000..cd0bab725 --- /dev/null +++ b/demos/roms-multitenant/src/test/java/com/redis/om/multitenant/TestConfig.java @@ -0,0 +1,48 @@ +package com.redis.om.multitenant; + +import java.time.Duration; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.core.env.Environment; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.connection.RedisStandaloneConfiguration; +import org.springframework.data.redis.connection.jedis.JedisClientConfiguration; +import org.springframework.data.redis.connection.jedis.JedisConnectionFactory; +import org.springframework.data.redis.core.StringRedisTemplate; + +import redis.clients.jedis.JedisPoolConfig; + +public class TestConfig { + @Autowired + Environment env; + + @Bean + public JedisConnectionFactory jedisConnectionFactory() { + String host = env.getProperty("spring.data.redis.host", "localhost"); + int port = env.getProperty("spring.data.redis.port", Integer.class, 6379); + + RedisStandaloneConfiguration conf = new RedisStandaloneConfiguration(host, port); + + final JedisPoolConfig poolConfig = new JedisPoolConfig(); + poolConfig.setTestWhileIdle(false); + poolConfig.setMinEvictableIdleDuration(Duration.ofMillis(60000)); + poolConfig.setTimeBetweenEvictionRuns(Duration.ofMillis(30000)); + poolConfig.setNumTestsPerEvictionRun(-1); + + final int timeout = 10000; + + final JedisClientConfiguration jedisClientConfiguration = JedisClientConfiguration.builder().connectTimeout(Duration + .ofMillis(timeout)).readTimeout(Duration.ofMillis(timeout)).usePooling().poolConfig(poolConfig).build(); + + return new JedisConnectionFactory(conf, jedisClientConfiguration); + } + + @Bean + public StringRedisTemplate redisTemplate(RedisConnectionFactory connectionFactory) { + StringRedisTemplate template = new StringRedisTemplate(); + template.setConnectionFactory(connectionFactory); + + return template; + } +} diff --git a/demos/roms-permits/build.gradle b/demos/roms-permits/build.gradle index 58d5fdda0..08779d220 100644 --- a/demos/roms-permits/build.gradle +++ b/demos/roms-permits/build.gradle @@ -47,6 +47,8 @@ dependencies { // Test dependencies testImplementation 'org.springframework.boot:spring-boot-starter-test' + testImplementation "com.redis:testcontainers-redis:${testcontainersRedisVersion}" + testImplementation "org.testcontainers:junit-jupiter:1.20.4" } // Use -parameters flag for Spring diff --git a/demos/roms-vectorizers/build.gradle b/demos/roms-vectorizers/build.gradle index 3a95d80df..bd09614bd 100644 --- a/demos/roms-vectorizers/build.gradle +++ b/demos/roms-vectorizers/build.gradle @@ -59,8 +59,10 @@ dependencies { // Test dependencies testImplementation 'org.springframework.boot:spring-boot-starter-test' + testImplementation 'org.springframework.boot:spring-boot-resttestclient' + testImplementation 'org.springframework.boot:spring-boot-data-redis' testImplementation "com.redis:testcontainers-redis:${testcontainersRedisVersion}" - testImplementation "org.testcontainers:junit-jupiter" + testImplementation "org.testcontainers:junit-jupiter:1.20.4" testImplementation 'org.mockito:mockito-core' } diff --git a/demos/roms-vectorizers/src/test/java/com/foogaro/vectorizers/config/TestRedisConfiguration.java b/demos/roms-vectorizers/src/test/java/com/foogaro/vectorizers/config/TestRedisConfiguration.java index 2ec9b2fbf..4df510c96 100644 --- a/demos/roms-vectorizers/src/test/java/com/foogaro/vectorizers/config/TestRedisConfiguration.java +++ b/demos/roms-vectorizers/src/test/java/com/foogaro/vectorizers/config/TestRedisConfiguration.java @@ -4,7 +4,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.boot.autoconfigure.AutoConfigureAfter; -import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration; +import org.springframework.boot.data.redis.autoconfigure.DataRedisAutoConfiguration; import org.springframework.boot.test.context.TestConfiguration; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Primary; @@ -24,7 +24,7 @@ @TestConfiguration @AutoConfigureAfter( - RedisAutoConfiguration.class + DataRedisAutoConfiguration.class ) @Testcontainers @Disabled( diff --git a/demos/roms-vectorizers/src/test/java/com/foogaro/vectorizers/controller/TestTextDataController.java b/demos/roms-vectorizers/src/test/java/com/foogaro/vectorizers/controller/TestTextDataController.java index 77094b20c..5b017b39c 100644 --- a/demos/roms-vectorizers/src/test/java/com/foogaro/vectorizers/controller/TestTextDataController.java +++ b/demos/roms-vectorizers/src/test/java/com/foogaro/vectorizers/controller/TestTextDataController.java @@ -13,7 +13,7 @@ import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.boot.test.web.client.TestRestTemplate; +import org.springframework.boot.resttestclient.TestRestTemplate; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.test.context.ActiveProfiles; diff --git a/demos/roms-vss-movies/build.gradle b/demos/roms-vss-movies/build.gradle index 94c2ccc52..cdd99d34b 100644 --- a/demos/roms-vss-movies/build.gradle +++ b/demos/roms-vss-movies/build.gradle @@ -53,6 +53,8 @@ dependencies { // Test dependencies testImplementation 'org.springframework.boot:spring-boot-starter-test' + testImplementation "com.redis:testcontainers-redis:${testcontainersRedisVersion}" + testImplementation "org.testcontainers:junit-jupiter:1.20.4" } // Use -parameters flag for Spring diff --git a/demos/roms-vss/build.gradle b/demos/roms-vss/build.gradle index 940a9423b..b76b8a7a2 100644 --- a/demos/roms-vss/build.gradle +++ b/demos/roms-vss/build.gradle @@ -54,6 +54,8 @@ dependencies { // Test dependencies testImplementation 'org.springframework.boot:spring-boot-starter-test' + testImplementation "com.redis:testcontainers-redis:${testcontainersRedisVersion}" + testImplementation "org.testcontainers:junit-jupiter:1.20.4" } // Use -parameters flag for Spring diff --git a/docs/content/modules/ROOT/pages/migration-guide.adoc b/docs/content/modules/ROOT/pages/migration-guide.adoc index bf54d1d70..f123fcfe8 100644 --- a/docs/content/modules/ROOT/pages/migration-guide.adoc +++ b/docs/content/modules/ROOT/pages/migration-guide.adoc @@ -3,6 +3,174 @@ This guide helps you migrate between major versions of Redis OM Spring. +== Upgrading to 2.0.0 (Spring Boot 4.0.0) + +Redis OM Spring 2.0.0 is a major version upgrade that brings support for Spring Boot 4.0.0 and its ecosystem. + +=== Dependency Changes + +[cols="1,1,1"] +|=== +|Dependency |Previous Version |New Version + +|Spring Boot +|3.5.8 +|4.0.0 + +|Spring Data Redis +|3.5.6 +|4.0.0 + +|Spring Framework +|6.2.x +|7.0.x + +|Jedis +|6.0.0 +|7.0.0 + +|Java +|17+ +|21+ (required) +|=== + +=== Breaking Changes + +==== Java 21 Required + +**Impact:** All users. + +Spring Boot 4.0 requires Java 21 as the minimum version. You must upgrade your JDK to Java 21 or later. + +**Migration Steps:** + +. Install Java 21 (e.g., from https://adoptium.net/[Eclipse Temurin] or your preferred JDK distribution) +. Update your build configuration: ++ +.Maven (pom.xml) +[source,xml] +---- + + 21 + +---- ++ +.Gradle (build.gradle) +[source,groovy] +---- +java { + toolchain { + languageVersion = JavaLanguageVersion.of(21) + } +} +---- + +==== Spring Boot 4.0 Modularization + +**Impact:** Users with test dependencies or custom auto-configuration. + +Spring Boot 4.0 has reorganized packages to improve modularity. Several test-related classes have moved to new modules. + +**Migration Steps:** + +If you use any of the following in your tests, update your imports and add the appropriate test dependencies: + +.AutoConfigureMockMvc +[source,java] +---- +// Before (3.x) +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; + +// After (4.0) +import org.springframework.boot.webmvc.test.autoconfigure.AutoConfigureMockMvc; +---- + +Add test dependency: +[source,xml] +---- + + org.springframework.boot + spring-boot-starter-webmvc-test + test + +---- + +.TestRestTemplate +[source,java] +---- +// Before (3.x) +import org.springframework.boot.test.web.client.TestRestTemplate; + +// After (4.0) +import org.springframework.boot.resttestclient.TestRestTemplate; +---- + +Add test dependency: +[source,xml] +---- + + org.springframework.boot + spring-boot-resttestclient + test + +---- + +.DataRedisAutoConfiguration +[source,java] +---- +// Before (3.x) +import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration; + +// After (4.0) +import org.springframework.boot.data.redis.autoconfigure.DataRedisAutoConfiguration; +---- + +Add dependency if needed: +[source,xml] +---- + + org.springframework.boot + spring-boot-data-redis + +---- + +==== Repository Auto-Configuration Changes + +**Impact:** Users with multiple Redis repository configurations or custom auto-configuration exclusions. + +Spring Boot 4.0's auto-configuration for Redis repositories is more aggressive and may create conflicting beans if you're using custom repository configurations. + +**Migration Steps:** + +If you encounter bean definition conflicts, exclude the auto-configuration: + +[source,java] +---- +import org.springframework.boot.data.redis.autoconfigure.DataRedisRepositoriesAutoConfiguration; + +@SpringBootApplication( + exclude = DataRedisRepositoriesAutoConfiguration.class +) +public class MyApplication { + // ... +} +---- + +=== Testing Your Migration + +After upgrading, ensure you test: + +. All repository methods and queries +. Custom auto-configuration classes +. Integration tests, especially those using TestContainers +. Any direct usage of Spring Data Redis or Jedis APIs + +=== Additional Resources + +* https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-4.0-Release-Notes[Spring Boot 4.0 Release Notes] +* https://github.com/spring-projects/spring-data-redis/wiki/Spring-Data-Redis-4.0-Release-Notes[Spring Data Redis 4.0 Release Notes] +* https://github.com/redis/jedis/releases/tag/v7.0.0[Jedis 7.0.0 Release Notes] + == Upgrading to 1.1.0 (Spring Boot 3.5.x) Redis OM Spring 1.1.0 introduces support for Spring Boot 3.5.x and includes important dependency upgrades. diff --git a/docs/content/modules/ROOT/pages/setup.adoc b/docs/content/modules/ROOT/pages/setup.adoc index 1e0f3df63..7009c030d 100644 --- a/docs/content/modules/ROOT/pages/setup.adoc +++ b/docs/content/modules/ROOT/pages/setup.adoc @@ -9,11 +9,11 @@ This guide will help you set up Redis OM Spring in your project and configure it Redis OM Spring has the following requirements: -* *Java 17+*: Redis OM Spring requires JDK 17 or newer -* *Spring Boot 3.0+*: Built on Spring Framework 6.0.11+ +* *Java 21+*: Redis OM Spring requires JDK 21 or newer +* *Spring Boot 4.0+*: Built on Spring Framework 7.0.x * *Redis 8.0.0+*: Requires Redis with JSON and Query Engine capabilities -* *Jedis 5.2.0+* (included): Redis OM Spring uses Jedis as its Redis client -* *Spring Data Redis 3.4.1+* (included): Built on Spring Data Redis +* *Jedis 7.0.0+* (included): Redis OM Spring uses Jedis as its Redis client +* *Spring Data Redis 4.0.0+* (included): Built on Spring Data Redis == Setting up Redis @@ -68,8 +68,8 @@ If you're starting a new project, you can create a basic Spring Boot application 2. Select: * Project: Maven or Gradle * Language: Java - * Spring Boot: 3.0.0+ - * Java: 17+ + * Spring Boot: 4.0.0+ + * Java: 21+ 3. Generate and download the project @@ -85,14 +85,14 @@ Add the dependency to your `pom.xml`: com.redis.om redis-om-spring - 1.0.0-RC2 + 2.0.0 com.redis.om redis-om-spring-ai - 1.0.0-RC2 + 2.0.0 ---- @@ -113,7 +113,7 @@ Redis OM Spring uses code generation to create metamodels. If you encounter issu org.springframework.boot spring-boot-configuration-processor - 3.3.0 + 4.0.0 org.projectlombok @@ -123,7 +123,7 @@ Redis OM Spring uses code generation to create metamodels. If you encounter issu com.redis.om redis-om-spring - 1.0.0-RC2 + 2.0.0 @@ -146,7 +146,7 @@ To use SNAPSHOT releases, add the snapshots repository to your `pom.xml`: com.redis.om redis-om-spring - 1.0.0-RC4-SNAPSHOT + 2.0.0-SNAPSHOT ---- @@ -157,7 +157,7 @@ Add the dependency to your `build.gradle`: [source,groovy] ---- ext { - redisOmVersion = '1.0.0-RC2' + redisOmVersion = '2.0.0' } dependencies { @@ -217,7 +217,7 @@ repositories { } ext { - redisOmVersion = '1.0.0-RC4-SNAPSHOT' + redisOmVersion = '2.0.0-SNAPSHOT' } ---- diff --git a/docs/content/modules/ROOT/pages/version-requirements.adoc b/docs/content/modules/ROOT/pages/version-requirements.adoc index 26eed0eae..0bb05621d 100644 --- a/docs/content/modules/ROOT/pages/version-requirements.adoc +++ b/docs/content/modules/ROOT/pages/version-requirements.adoc @@ -18,29 +18,29 @@ Redis OM Spring has the following version requirements: |Notes |Spring Boot -|3.3.x -|3.5.8 or later -|Built with Spring Boot 3.5.8 +|4.0.0 +|4.0.0 or later +|Built with Spring Boot 4.0.0 |Spring Data Redis -|3.4.1 -|3.5.6 or later +|4.0.0 +|4.0.0 or later |Aligned with Spring Boot version |Spring Framework -|6.2.x -|Latest 6.x +|7.0.x +|Latest 7.x |Transitive via Spring Boot |Jedis -|6.0.0 -|6.0.0 or later -|Redis Java client. **Breaking change in 6.0.0** - see xref:migration-guide.adoc[Migration Guide] +|7.0.0 +|7.0.0 or later +|Redis Java client |Java -|17 -|17 or 21 -|Spring Boot 3.x requires Java 17+ +|21 +|21 +|Spring Boot 4.x requires Java 21+ |Redis Stack |6.2.x @@ -58,11 +58,11 @@ Redis OM Spring follows an **N-2 support policy** for Spring Boot versions: === Example -As of Redis OM Spring 1.1.0 (November 2025): +As of Redis OM Spring 2.0.0 (November 2025): -* **Built with**: Spring Boot 3.5.8 -* **Minimum supported**: Spring Boot 3.3.x (OSS support until June 2025) -* **Recommended**: Spring Boot 3.5.x +* **Built with**: Spring Boot 4.0.0 +* **Minimum supported**: Spring Boot 4.0.0 +* **Recommended**: Spring Boot 4.0.0 or later [WARNING] ==== @@ -97,7 +97,7 @@ To determine which Spring Boot versions Redis OM Spring supports: If you need to upgrade your Spring Boot version: -1. Check the https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-3.x-Release-Notes[Spring Boot release notes] for breaking changes +1. Check the https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-4.0-Release-Notes[Spring Boot release notes] for breaking changes 2. Update your `pom.xml` or `build.gradle` to use the new version 3. Run your tests to ensure compatibility 4. Pay special attention to: @@ -107,13 +107,13 @@ If you need to upgrade your Spring Boot version: == Java Version Requirements -Redis OM Spring requires Java 17 or higher because: +Redis OM Spring requires Java 21 or higher because: -* Spring Boot 3.x requires Java 17 as a minimum -* We use Java 17 language features in our codebase -* Java 17 is an LTS (Long Term Support) release +* Spring Boot 4.x requires Java 21 as a minimum +* We use Java 21 language features in our codebase +* Java 21 is an LTS (Long Term Support) release -We recommend using Java 17 or Java 21 (the next LTS) for production deployments. +We recommend using Java 21 for production deployments. == Redis Requirements @@ -146,8 +146,8 @@ When using Redis OM Spring, ensure your dependencies are aligned: [source,xml] ---- - 3.5.8 - 1.1.0 + 4.0.0 + 2.0.0 diff --git a/docs/package-lock.json b/docs/package-lock.json index 9f03e40d3..78cb02199 100644 --- a/docs/package-lock.json +++ b/docs/package-lock.json @@ -1064,9 +1064,9 @@ } }, "node_modules/glob": { - "version": "10.4.5", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", - "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", "license": "ISC", "dependencies": { "foreground-child": "^3.1.0", @@ -1446,9 +1446,9 @@ } }, "node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", "license": "MIT", "dependencies": { "argparse": "^2.0.1" diff --git a/gradle.properties b/gradle.properties index e199ee80d..d8a130357 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,14 +1,14 @@ -version = 1.1.1 +version = 2.0.0 group = com.redis.om description = Redis OM Spring provides powerful repository and custom object-mapping abstractions built on top of the powerful Spring Data Redis (SDR) framework. -springBootVersion = 3.5.8 +springBootVersion = 4.0.0 springDependencyVersion = 1.1.7 testcontainersRedisVersion = 2.2.4 -sdrVersion = 3.5.6 -jedisVersion = 6.0.0 +sdrVersion = 4.0.0 +jedisVersion = 7.0.0 cdi = 2.0-PFD autoServiceVersion = 1.1.1 guavaVersion = 33.4.8-jre diff --git a/redis-om-spring/src/main/java/com/redis/om/spring/RedisModulesConfiguration.java b/redis-om-spring/src/main/java/com/redis/om/spring/RedisModulesConfiguration.java index f866b4795..32e4e04b1 100644 --- a/redis-om-spring/src/main/java/com/redis/om/spring/RedisModulesConfiguration.java +++ b/redis-om-spring/src/main/java/com/redis/om/spring/RedisModulesConfiguration.java @@ -15,8 +15,6 @@ import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.boot.autoconfigure.data.redis.RedisProperties; -import org.springframework.boot.autoconfigure.gson.GsonBuilderCustomizer; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.cache.CacheManager; import org.springframework.cache.concurrent.ConcurrentMapCacheManager; @@ -50,6 +48,7 @@ import com.redis.om.spring.search.stream.EntityStream; import com.redis.om.spring.search.stream.EntityStreamImpl; import com.redis.om.spring.serialization.gson.*; +import com.redis.om.spring.serialization.gson.GsonBuilderCustomizer; import com.redis.om.spring.vectorize.Embedder; import com.redis.om.spring.vectorize.NoopEmbedder; @@ -78,7 +77,7 @@ proxyBeanMethods = false ) @EnableConfigurationProperties( - { RedisProperties.class, RedisOMProperties.class } + { RedisOMProperties.class } ) @EnableAspectJAutoProxy @ComponentScan( @@ -169,6 +168,23 @@ public GsonBuilder gsonBuilder(List customizers) { return builder; } + /** + * Creates the Gson instance from the configured GsonBuilder. + *

+ * This bean provides the Gson instance used by various components for JSON + * serialization and deserialization throughout the application. + * + * @param gsonBuilder the configured Gson builder + * @return the Gson instance + */ + @Bean + @ConditionalOnMissingBean + public com.google.gson.Gson gson(@Qualifier( + "omGsonBuilder" + ) GsonBuilder gsonBuilder) { + return gsonBuilder.create(); + } + /** * Creates the Redis modules client for accessing Redis Stack modules. *

diff --git a/redis-om-spring/src/main/java/com/redis/om/spring/convert/MappingRedisOMConverter.java b/redis-om-spring/src/main/java/com/redis/om/spring/convert/MappingRedisOMConverter.java index 58113323c..bb69453c4 100644 --- a/redis-om-spring/src/main/java/com/redis/om/spring/convert/MappingRedisOMConverter.java +++ b/redis-om-spring/src/main/java/com/redis/om/spring/convert/MappingRedisOMConverter.java @@ -18,6 +18,7 @@ import org.springframework.core.convert.support.DefaultConversionService; import org.springframework.core.convert.support.GenericConversionService; import org.springframework.data.convert.CustomConversions; +import org.springframework.data.core.TypeInformation; import org.springframework.data.mapping.*; import org.springframework.data.mapping.model.EntityInstantiator; import org.springframework.data.mapping.model.EntityInstantiators; @@ -33,7 +34,6 @@ import org.springframework.data.redis.core.mapping.RedisPersistentEntity; import org.springframework.data.redis.core.mapping.RedisPersistentProperty; import org.springframework.data.redis.util.ByteUtils; -import org.springframework.data.util.TypeInformation; import org.springframework.lang.Nullable; import org.springframework.util.*; @@ -625,6 +625,36 @@ private void writeInternal(@Nullable String keyspace, String path, @Nullable Obj return; } + // Handle arrays by iterating and writing each element directly + if (value.getClass().isArray() && !(value instanceof byte[])) { + TypeInformation componentType; + if (typeHint.getComponentType() != null) { + componentType = typeHint.getComponentType(); + } else { + // For arrays, get the component type from the actual array class + Class arrayComponentType = value.getClass().getComponentType(); + componentType = TypeInformation.of(arrayComponentType); + } + + int length = java.lang.reflect.Array.getLength(value); + for (int i = 0; i < length; i++) { + Object element = java.lang.reflect.Array.get(value, i); + + // Break if we encounter a null element (consistent with collection handling) + if (element == null) { + break; + } + + String currentPath = path + (path.isEmpty() ? "" : ".") + "[" + i + "]"; + if (customConversions.hasCustomWriteTarget(element.getClass())) { + writeToBucket(currentPath, element, sink, componentType.getType()); + } else { + writeInternal(keyspace, currentPath, element, componentType, sink); + } + } + return; + } + if (value.getClass() != typeHint.getType()) { typeMapper.writeType(value.getClass(), sink.getBucket().getPropertyPath(path)); } @@ -658,6 +688,14 @@ private void writeInternal(@Nullable String keyspace, String path, @Nullable Obj .getRequiredComponentType(), sink); } else { + // Check if collection contains nulls (either null elements or arrays with nulls) + // If so, skip writing the collection entirely + boolean hasNulls = collectionContainsNulls(propertyValue); + if (hasNulls) { + // Don't persist collections with nulls + return; + } + if (Iterable.class.isAssignableFrom(propertyValue.getClass())) { writeCollection(entity.getType(), keyspace, propertyStringPath, (Iterable) propertyValue, @@ -1124,6 +1162,41 @@ private Object toArray(Collection source, Class arrayType, Class v return i > 0 ? targetArray : null; } + /** + * Checks if a collection contains any null elements or if any nested arrays contain nulls. + * This is used to validate collections before persisting them to Redis. + * + * @param value the collection to check (can be Iterable or array) + * @return true if the collection contains nulls, false otherwise + */ + private boolean collectionContainsNulls(Object value) { + Iterable iterable; + + if (value instanceof Iterable) { + iterable = (Iterable) value; + } else if (value.getClass().isArray()) { + iterable = CollectionUtils.arrayToList(value); + } else { + return false; + } + + for (Object element : iterable) { + if (element == null) { + return true; + } + // Check if element is an array and if that array contains nulls + if (element.getClass().isArray() && !(element instanceof byte[])) { + int length = java.lang.reflect.Array.getLength(element); + for (int i = 0; i < length; i++) { + if (java.lang.reflect.Array.get(element, i) == null) { + return true; + } + } + } + } + return false; + } + /** * Sets the reference resolver used for resolving object references in Redis. * diff --git a/redis-om-spring/src/main/java/com/redis/om/spring/id/ULIDIdentifierGenerator.java b/redis-om-spring/src/main/java/com/redis/om/spring/id/ULIDIdentifierGenerator.java index 2e69574a4..f1d57c304 100644 --- a/redis-om-spring/src/main/java/com/redis/om/spring/id/ULIDIdentifierGenerator.java +++ b/redis-om-spring/src/main/java/com/redis/om/spring/id/ULIDIdentifierGenerator.java @@ -1,8 +1,8 @@ package com.redis.om.spring.id; import org.springframework.dao.InvalidDataAccessApiUsageException; +import org.springframework.data.core.TypeInformation; import org.springframework.data.keyvalue.core.IdentifierGenerator; -import org.springframework.data.util.TypeInformation; import org.springframework.util.ClassUtils; import com.github.f4b6a3.ulid.Ulid; diff --git a/redis-om-spring/src/main/java/com/redis/om/spring/indexing/RediSearchIndexer.java b/redis-om-spring/src/main/java/com/redis/om/spring/indexing/RediSearchIndexer.java index 2f111d3c5..ba4eebb5a 100644 --- a/redis-om-spring/src/main/java/com/redis/om/spring/indexing/RediSearchIndexer.java +++ b/redis-om-spring/src/main/java/com/redis/om/spring/indexing/RediSearchIndexer.java @@ -20,13 +20,13 @@ import org.springframework.context.expression.BeanFactoryResolver; import org.springframework.data.annotation.Id; import org.springframework.data.annotation.Reference; +import org.springframework.data.core.TypeInformation; import org.springframework.data.geo.Point; import org.springframework.data.redis.core.RedisHash; import org.springframework.data.redis.core.TimeToLive; import org.springframework.data.redis.core.convert.KeyspaceConfiguration.KeyspaceSettings; import org.springframework.data.redis.core.mapping.RedisMappingContext; import org.springframework.data.redis.core.mapping.RedisPersistentEntity; -import org.springframework.data.util.TypeInformation; import org.springframework.expression.Expression; import org.springframework.expression.ExpressionParser; import org.springframework.expression.spel.standard.SpelExpressionParser; @@ -57,12 +57,12 @@ /** * Component responsible for creating and managing RediSearch indices for Redis OM Spring entities. - * + * * This class handles the automatic creation of search indices for entities annotated with {@link Document} * or {@link RedisHash}. It processes field annotations like {@link Indexed}, {@link Searchable}, * {@link TextIndexed}, {@link TagIndexed}, {@link NumericIndexed}, {@link GeoIndexed}, and * {@link VectorIndexed} to configure appropriate search schema fields. - * + * *

The indexer maintains mappings between: *

    *
  • Redis keyspaces and entity classes
  • @@ -70,7 +70,7 @@ *
  • Entity fields and their search index aliases
  • *
  • Entity classes and their identifier filters
  • *
- * + * *

Key features include: *

    *
  • Automatic index creation with configurable creation modes
  • @@ -80,7 +80,7 @@ *
  • Reference field indexing
  • *
  • TTL (Time To Live) configuration
  • *
- * + * * @see Document * @see RedisHash * @see IndexingOptions @@ -576,7 +576,7 @@ public String getKeyspacePrefix(Class entityClass) { * Checks whether an index definition exists for the specified entity class. * This method verifies if the entity class has been registered and processed * for index creation, regardless of whether the actual Redis index exists. - * + * * @param entityClass the entity class to check for index definition * @return true if an index definition exists for the entity class, false otherwise */ @@ -588,7 +588,7 @@ public boolean indexDefinitionExistsFor(Class entityClass) { * Checks whether a RediSearch index actually exists in Redis for the specified entity class. * This method queries Redis directly to verify the existence of the search index, * unlike {@link #indexDefinitionExistsFor(Class)} which only checks internal mappings. - * + * * @param entityClass the entity class to check for index existence in Redis * @return true if the search index exists in Redis, false otherwise * @throws JedisDataException if a Redis error occurs other than index not found errors @@ -601,7 +601,7 @@ public boolean indexExistsFor(Class entityClass) { if (errorMessage != null) { String lowerCaseMessage = errorMessage.toLowerCase(); // Handle various error messages for missing index across different Redis versions - // - "Unknown index name" or "Unknown Index name" - Redis Stack / Redis 7.x + // - "Unknown index name" or "Unknown Index name" - Redis Stack / Redis 7.x // - Potentially other variations in Redis 8.0+ if (lowerCaseMessage.contains("unknown index") || lowerCaseMessage.contains("no such index") || lowerCaseMessage .contains("index does not exist") || lowerCaseMessage.contains("not found")) { @@ -626,7 +626,7 @@ Map getIndexInfo(Class entityClass) { * Retrieves the search schema fields for the specified entity class. * The schema contains the complete field definitions used to create the RediSearch index, * including field names, types, and indexing options. - * + * * @param entityClass the entity class to get the search schema for * @return a list of SearchField objects representing the index schema, * or null if no schema has been generated for the entity class diff --git a/redis-om-spring/src/main/java/com/redis/om/spring/mapping/RedisEnhancedMappingContext.java b/redis-om-spring/src/main/java/com/redis/om/spring/mapping/RedisEnhancedMappingContext.java index e75342e86..6fca31d63 100644 --- a/redis-om-spring/src/main/java/com/redis/om/spring/mapping/RedisEnhancedMappingContext.java +++ b/redis-om-spring/src/main/java/com/redis/om/spring/mapping/RedisEnhancedMappingContext.java @@ -2,13 +2,13 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.springframework.data.core.TypeInformation; import org.springframework.data.redis.core.TimeToLiveAccessor; import org.springframework.data.redis.core.convert.KeyspaceConfiguration; import org.springframework.data.redis.core.convert.MappingConfiguration; import org.springframework.data.redis.core.index.IndexConfiguration; import org.springframework.data.redis.core.mapping.RedisMappingContext; import org.springframework.data.redis.core.mapping.RedisPersistentEntity; -import org.springframework.data.util.TypeInformation; /** * Enhanced Redis mapping context that extends Spring Data Redis's {@link RedisMappingContext} diff --git a/redis-om-spring/src/main/java/com/redis/om/spring/mapping/RedisEnhancedPersistentEntity.java b/redis-om-spring/src/main/java/com/redis/om/spring/mapping/RedisEnhancedPersistentEntity.java index b44052697..e3ae4fc44 100644 --- a/redis-om-spring/src/main/java/com/redis/om/spring/mapping/RedisEnhancedPersistentEntity.java +++ b/redis-om-spring/src/main/java/com/redis/om/spring/mapping/RedisEnhancedPersistentEntity.java @@ -5,13 +5,13 @@ import java.util.List; import java.util.Optional; +import org.springframework.data.core.TypeInformation; import org.springframework.data.keyvalue.core.mapping.KeySpaceResolver; import org.springframework.data.mapping.MappingException; import org.springframework.data.redis.core.TimeToLiveAccessor; import org.springframework.data.redis.core.mapping.BasicRedisPersistentEntity; import org.springframework.data.redis.core.mapping.RedisPersistentEntity; import org.springframework.data.redis.core.mapping.RedisPersistentProperty; -import org.springframework.data.util.TypeInformation; import org.springframework.lang.Nullable; import com.redis.om.spring.annotations.RedisKey; diff --git a/redis-om-spring/src/main/java/com/redis/om/spring/repository/query/RediSearchQuery.java b/redis-om-spring/src/main/java/com/redis/om/spring/repository/query/RediSearchQuery.java index 11ede3171..2177267a2 100644 --- a/redis-om-spring/src/main/java/com/redis/om/spring/repository/query/RediSearchQuery.java +++ b/redis-om-spring/src/main/java/com/redis/om/spring/repository/query/RediSearchQuery.java @@ -17,12 +17,12 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.beans.factory.annotation.Value; +import org.springframework.data.core.PropertyPath; import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort.Order; import org.springframework.data.geo.Point; import org.springframework.data.keyvalue.core.KeyValueOperations; -import org.springframework.data.mapping.PropertyPath; import org.springframework.data.repository.core.RepositoryMetadata; import org.springframework.data.repository.query.*; import org.springframework.data.repository.query.parser.AbstractQueryCreator; diff --git a/redis-om-spring/src/main/java/com/redis/om/spring/repository/query/RedisEnhancedQuery.java b/redis-om-spring/src/main/java/com/redis/om/spring/repository/query/RedisEnhancedQuery.java index b638ab3f3..3ed17ce07 100644 --- a/redis-om-spring/src/main/java/com/redis/om/spring/repository/query/RedisEnhancedQuery.java +++ b/redis-om-spring/src/main/java/com/redis/om/spring/repository/query/RedisEnhancedQuery.java @@ -14,12 +14,12 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.beans.factory.annotation.Value; +import org.springframework.data.core.PropertyPath; import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort.Order; import org.springframework.data.geo.Point; import org.springframework.data.keyvalue.core.KeyValueOperations; -import org.springframework.data.mapping.PropertyPath; import org.springframework.data.redis.core.RedisCallback; import org.springframework.data.redis.core.RedisOperations; import org.springframework.data.redis.core.convert.RedisData; @@ -893,6 +893,13 @@ private String prepareQuery(final Object[] parameters, boolean excludeNullParams while (iterator.hasNext()) { Parameter p = iterator.next(); + + // Skip special parameters like Pageable, Sort, etc. + if (p.isSpecialParameter()) { + index++; + continue; + } + Optional maybeKey = p.getName(); String key; if (maybeKey.isPresent()) { diff --git a/redis-om-spring/src/main/java/com/redis/om/spring/serialization/gson/GsonBuilderCustomizer.java b/redis-om-spring/src/main/java/com/redis/om/spring/serialization/gson/GsonBuilderCustomizer.java new file mode 100644 index 000000000..170760fdc --- /dev/null +++ b/redis-om-spring/src/main/java/com/redis/om/spring/serialization/gson/GsonBuilderCustomizer.java @@ -0,0 +1,23 @@ +package com.redis.om.spring.serialization.gson; + +import com.google.gson.GsonBuilder; + +/** + * Callback interface that can be used to customize a {@link GsonBuilder}. + *

+ * This interface replaces the Spring Boot GsonBuilderCustomizer which was moved + * to a separate module in Spring Boot 4.0. It provides the same functionality + * for customizing GsonBuilder instances in Redis OM Spring. + * + * @since 2.0.0 + */ +@FunctionalInterface +public interface GsonBuilderCustomizer { + + /** + * Customize the given {@link GsonBuilder}. + * + * @param gsonBuilder the builder to customize + */ + void customize(GsonBuilder gsonBuilder); +} diff --git a/tests/build.gradle b/tests/build.gradle index de9541d73..f381a1ed8 100644 --- a/tests/build.gradle +++ b/tests/build.gradle @@ -77,7 +77,8 @@ dependencies { testImplementation 'org.mockito:mockito-core' testImplementation "com.redis:testcontainers-redis:${testcontainersRedisVersion}" testImplementation "com.karuslabs:elementary:${elementaryVersion}" - testImplementation "org.testcontainers:junit-jupiter" + testImplementation "org.testcontainers:junit-jupiter:1.20.4" + testImplementation 'org.springframework.boot:spring-boot-jackson2' // Other implementation 'com.fasterxml.jackson.core:jackson-databind' diff --git a/tests/src/test/java/com/redis/om/spring/id/ULIDIdentifierTest.java b/tests/src/test/java/com/redis/om/spring/id/ULIDIdentifierTest.java index c38e207d0..bfa3942a5 100644 --- a/tests/src/test/java/com/redis/om/spring/id/ULIDIdentifierTest.java +++ b/tests/src/test/java/com/redis/om/spring/id/ULIDIdentifierTest.java @@ -12,7 +12,7 @@ import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.dao.InvalidDataAccessApiUsageException; -import org.springframework.data.util.TypeInformation; +import org.springframework.data.core.TypeInformation; import com.github.f4b6a3.ulid.Ulid; import com.google.gson.JsonObject; diff --git a/tests/src/test/java/com/redis/om/spring/indexing/ComprehensiveDynamicIndexingIntegrationTest.java b/tests/src/test/java/com/redis/om/spring/indexing/ComprehensiveDynamicIndexingIntegrationTest.java index 5ca4dbf70..22af5e64b 100644 --- a/tests/src/test/java/com/redis/om/spring/indexing/ComprehensiveDynamicIndexingIntegrationTest.java +++ b/tests/src/test/java/com/redis/om/spring/indexing/ComprehensiveDynamicIndexingIntegrationTest.java @@ -14,6 +14,7 @@ import org.springframework.test.context.TestPropertySource; import com.redis.om.spring.AbstractBaseOMTest; +import com.redis.om.spring.TestConfig; import com.redis.om.spring.annotations.Document; import com.redis.om.spring.annotations.EnableRedisDocumentRepositories; import com.redis.om.spring.annotations.Indexed; @@ -33,7 +34,7 @@ * - 6d97f78: Dynamic index resolution with multi-tenant support */ @DirtiesContext -@SpringBootTest(classes = ComprehensiveDynamicIndexingIntegrationTest.TestConfig.class) +@SpringBootTest(classes = ComprehensiveDynamicIndexingIntegrationTest.Config.class) @TestPropertySource(properties = { "app.environment=test", "app.version=1.2.3", @@ -47,7 +48,7 @@ public class ComprehensiveDynamicIndexingIntegrationTest extends AbstractBaseOMT @EnableRedisDocumentRepositories(basePackages = { "com.redis.om.spring.fixtures.document.repository" }) - static class TestConfig { + static class Config extends TestConfig { @Bean public IndexMigrationService indexMigrationService(RediSearchIndexer indexer, ApplicationContext ctx) { diff --git a/tests/src/test/java/com/redis/om/spring/indexing/ConfigurableIndexDefinitionProviderIntegrationTest.java b/tests/src/test/java/com/redis/om/spring/indexing/ConfigurableIndexDefinitionProviderIntegrationTest.java index 2d628d039..3d1f6e1b6 100644 --- a/tests/src/test/java/com/redis/om/spring/indexing/ConfigurableIndexDefinitionProviderIntegrationTest.java +++ b/tests/src/test/java/com/redis/om/spring/indexing/ConfigurableIndexDefinitionProviderIntegrationTest.java @@ -16,6 +16,7 @@ import org.springframework.test.annotation.DirtiesContext; import com.redis.om.spring.AbstractBaseOMTest; +import com.redis.om.spring.TestConfig; import com.redis.om.spring.annotations.Document; import com.redis.om.spring.annotations.EnableRedisDocumentRepositories; import com.redis.om.spring.annotations.Indexed; @@ -35,13 +36,13 @@ * Tests the complete Spring Data Redis integration bridge functionality. */ @DirtiesContext -@SpringBootTest(classes = ConfigurableIndexDefinitionProviderIntegrationTest.TestConfig.class) +@SpringBootTest(classes = ConfigurableIndexDefinitionProviderIntegrationTest.Config.class) public class ConfigurableIndexDefinitionProviderIntegrationTest extends AbstractBaseOMTest { @SpringBootApplication @Configuration @EnableRedisDocumentRepositories(basePackageClasses = ConfigurableIndexDefinitionProviderIntegrationTest.class) - static class TestConfig { + static class Config extends TestConfig { @Bean public ConfigurableIndexDefinitionProvider configurableIndexDefinitionProvider( RediSearchIndexer indexer, ApplicationContext applicationContext) { diff --git a/tests/src/test/java/com/redis/om/spring/indexing/EntityClassSpelEdgeCasesTest.java b/tests/src/test/java/com/redis/om/spring/indexing/EntityClassSpelEdgeCasesTest.java index 28355d9b7..0865afbd3 100644 --- a/tests/src/test/java/com/redis/om/spring/indexing/EntityClassSpelEdgeCasesTest.java +++ b/tests/src/test/java/com/redis/om/spring/indexing/EntityClassSpelEdgeCasesTest.java @@ -14,6 +14,7 @@ import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.context.TestPropertySource; +import com.redis.om.spring.TestConfig; import com.redis.om.spring.annotations.Document; import com.redis.om.spring.annotations.EnableRedisDocumentRepositories; import com.redis.om.spring.annotations.Indexed; @@ -32,13 +33,13 @@ "app.null.value=" }) @DirtiesContext -@SpringBootTest(classes = EntityClassSpelEdgeCasesTest.TestConfig.class) +@SpringBootTest(classes = EntityClassSpelEdgeCasesTest.Config.class) public class EntityClassSpelEdgeCasesTest { @SpringBootApplication @Configuration @EnableRedisDocumentRepositories(basePackageClasses = EntityClassSpelEdgeCasesTest.class) - static class TestConfig { + static class Config extends TestConfig { @Bean public TenantResolver tenantResolver() { return new TenantResolver(); diff --git a/tests/src/test/java/com/redis/om/spring/indexing/MultiTenantIndexIsolationIntegrationTest.java b/tests/src/test/java/com/redis/om/spring/indexing/MultiTenantIndexIsolationIntegrationTest.java index e21b479b3..9768adcc9 100644 --- a/tests/src/test/java/com/redis/om/spring/indexing/MultiTenantIndexIsolationIntegrationTest.java +++ b/tests/src/test/java/com/redis/om/spring/indexing/MultiTenantIndexIsolationIntegrationTest.java @@ -17,6 +17,7 @@ import com.google.gson.Gson; import com.redis.om.spring.AbstractBaseOMTest; +import com.redis.om.spring.TestConfig; import com.redis.om.spring.annotations.Document; import com.redis.om.spring.annotations.EnableRedisDocumentRepositories; import com.redis.om.spring.annotations.Indexed; @@ -34,13 +35,13 @@ * with separate indices and isolated searches per tenant. */ @DirtiesContext -@SpringBootTest(classes = MultiTenantIndexIsolationIntegrationTest.TestConfig.class) +@SpringBootTest(classes = MultiTenantIndexIsolationIntegrationTest.Config.class) public class MultiTenantIndexIsolationIntegrationTest extends AbstractBaseOMTest { @SpringBootApplication @Configuration @EnableRedisDocumentRepositories(basePackageClasses = MultiTenantIndexIsolationIntegrationTest.class) - static class TestConfig { + static class Config extends TestConfig { @Bean public IndexMigrationService indexMigrationService(RediSearchIndexer indexer, ApplicationContext ctx) { diff --git a/tests/src/test/java/com/redis/om/spring/indexing/RealIntegrationTest.java b/tests/src/test/java/com/redis/om/spring/indexing/RealIntegrationTest.java index 978d862d5..0805db8dd 100644 --- a/tests/src/test/java/com/redis/om/spring/indexing/RealIntegrationTest.java +++ b/tests/src/test/java/com/redis/om/spring/indexing/RealIntegrationTest.java @@ -10,6 +10,7 @@ import org.springframework.context.annotation.Configuration; import com.redis.om.spring.AbstractBaseOMTest; +import com.redis.om.spring.TestConfig; import com.redis.om.spring.annotations.Document; import com.redis.om.spring.annotations.Indexed; import com.redis.om.spring.annotations.IndexingOptions; @@ -21,13 +22,13 @@ import org.springframework.test.annotation.DirtiesContext; @DirtiesContext -@SpringBootTest(classes = RealIntegrationTest.TestConfig.class) +@SpringBootTest(classes = RealIntegrationTest.Config.class) public class RealIntegrationTest extends AbstractBaseOMTest { @SpringBootApplication @Configuration @EnableRedisDocumentRepositories(basePackageClasses = RealIntegrationTest.class) - static class TestConfig { + static class Config extends TestConfig { @Bean public IndexMigrationService indexMigrationService(RediSearchIndexer indexer, ApplicationContext ctx) { diff --git a/tests/src/test/java/com/redis/om/spring/search/stream/TestTupleJsonSerialization.java b/tests/src/test/java/com/redis/om/spring/search/stream/TestTupleJsonSerialization.java index 853234a87..83b9184d3 100644 --- a/tests/src/test/java/com/redis/om/spring/search/stream/TestTupleJsonSerialization.java +++ b/tests/src/test/java/com/redis/om/spring/search/stream/TestTupleJsonSerialization.java @@ -13,8 +13,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.json.JacksonTester; -import org.springframework.boot.test.json.JsonContent; +import org.springframework.core.io.ClassPathResource; import org.springframework.data.geo.Point; import com.fasterxml.jackson.databind.ObjectMapper; @@ -38,15 +37,10 @@ class TestTupleJsonSerialization extends AbstractBaseDocumentTest { @Autowired EntityStream entityStream; - @SuppressWarnings( - "unused" - ) - private JacksonTester>> json; + private ObjectMapper objectMapper = new ObjectMapper(); @BeforeEach void setupAndCleanup() { - ObjectMapper objectMapper = new ObjectMapper(); - JacksonTester.initFields(this, objectMapper); // companies repository.deleteAll(); @@ -81,10 +75,12 @@ void testTripleResultWithLabels() throws IOException { assertEquals(3, results.size()); - JsonContent>> asJson = json.write(results); + String actualJson = objectMapper.writeValueAsString(results); + String expectedJson = new String(new ClassPathResource("com/redis/om/spring/search/stream/companies.json") + .getInputStream().readAllBytes()); // See JSON file under - // srr/test/resource/com/redis/om/spring/search/stream/companies.json - assertThat(asJson).isEqualToJson("companies.json"); + // src/test/resources/com/redis/om/spring/search/stream/companies.json + assertThat(actualJson).isEqualToIgnoringWhitespace(expectedJson); } }