Skip to content

Commit 48cfd02

Browse files
authored
feature: composite PKs , QBE for EntityStreams Test (#534)
* feature: implement support for composite IDs using @IdClass * release 0.9.8-SNAPSHOT * dependencies: move up to Spring Boot/SDR 3.3.7 * feature: add repository methods to get Redis key from an entity (getKeyFor) * test: add tests for QBE with EntityStreams * test: add test String interpolation of params in EntityStream aggregations
1 parent 0448262 commit 48cfd02

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+2073
-94
lines changed

README.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -408,7 +408,7 @@ Iterable<MyDoc> allMatches = repository.findAll(example);
408408
<version>${version}</version>
409409
</dependency>
410410
```
411-
> Check below if using Redis OM Spring version greater than `0.9.7`
411+
> Check below if using Redis OM Spring version greater than `0.9.8-SNAPSHOT`
412412
> ⚠️ Redis OM Spring versions greater than `v0.9.1` require the addition
413413
of the [**Spring Milestone Repository**](https://repo.spring.io/milestone) to account
414414
for the recent integration with the [**Spring AI**](https://docs.spring.io/spring-ai/reference/) project. When Spring AI `v1.0.0` is
@@ -430,10 +430,10 @@ repositories {
430430
}
431431
```
432432

433-
> ⚠️ Redis OM Spring versions greater than `v0.9.7` made OPTIONAL the addition
433+
> ⚠️ Redis OM Spring versions greater than `v0.9.8-SNAPSHOT` made OPTIONAL the addition
434434
of the [**Spring Milestone Repository**](https://repo.spring.io/milestone) to account
435435
for the recent integration with the [**Spring AI**](https://docs.spring.io/spring-ai/reference/) project. When Spring AI `v1.0.0` is
436-
released we will drop this requirement. If you want to opt-in for Vector Similarity Search features, you need to manually add the dependencies below. Check the VSS demo for
436+
released we will drop this requirement. If you want to opt-in for Vector Similarity Search features, you need to manually add the dependencies below. Check the VSS demo for
437437
for a full example.
438438

439439
```xml
@@ -535,7 +535,7 @@ inherited from the parent poms):
535535
<path>
536536
<groupId>com.redis.om</groupId>
537537
<artifactId>redis-om-spring</artifactId>
538-
<version>0.9.7</version>
538+
<version>0.9.8-SNAPSHOT</version>
539539
</path>
540540
</annotationProcessorPaths>
541541
</configuration>
@@ -582,7 +582,7 @@ repositories {
582582

583583
```groovy
584584
ext {
585-
redisOmVersion = '0.9.7'
585+
redisOmVersion = '0.9.8-SNAPSHOT'
586586
}
587587
588588
dependencies {

demos/roms-documents/pom.xml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
<parent>
77
<groupId>org.springframework.boot</groupId>
88
<artifactId>spring-boot-starter-parent</artifactId>
9-
<version>3.3.2</version>
9+
<version>3.3.7</version>
1010
<relativePath/> <!-- lookup parent from repository -->
1111
</parent>
1212

@@ -31,7 +31,7 @@
3131
<dependency>
3232
<groupId>com.redis.om</groupId>
3333
<artifactId>redis-om-spring</artifactId>
34-
<version>0.9.7</version>
34+
<version>0.9.8-SNAPSHOT</version>
3535
</dependency>
3636
<dependency>
3737
<groupId>org.springframework.boot</groupId>
@@ -141,7 +141,7 @@
141141
<path>
142142
<groupId>com.redis.om</groupId>
143143
<artifactId>redis-om-spring</artifactId>
144-
<version>0.9.7</version>
144+
<version>0.9.8-SNAPSHOT</version>
145145
</path>
146146
</annotationProcessorPaths>
147147
</configuration>

demos/roms-hashes/pom.xml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
<parent>
77
<groupId>org.springframework.boot</groupId>
88
<artifactId>spring-boot-starter-parent</artifactId>
9-
<version>3.3.2</version>
9+
<version>3.3.7</version>
1010
<relativePath/> <!-- lookup parent from repository -->
1111
</parent>
1212

@@ -30,7 +30,7 @@
3030
<dependency>
3131
<groupId>com.redis.om</groupId>
3232
<artifactId>redis-om-spring</artifactId>
33-
<version>0.9.7</version>
33+
<version>0.9.8-SNAPSHOT</version>
3434
</dependency>
3535
<dependency>
3636
<groupId>org.springframework.boot</groupId>

demos/roms-permits/pom.xml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
<parent>
77
<groupId>org.springframework.boot</groupId>
88
<artifactId>spring-boot-starter-parent</artifactId>
9-
<version>3.3.2</version>
9+
<version>3.3.7</version>
1010
<relativePath/>
1111
<!-- lookup parent from repository -->
1212
</parent>
@@ -34,7 +34,7 @@
3434
<dependency>
3535
<groupId>com.redis.om</groupId>
3636
<artifactId>redis-om-spring</artifactId>
37-
<version>0.9.7</version>
37+
<version>0.9.8-SNAPSHOT</version>
3838
</dependency>
3939

4040
<dependency>

demos/roms-vss/pom.xml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
<parent>
77
<groupId>org.springframework.boot</groupId>
88
<artifactId>spring-boot-starter-parent</artifactId>
9-
<version>3.3.2</version>
9+
<version>3.3.7</version>
1010
<relativePath/> <!-- lookup parent from repository -->
1111
</parent>
1212

@@ -24,7 +24,7 @@
2424
<maven.test.source>21</maven.test.source>
2525
<maven.test.target>21</maven.test.target>
2626
<maven.deploy.skip>true</maven.deploy.skip>
27-
<spring.version>3.3.2</spring.version>
27+
<spring.version>3.3.7</spring.version>
2828
<spring-ai.version>1.0.0-M2</spring-ai.version>
2929
<djl.starter.version>0.26</djl.starter.version>
3030
<djl.version>0.27.0</djl.version>
@@ -54,7 +54,7 @@
5454
<dependency>
5555
<groupId>com.redis.om</groupId>
5656
<artifactId>redis-om-spring</artifactId>
57-
<version>0.9.7</version>
57+
<version>0.9.8-SNAPSHOT</version>
5858
</dependency>
5959
<dependency>
6060
<groupId>org.springframework.boot</groupId>

pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
<modelVersion>4.0.0</modelVersion>
77
<groupId>com.redis.om</groupId>
88
<artifactId>redis-om-spring-parent</artifactId>
9-
<version>0.9.7</version>
9+
<version>0.9.8-SNAPSHOT</version>
1010
<name>redis-om-spring-parent</name>
1111
<packaging>pom</packaging>
1212

redis-om-spring/pom.xml

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
<groupId>com.redis.om</groupId>
99
<artifactId>redis-om-spring</artifactId>
10-
<version>0.9.7</version>
10+
<version>0.9.8-SNAPSHOT</version>
1111
<packaging>jar</packaging>
1212

1313
<name>redis-om-spring</name>
@@ -61,8 +61,8 @@
6161
<maven.test.source>21</maven.test.source>
6262
<maven.test.target>21</maven.test.target>
6363
<java.version>21</java.version>
64-
<spring.version>3.3.2</spring.version>
65-
<sdr.version>3.3.2</sdr.version>
64+
<spring.version>3.3.7</spring.version>
65+
<sdr.version>3.3.7</sdr.version>
6666
<jedis.version>5.0.2</jedis.version>
6767
<cdi>2.0-PFD</cdi>
6868
<auto-service.version>1.1.1</auto-service.version>
@@ -170,6 +170,11 @@
170170
<groupId>org.hibernate.validator</groupId>
171171
<artifactId>hibernate-validator</artifactId>
172172
</dependency>
173+
<dependency>
174+
<groupId>jakarta.persistence</groupId>
175+
<artifactId>jakarta.persistence-api</artifactId>
176+
<version>3.2.0</version>
177+
</dependency>
173178
<!-- Spring AI begin -->
174179
<dependency>
175180
<groupId>org.springframework.ai</groupId>

redis-om-spring/src/main/java/com/redis/om/spring/RedisEnhancedKeyValueAdapter.java

Lines changed: 73 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,15 @@
55
import com.redis.om.spring.convert.RedisOMCustomConversions;
66
import com.redis.om.spring.id.IdentifierFilter;
77
import com.redis.om.spring.indexing.RediSearchIndexer;
8+
import com.redis.om.spring.mapping.RedisEnhancedMappingContext;
9+
import com.redis.om.spring.mapping.RedisEnhancedPersistentEntity;
810
import com.redis.om.spring.ops.RedisModulesOperations;
911
import com.redis.om.spring.ops.search.SearchOperations;
1012
import com.redis.om.spring.vectorize.Embedder;
13+
import jakarta.persistence.IdClass;
14+
import org.apache.commons.logging.Log;
15+
import org.apache.commons.logging.LogFactory;
16+
import org.springframework.beans.BeanWrapper;
1117
import org.springframework.data.convert.CustomConversions;
1218
import org.springframework.data.mapping.PersistentPropertyAccessor;
1319
import org.springframework.data.redis.connection.RedisConnection;
@@ -22,11 +28,11 @@
2228
import org.springframework.data.redis.core.mapping.RedisMappingContext;
2329
import org.springframework.data.redis.core.mapping.RedisPersistentEntity;
2430
import org.springframework.data.redis.core.mapping.RedisPersistentProperty;
31+
import org.springframework.data.util.DirectFieldAccessFallbackBeanWrapper;
2532
import org.springframework.lang.Nullable;
2633
import org.springframework.util.Assert;
2734
import org.springframework.util.CollectionUtils;
2835
import org.springframework.util.ObjectUtils;
29-
import redis.clients.jedis.commands.KeyCommands;
3036
import redis.clients.jedis.search.Query;
3137
import redis.clients.jedis.search.SearchResult;
3238

@@ -38,6 +44,7 @@
3844

3945
public class RedisEnhancedKeyValueAdapter extends RedisKeyValueAdapter {
4046

47+
private static final Log logger = LogFactory.getLog(RedisEnhancedKeyValueAdapter.class);
4148
private final RedisOperations<?, ?> redisOperations;
4249
private final RedisConverter converter;
4350
private final RedisModulesOperations<String> modulesOperations;
@@ -60,7 +67,7 @@ public RedisEnhancedKeyValueAdapter( //
6067
RediSearchIndexer indexer, //
6168
Embedder embedder, //
6269
RedisOMProperties redisOMProperties) {
63-
this(redisOps, rmo, new RedisMappingContext(), indexer, embedder, redisOMProperties);
70+
this(redisOps, rmo, new RedisEnhancedMappingContext(), indexer, embedder, redisOMProperties);
6471
}
6572

6673
/**
@@ -137,20 +144,14 @@ public Object put(Object id, Object item, String keyspace) {
137144
if (item instanceof RedisData redisData) {
138145
rdo = redisData;
139146
} else {
140-
String idAsString = converter.getConversionService().convert(id, String.class);
141-
if (idAsString == null) {
142-
idAsString = id.toString();
143-
}
147+
String idAsString = validateKeyForWriting(id, item);
144148
byte[] redisKey = createKey(sanitizeKeyspace(keyspace), idAsString);
145149
auditor.processEntity(redisKey, item);
146150
embedder.processEntity(item);
147151

148152
rdo = new RedisData();
149153
converter.write(item, rdo);
150-
}
151-
152-
if (rdo.getId() == null) {
153-
rdo.setId(converter.getConversionService().convert(id, String.class));
154+
rdo.setId(idAsString);
154155
}
155156

156157
redisOperations.executePipelined((RedisCallback<Object>) connection -> {
@@ -181,7 +182,7 @@ public Object put(Object id, Object item, String keyspace) {
181182
public <T> T get(Object id, String keyspace, Class<T> type) {
182183

183184
String stringId = asStringValue(id);
184-
String stringKeyspace = sanitizeKeyspace(asStringValue(keyspace));
185+
String stringKeyspace = sanitizeKeyspace(keyspace);
185186

186187
byte[] binId = createKey(stringKeyspace, stringId);
187188

@@ -291,11 +292,14 @@ public <T> List<T> getAllOf(String keyspace, Class<T> type, long offset, int row
291292
*/
292293
@Override
293294
public <T> T delete(Object id, String keyspace, Class<T> type) {
294-
T o = get(id, keyspace, type);
295+
String stringId = asStringValue(id);
296+
String stringKeyspace = sanitizeKeyspace(keyspace);
297+
298+
T o = get(stringId, stringKeyspace, type);
295299

296300
if (o != null) {
297301

298-
byte[] keyToDelete = createKey(sanitizeKeyspace(asStringValue(keyspace)), asStringValue(id));
302+
byte[] keyToDelete = createKey(stringKeyspace, stringId);
299303

300304
redisOperations.execute((RedisCallback<Void>) connection -> {
301305
connection.keyCommands().unlink(keyToDelete);
@@ -334,8 +338,8 @@ public long count(String keyspace) {
334338
*/
335339
@Override
336340
public boolean contains(Object id, String keyspace) {
337-
Boolean exists = redisOperations.execute(
338-
(RedisCallback<Boolean>) connection -> connection.keyCommands().exists(toBytes(getKey(keyspace, id))));
341+
Boolean exists = redisOperations.execute((RedisCallback<Boolean>) connection -> connection.keyCommands()
342+
.exists(toBytes(getKey(keyspace, asStringValue(id)))));
339343

340344
return exists != null && exists;
341345
}
@@ -348,8 +352,9 @@ public void update(PartialUpdate<?> update) {
348352

349353
String keyspace = sanitizeKeyspace(entity.getKeySpace());
350354
Object id = update.getId();
355+
String stringId = asStringValue(id);
351356

352-
byte[] redisKey = createKey(keyspace, converter.getConversionService().convert(id, String.class));
357+
byte[] redisKey = createKey(keyspace, stringId);
353358

354359
RedisData rdo = new RedisData();
355360
this.converter.write(update, rdo);
@@ -423,11 +428,60 @@ private RedisUpdateObject fetchDeletePathsFromHash(RedisUpdateObject redisUpdate
423428
private String asStringValue(Object value) {
424429
if (value instanceof String valueAsString) {
425430
return valueAsString;
431+
}
432+
433+
// For composite IDs used in @IdClass
434+
if (value != null) {
435+
// Get all persistent entities
436+
for (RedisPersistentEntity<?> entity : converter.getMappingContext().getPersistentEntities()) {
437+
// Find the entity that uses this ID class
438+
IdClass idClassAnn = entity.getType().getAnnotation(IdClass.class);
439+
if (idClassAnn != null && idClassAnn.value().equals(value.getClass())) {
440+
// Found the entity that uses this ID class
441+
BeanWrapper wrapper = new DirectFieldAccessFallbackBeanWrapper(value);
442+
RedisEnhancedPersistentEntity<?> enhancedEntity = (RedisEnhancedPersistentEntity<?>) entity;
443+
444+
// Build composite key from ID properties in order
445+
List<String> idParts = new ArrayList<>();
446+
for (RedisPersistentProperty idProperty : enhancedEntity.getIdProperties()) {
447+
Object propertyValue = wrapper.getPropertyValue(idProperty.getName());
448+
if (propertyValue != null) {
449+
idParts.add(propertyValue.toString());
450+
}
451+
}
452+
return String.join(":", idParts);
453+
}
454+
}
455+
}
456+
457+
return getConverter().getConversionService().convert(value, String.class);
458+
}
459+
460+
private String validateKeyForWriting(Object id, Object item) {
461+
// Get the mapping context's entity info
462+
RedisEnhancedPersistentEntity<?> entity = (RedisEnhancedPersistentEntity<?>) converter.getMappingContext()
463+
.getRequiredPersistentEntity(item.getClass());
464+
465+
// Handle composite IDs
466+
if (entity.isIdClassComposite()) {
467+
BeanWrapper wrapper = new DirectFieldAccessFallbackBeanWrapper(item);
468+
List<String> idParts = new ArrayList<>();
469+
470+
for (RedisPersistentProperty idProperty : entity.getIdProperties()) {
471+
Object propertyValue = wrapper.getPropertyValue(idProperty.getName());
472+
if (propertyValue != null) {
473+
idParts.add(propertyValue.toString());
474+
}
475+
}
476+
477+
return String.join(":", idParts);
426478
} else {
427-
return getConverter().getConversionService().convert(value, String.class);
479+
// Regular single ID handling
480+
return converter.getConversionService().convert(id, String.class);
428481
}
429482
}
430483

484+
431485
/**
432486
* Read back and set {@link TimeToLive} for the property.
433487
*
@@ -504,7 +558,8 @@ public byte[] createKey(String keyspace, String id) {
504558
IdentifierFilter<String> filter = (IdentifierFilter<String>) maybeIdentifierFilter.get();
505559
id = filter.filter(id);
506560
}
507-
return toBytes(keyspace + ":" + id);
561+
562+
return toBytes(keyspace.endsWith(":") ? keyspace + id : keyspace + ":" + id);
508563
}
509564

510565
/**

0 commit comments

Comments
 (0)