Skip to content

Commit a4c7654

Browse files
committed
fix: optimize existsById() to use Redis EXISTS command instead of data retrieval (#657)
The existsById() method in both RedisDocumentRepository and RedisEnhancedRepository was inefficiently retrieving entire data structures when only existence validation was needed. This optimization replaces the inherited Spring Data behavior with direct Redis EXISTS commands, providing significant performance gains. Changes: - Override existsById() in SimpleRedisDocumentRepository and SimpleRedisEnhancedRepository - Use connection.keyCommands().exists() for O(1) existence checks - Properly handle composite IDs by implementing getKeyForId() helper methods - Apply ID filters and maintain full backward compatibility Performance impact: - Documents: existsById() → findById() → JSON.GET → full document deserialization - Hashes: existsById() → findById() → HGETALL → full hash retrieval - After: existsById() → EXISTS → boolean result only - Eliminates unnecessary network transfer and CPU overhead for existence checks
1 parent 3a1a44e commit a4c7654

File tree

2 files changed

+90
-0
lines changed

2 files changed

+90
-0
lines changed

redis-om-spring/src/main/java/com/redis/om/spring/repository/support/SimpleRedisDocumentRepository.java

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
import org.springframework.data.keyvalue.core.KeyValueOperations;
3333
import org.springframework.data.keyvalue.core.mapping.KeyValuePersistentEntity;
3434
import org.springframework.data.keyvalue.repository.support.SimpleKeyValueRepository;
35+
import org.springframework.data.redis.core.RedisCallback;
3536
import org.springframework.data.redis.core.RedisTemplate;
3637
import org.springframework.data.redis.core.TimeToLive;
3738
import org.springframework.data.redis.core.convert.KeyspaceConfiguration;
@@ -722,6 +723,50 @@ public <S extends T> boolean exists(Example<S> example) {
722723
return count(example) > 0;
723724
}
724725

726+
@Override
727+
public boolean existsById(ID id) {
728+
Assert.notNull(id, "The given id must not be null");
729+
730+
// Use direct Jedis EXISTS command for optimal performance
731+
// Construct key properly for composite IDs
732+
String fullKey = getKeyForId(id);
733+
734+
return Boolean.TRUE.equals(modulesOperations.template().execute((RedisCallback<Boolean>) connection -> connection
735+
.keyCommands().exists(fullKey.getBytes())));
736+
}
737+
738+
private String getKeyForId(Object id) {
739+
// Get the mapping context's entity info
740+
RedisEnhancedPersistentEntity<?> persistentEntity = (RedisEnhancedPersistentEntity<?>) mappingContext
741+
.getRequiredPersistentEntity(metadata.getJavaType());
742+
743+
String stringId;
744+
745+
// Handle composite IDs
746+
if (persistentEntity.isIdClassComposite()) {
747+
BeanWrapper wrapper = new DirectFieldAccessFallbackBeanWrapper(id);
748+
List<String> idParts = new ArrayList<>();
749+
for (RedisPersistentProperty idProperty : persistentEntity.getIdProperties()) {
750+
Object propertyValue = wrapper.getPropertyValue(idProperty.getName());
751+
if (propertyValue != null) {
752+
idParts.add(propertyValue.toString());
753+
}
754+
}
755+
stringId = String.join(":", idParts);
756+
} else {
757+
stringId = mappingConverter.getConversionService().convert(id, String.class);
758+
}
759+
760+
// Apply ID filters if they exist
761+
var maybeIdentifierFilter = indexer.getIdentifierFilterFor(metadata.getJavaType());
762+
if (maybeIdentifierFilter.isPresent()) {
763+
IdentifierFilter<String> filter = (IdentifierFilter<String>) maybeIdentifierFilter.get();
764+
stringId = filter.filter(stringId);
765+
}
766+
767+
return getKeyspace() + stringId;
768+
}
769+
725770
// -------------------------------------------------------------------------
726771
// Query By Example Fluent API - QueryByExampleExecutor
727772
// -------------------------------------------------------------------------

redis-om-spring/src/main/java/com/redis/om/spring/repository/support/SimpleRedisEnhancedRepository.java

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import org.springframework.data.keyvalue.core.mapping.KeyValuePersistentEntity;
2323
import org.springframework.data.keyvalue.repository.support.SimpleKeyValueRepository;
2424
import org.springframework.data.redis.core.PartialUpdate;
25+
import org.springframework.data.redis.core.RedisCallback;
2526
import org.springframework.data.redis.core.RedisTemplate;
2627
import org.springframework.data.redis.core.convert.RedisData;
2728
import org.springframework.data.redis.core.convert.ReferenceResolverImpl;
@@ -596,6 +597,50 @@ public <S extends T> boolean exists(Example<S> example) {
596597
return count(example) > 0;
597598
}
598599

600+
@Override
601+
public boolean existsById(ID id) {
602+
Assert.notNull(id, "The given id must not be null");
603+
604+
// Use direct Jedis EXISTS command for optimal performance
605+
// Construct key properly for composite IDs
606+
String fullKey = getKeyForId(id);
607+
608+
return Boolean.TRUE.equals(modulesOperations.template().execute((RedisCallback<Boolean>) connection -> connection
609+
.keyCommands().exists(fullKey.getBytes())));
610+
}
611+
612+
private String getKeyForId(Object id) {
613+
// Get the mapping context's entity info
614+
RedisEnhancedPersistentEntity<?> persistentEntity = (RedisEnhancedPersistentEntity<?>) mappingConverter
615+
.getMappingContext().getRequiredPersistentEntity(metadata.getJavaType());
616+
617+
String stringId;
618+
619+
// Handle composite IDs
620+
if (persistentEntity.isIdClassComposite()) {
621+
BeanWrapper wrapper = new DirectFieldAccessFallbackBeanWrapper(id);
622+
List<String> idParts = new ArrayList<>();
623+
for (RedisPersistentProperty idProperty : persistentEntity.getIdProperties()) {
624+
Object propertyValue = wrapper.getPropertyValue(idProperty.getName());
625+
if (propertyValue != null) {
626+
idParts.add(propertyValue.toString());
627+
}
628+
}
629+
stringId = String.join(":", idParts);
630+
} else {
631+
stringId = mappingConverter.getConversionService().convert(id, String.class);
632+
}
633+
634+
// Apply ID filters if they exist
635+
var maybeIdentifierFilter = indexer.getIdentifierFilterFor(metadata.getJavaType());
636+
if (maybeIdentifierFilter.isPresent()) {
637+
IdentifierFilter<String> filter = (IdentifierFilter<String>) maybeIdentifierFilter.get();
638+
stringId = filter.filter(stringId);
639+
}
640+
641+
return getKeyspace() + stringId;
642+
}
643+
599644
// -------------------------------------------------------------------------
600645
// Query By Example Fluent API - QueryByExampleExecutor
601646
// -------------------------------------------------------------------------

0 commit comments

Comments
 (0)