diff --git a/docs/content/modules/ROOT/nav.adoc b/docs/content/modules/ROOT/nav.adoc index 5c4baffb..17123fdf 100644 --- a/docs/content/modules/ROOT/nav.adoc +++ b/docs/content/modules/ROOT/nav.adoc @@ -7,6 +7,7 @@ * xref:version-requirements.adoc[Version Requirements] * xref:configuration.adoc[Configuration] * xref:quickstart.adoc[Quick Start Example] +* xref:migration-guide.adoc[Migration Guide] .Core Concepts * xref:data-models.adoc[Redis Data Models] diff --git a/docs/content/modules/ROOT/pages/migration-guide.adoc b/docs/content/modules/ROOT/pages/migration-guide.adoc new file mode 100644 index 00000000..bf54d1d7 --- /dev/null +++ b/docs/content/modules/ROOT/pages/migration-guide.adoc @@ -0,0 +1,114 @@ += Migration Guide +:navtitle: Migration Guide + +This guide helps you migrate between major versions of Redis OM Spring. + +== 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. + +=== Dependency Changes + +[cols="1,1,1"] +|=== +|Dependency |Previous Version |New Version + +|Spring Boot +|3.4.x +|3.5.8 + +|Spring Data Redis +|3.4.x +|3.5.6 + +|Jedis +|5.2.0 +|6.0.0 +|=== + +=== Breaking Changes + +==== Jedis 6.0.0 Query Escaping + +**Impact:** Users writing custom RediSearch queries using Jedis directly. + +Jedis 6.0.0 changed how it handles query string escaping for RediSearch queries. Multi-word search terms must now use double quotes (`"`) instead of single quotes (`'`). + +**Before (Jedis 5.2.0):** +[source,java] +---- +SearchOperations ops = modulesOperations.opsForSearch("myIndex"); +SearchResult result = ops.search(new Query("@title:'hello world'")); +---- + +**After (Jedis 6.0.0):** +[source,java] +---- +SearchOperations ops = modulesOperations.opsForSearch("myIndex"); +SearchResult result = ops.search(new Query("@title:\"hello world\"")); +---- + +**Who is affected:** + +* Users implementing custom repository methods that construct `Query` objects with Jedis +* Users directly using `SearchOperations` with string-based queries containing spaces + +**Who is NOT affected:** + +* Users only using Redis OM Spring's built-in repository query methods +* Users using Entity Streams API +* Users using `@Query` annotations (these are handled internally by Redis OM Spring) + +=== Migration Steps + +. Update your `pom.xml` or `build.gradle` to use Redis OM Spring 1.1.0 +. If you have custom repository implementations using Jedis `Query` objects: +.. Review all custom query strings +.. Replace single quotes with double quotes around multi-word search terms +.. Test your custom queries thoroughly + +=== Example Migration + +Here's a complete example of migrating a custom repository method: + +.Before (1.0.x) +[source,java] +---- +@Override +public Optional findByTitle(String title) { + SearchOperations ops = modulesOperations.opsForSearch(MyEntity.class.getName() + "Idx"); + SearchResult result = ops.search(new Query("@title:'" + title + "'")); // <1> + if (result.getTotalResults() > 0) { + Document doc = result.getDocuments().get(0); + return Optional.of(ObjectUtils.documentToEntity(doc, MyEntity.class, converter)); + } + return Optional.empty(); +} +---- +<1> Single quotes around the title value + +.After (1.1.0) +[source,java] +---- +@Override +public Optional findByTitle(String title) { + SearchOperations ops = modulesOperations.opsForSearch(MyEntity.class.getName() + "Idx"); + SearchResult result = ops.search(new Query("@title:\"" + title + "\"")); // <1> + if (result.getTotalResults() > 0) { + Document doc = result.getDocuments().get(0); + return Optional.of(ObjectUtils.documentToEntity(doc, MyEntity.class, converter)); + } + return Optional.empty(); +} +---- +<1> Double quotes around the title value + +=== Testing Your Migration + +After upgrading, ensure you test: + +. All custom repository methods that use Jedis `Query` objects +. Any direct usage of `SearchOperations` +. Integration tests with multi-word search terms + +The Redis OM Spring test suite includes comprehensive tests for query escaping. Refer to the test examples in the repository for best practices. diff --git a/docs/content/modules/ROOT/pages/version-requirements.adoc b/docs/content/modules/ROOT/pages/version-requirements.adoc index 395669bf..26eed0ea 100644 --- a/docs/content/modules/ROOT/pages/version-requirements.adoc +++ b/docs/content/modules/ROOT/pages/version-requirements.adoc @@ -19,12 +19,12 @@ Redis OM Spring has the following version requirements: |Spring Boot |3.3.x -|3.4.x or 3.5.x -|Built with Spring Boot 3.4.5 +|3.5.8 or later +|Built with Spring Boot 3.5.8 |Spring Data Redis |3.4.1 -|3.4.5 or later +|3.5.6 or later |Aligned with Spring Boot version |Spring Framework @@ -33,9 +33,9 @@ Redis OM Spring has the following version requirements: |Transitive via Spring Boot |Jedis -|5.2.0 -|5.2.0 or later -|Redis Java client +|6.0.0 +|6.0.0 or later +|Redis Java client. **Breaking change in 6.0.0** - see xref:migration-guide.adoc[Migration Guide] |Java |17 @@ -58,11 +58,11 @@ Redis OM Spring follows an **N-2 support policy** for Spring Boot versions: === Example -As of Redis OM Spring 1.0.0-RC4 (August 2025): +As of Redis OM Spring 1.1.0 (November 2025): -* **Built with**: Spring Boot 3.4.8 +* **Built with**: Spring Boot 3.5.8 * **Minimum supported**: Spring Boot 3.3.x (OSS support until June 2025) -* **Recommended**: Spring Boot 3.4.x or 3.5.x +* **Recommended**: Spring Boot 3.5.x [WARNING] ==== @@ -146,8 +146,8 @@ When using Redis OM Spring, ensure your dependencies are aligned: [source,xml] ---- - 3.4.5 - 1.0.0-RC4 + 3.5.8 + 1.1.0 diff --git a/gradle.properties b/gradle.properties index 81bf4a9f..06278016 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,14 +1,14 @@ -version = 1.0.5 +version = 1.1.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.4.12 +springBootVersion = 3.5.8 springDependencyVersion = 1.1.7 testcontainersRedisVersion = 2.2.4 -sdrVersion = 3.4.12 -jedisVersion = 5.2.0 +sdrVersion = 3.5.6 +jedisVersion = 6.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/repository/query/RediSearchQuery.java b/redis-om-spring/src/main/java/com/redis/om/spring/repository/query/RediSearchQuery.java index c330a1d9..55ed607e 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 @@ -202,18 +202,18 @@ private static FieldType getRedisFieldTypeForMapValue(Class fieldType) { *
  • Initializes pagination, sorting, and projection capabilities
  • * * - * @param queryMethod the repository method metadata containing signature and return type information - * @param metadata the repository metadata providing domain type and interface details - * @param indexer the RediSearch indexer for managing search indexes and field mappings - * @param evaluationContextProvider Spring Data's context provider for SpEL expression evaluation - * @param keyValueOperations low-level Redis key-value operations template - * @param rmo Redis modules operations providing access to RediSearch, RedisJSON, and - * probabilistic data structures - * @param queryCreator the query creator class for building Redis queries (currently unused but reserved - * for future extensibility) - * @param gsonBuilder pre-configured Gson builder for JSON serialization/deserialization of Redis - * documents - * @param redisOMProperties configuration properties for Redis OM Spring behavior and defaults + * @param queryMethod the repository method metadata containing signature and return type information + * @param metadata the repository metadata providing domain type and interface details + * @param indexer the RediSearch indexer for managing search indexes and field mappings + * @param valueExpressionDelegate Spring Data's delegate for value expression evaluation in query methods + * @param keyValueOperations low-level Redis key-value operations template + * @param rmo Redis modules operations providing access to RediSearch, RedisJSON, and + * probabilistic data structures + * @param queryCreator the query creator class for building Redis queries (currently unused but reserved + * for future extensibility) + * @param gsonBuilder pre-configured Gson builder for JSON serialization/deserialization of Redis + * documents + * @param redisOMProperties configuration properties for Redis OM Spring behavior and defaults * */ @SuppressWarnings( @@ -223,7 +223,7 @@ public RediSearchQuery(// QueryMethod queryMethod, // RepositoryMetadata metadata, // RediSearchIndexer indexer, // - QueryMethodEvaluationContextProvider evaluationContextProvider, // + org.springframework.data.repository.query.ValueExpressionDelegate valueExpressionDelegate, // KeyValueOperations keyValueOperations, // RedisModulesOperations rmo, // Class> queryCreator, // 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 02889d62..b638ab3f 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 @@ -161,17 +161,17 @@ public class RedisEnhancedQuery implements RepositoryQuery { *
  • Set up parameter binding for safe query execution
  • * * - * @param queryMethod the Spring Data query method metadata containing method signature, - * return type, and parameter information - * @param metadata the repository metadata providing domain type and interface information - * @param indexer the RediSearch indexer for index name resolution and field mapping - * @param evaluationContextProvider context provider for SpEL expression evaluation - * @param keyValueOperations Spring Data KeyValue operations for basic entity operations - * @param redisOperations low-level Redis operations for direct Redis access - * @param rmo Redis modules operations providing access to RediSearch, RedisJSON, and other - * modules - * @param queryCreator the query creator class for custom query construction (currently unused) - * @param redisOMProperties configuration properties for Redis OM behavior and defaults + * @param queryMethod the Spring Data query method metadata containing method signature, + * return type, and parameter information + * @param metadata the repository metadata providing domain type and interface information + * @param indexer the RediSearch indexer for index name resolution and field mapping + * @param valueExpressionDelegate Spring Data's delegate for value expression evaluation in query methods + * @param keyValueOperations Spring Data KeyValue operations for basic entity operations + * @param redisOperations low-level Redis operations for direct Redis access + * @param rmo Redis modules operations providing access to RediSearch, RedisJSON, and other + * modules + * @param queryCreator the query creator class for custom query construction (currently unused) + * @param redisOMProperties configuration properties for Redis OM behavior and defaults * */ @SuppressWarnings( @@ -180,7 +180,7 @@ public class RedisEnhancedQuery implements RepositoryQuery { public RedisEnhancedQuery(QueryMethod queryMethod, // RepositoryMetadata metadata, // RediSearchIndexer indexer, // - QueryMethodEvaluationContextProvider evaluationContextProvider, // + org.springframework.data.repository.query.ValueExpressionDelegate valueExpressionDelegate, // KeyValueOperations keyValueOperations, // RedisOperations redisOperations, // RedisModulesOperations rmo, // diff --git a/redis-om-spring/src/main/java/com/redis/om/spring/repository/support/RedisDocumentRepositoryFactory.java b/redis-om-spring/src/main/java/com/redis/om/spring/repository/support/RedisDocumentRepositoryFactory.java index fd3917cb..2d93a10a 100644 --- a/redis-om-spring/src/main/java/com/redis/om/spring/repository/support/RedisDocumentRepositoryFactory.java +++ b/redis-om-spring/src/main/java/com/redis/om/spring/repository/support/RedisDocumentRepositoryFactory.java @@ -20,8 +20,8 @@ import org.springframework.data.repository.query.QueryLookupStrategy; import org.springframework.data.repository.query.QueryLookupStrategy.Key; import org.springframework.data.repository.query.QueryMethod; -import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider; import org.springframework.data.repository.query.RepositoryQuery; +import org.springframework.data.repository.query.ValueExpressionDelegate; import org.springframework.data.repository.query.parser.AbstractQueryCreator; import org.springframework.lang.Nullable; import org.springframework.util.Assert; @@ -222,9 +222,9 @@ protected Class getRepositoryBaseClass(RepositoryMetadata metadata) { @Override protected Optional getQueryLookupStrategy(@Nullable Key key, // - QueryMethodEvaluationContextProvider evaluationContextProvider // + ValueExpressionDelegate valueExpressionDelegate // ) { - return Optional.of(new RediSearchQueryLookupStrategy(evaluationContextProvider, // + return Optional.of(new RediSearchQueryLookupStrategy(valueExpressionDelegate, // this.keyValueOperations, // this.rmo, // this.indexer, // @@ -237,7 +237,7 @@ protected Optional getQueryLookupStrategy(@Nullable Key key } private record RediSearchQueryLookupStrategy( // - QueryMethodEvaluationContextProvider evaluationContextProvider, // + ValueExpressionDelegate valueExpressionDelegate, // KeyValueOperations keyValueOperations, // RedisModulesOperations rmo, // RediSearchIndexer indexer, // @@ -247,12 +247,12 @@ private record RediSearchQueryLookupStrategy( // GsonBuilder gsonBuilder) implements QueryLookupStrategy { /** - * @param evaluationContextProvider + * @param valueExpressionDelegate * @param keyValueOperations * @param queryCreator */ private RediSearchQueryLookupStrategy { - Assert.notNull(evaluationContextProvider, "EvaluationContextProvider must not be null!"); + Assert.notNull(valueExpressionDelegate, "ValueExpressionDelegate must not be null!"); Assert.notNull(keyValueOperations, "KeyValueOperations must not be null!"); Assert.notNull(rmo, "RedisModulesOperations must not be null!"); Assert.notNull(indexer, "RediSearchIndexer must not be null!"); @@ -288,7 +288,7 @@ public RepositoryQuery resolveQuery( // QueryMethod.class, // RepositoryMetadata.class, // RediSearchIndexer.class, // - QueryMethodEvaluationContextProvider.class, // + ValueExpressionDelegate.class, // KeyValueOperations.class, // RedisModulesOperations.class, // Class.class, // @@ -296,7 +296,7 @@ public RepositoryQuery resolveQuery( // RedisOMProperties.class); Assert.state(constructor != null, String.format( - "Constructor %s(QueryMethod, RepositoryMetadata, RediSearchIndexer, EvaluationContextProvider, KeyValueOperations, RedisModulesOperations, Class, GsonBuilder, RedisOMSpringProperties) not available!", + "Constructor %s(QueryMethod, RepositoryMetadata, RediSearchIndexer, ValueExpressionDelegate, KeyValueOperations, RedisModulesOperations, Class, GsonBuilder, RedisOMSpringProperties) not available!", ClassUtils.getShortName(this.repositoryQueryType))); return BeanUtils.instantiateClass( // @@ -304,7 +304,7 @@ public RepositoryQuery resolveQuery( // queryMethod, // metadata, // indexer, // - evaluationContextProvider, // + valueExpressionDelegate, // this.keyValueOperations, // this.rmo, // this.queryCreator, // diff --git a/redis-om-spring/src/main/java/com/redis/om/spring/repository/support/RedisEnhancedRepositoryFactory.java b/redis-om-spring/src/main/java/com/redis/om/spring/repository/support/RedisEnhancedRepositoryFactory.java index 9adaff16..1a45e61e 100644 --- a/redis-om-spring/src/main/java/com/redis/om/spring/repository/support/RedisEnhancedRepositoryFactory.java +++ b/redis-om-spring/src/main/java/com/redis/om/spring/repository/support/RedisEnhancedRepositoryFactory.java @@ -24,8 +24,8 @@ import org.springframework.data.repository.query.QueryLookupStrategy; import org.springframework.data.repository.query.QueryLookupStrategy.Key; import org.springframework.data.repository.query.QueryMethod; -import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider; import org.springframework.data.repository.query.RepositoryQuery; +import org.springframework.data.repository.query.ValueExpressionDelegate; import org.springframework.data.repository.query.parser.AbstractQueryCreator; import org.springframework.lang.Nullable; import org.springframework.util.Assert; @@ -223,8 +223,8 @@ protected Class getRepositoryBaseClass(RepositoryMetadata metadata) { * org.springframework.data.repository.query.EvaluationContextProvider) */ @Override protected Optional getQueryLookupStrategy(@Nullable Key key, - QueryMethodEvaluationContextProvider evaluationContextProvider) { - return Optional.of(new RedisEnhancedQueryLookupStrategy(key, evaluationContextProvider, this.keyValueOperations, // + ValueExpressionDelegate valueExpressionDelegate) { + return Optional.of(new RedisEnhancedQueryLookupStrategy(key, valueExpressionDelegate, this.keyValueOperations, // this.redisOperations, // this.rmo, // this.indexer, // @@ -240,7 +240,7 @@ protected Optional getQueryLookupStrategy(@Nullable Key key */ private static class RedisEnhancedQueryLookupStrategy implements QueryLookupStrategy { - private final QueryMethodEvaluationContextProvider evaluationContextProvider; + private final ValueExpressionDelegate valueExpressionDelegate; private final KeyValueOperations keyValueOperations; private final RedisModulesOperations rmo; private final RedisOperations redisOperations; @@ -252,14 +252,14 @@ private static class RedisEnhancedQueryLookupStrategy implements QueryLookupStra /** * @param key - * @param evaluationContextProvider + * @param valueExpressionDelegate * @param keyValueOperations * @param queryCreator * @since 1.1 */ public RedisEnhancedQueryLookupStrategy( // @Nullable Key key, // - QueryMethodEvaluationContextProvider evaluationContextProvider, // + ValueExpressionDelegate valueExpressionDelegate, // KeyValueOperations keyValueOperations, // RedisOperations redisOperations, // RedisModulesOperations rmo, // @@ -268,7 +268,7 @@ public RedisEnhancedQueryLookupStrategy( // Class> queryCreator, // Class repositoryQueryType) { - Assert.notNull(evaluationContextProvider, "EvaluationContextProvider must not be null!"); + Assert.notNull(valueExpressionDelegate, "ValueExpressionDelegate must not be null!"); Assert.notNull(keyValueOperations, "KeyValueOperations must not be null!"); Assert.notNull(redisOperations, "RedisOperations must not be null!"); Assert.notNull(rmo, "RedisModulesOperations must not be null!"); @@ -276,7 +276,7 @@ public RedisEnhancedQueryLookupStrategy( // Assert.notNull(queryCreator, "Query creator type must not be null!"); Assert.notNull(repositoryQueryType, "RepositoryQueryType type must not be null!"); - this.evaluationContextProvider = evaluationContextProvider; + this.valueExpressionDelegate = valueExpressionDelegate; this.keyValueOperations = keyValueOperations; this.redisOperations = redisOperations; this.rmo = rmo; @@ -313,7 +313,7 @@ public RepositoryQuery resolveQuery( // QueryMethod.class, // RepositoryMetadata.class, // RediSearchIndexer.class, // - QueryMethodEvaluationContextProvider.class, // + ValueExpressionDelegate.class, // KeyValueOperations.class, // RedisOperations.class, // RedisModulesOperations.class, // @@ -321,14 +321,14 @@ public RepositoryQuery resolveQuery( // RedisOMProperties.class); Assert.state(constructor != null, String.format( - "Constructor %s(QueryMethod, RepositoryMetadata, RediSearchIndexer, EvaluationContextProvider, KeyValueOperations, RedisOperations, RedisModulesOperations, Class, RedisOMSpringProperties) not available!", + "Constructor %s(QueryMethod, RepositoryMetadata, RediSearchIndexer, ValueExpressionDelegate, KeyValueOperations, RedisOperations, RedisModulesOperations, Class, RedisOMSpringProperties) not available!", ClassUtils.getShortName(this.repositoryQueryType))); return BeanUtils.instantiateClass(constructor, // queryMethod, // metadata, // indexer, // - evaluationContextProvider, // + valueExpressionDelegate, // this.keyValueOperations, // this.redisOperations, // this.rmo, // diff --git a/tests/src/test/java/com/redis/om/spring/fixtures/hash/repository/MyHashQueriesImpl.java b/tests/src/test/java/com/redis/om/spring/fixtures/hash/repository/MyHashQueriesImpl.java index 21498580..c78c649e 100644 --- a/tests/src/test/java/com/redis/om/spring/fixtures/hash/repository/MyHashQueriesImpl.java +++ b/tests/src/test/java/com/redis/om/spring/fixtures/hash/repository/MyHashQueriesImpl.java @@ -26,7 +26,7 @@ public class MyHashQueriesImpl implements MyHashQueries { @Override public Optional findByTitle(String title) { SearchOperations ops = modulesOperations.opsForSearch(MyHash.class.getName() + "Idx"); - SearchResult result = ops.search(new Query("@title:'" + title + "'")); + SearchResult result = ops.search(new Query("@title:\"" + title + "\"")); if (result.getTotalResults() > 0) { Document doc = result.getDocuments().get(0); return Optional.of(ObjectUtils.documentToEntity(doc, MyHash.class, converter));