Skip to content

Commit 51c17ca

Browse files
DATAREDIS-471 - Allow removal of complex types.
Allow removing complex types with all belonging nested structures and make sure existing indexes are updated correctly by removing the entity id from the according sets.
1 parent b517a38 commit 51c17ca

File tree

2 files changed

+198
-41
lines changed

2 files changed

+198
-41
lines changed

src/main/java/org/springframework/data/redis/core/RedisKeyValueAdapter.java

Lines changed: 83 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import java.io.Serializable;
1919
import java.util.ArrayList;
2020
import java.util.Collection;
21+
import java.util.LinkedHashSet;
2122
import java.util.List;
2223
import java.util.Map;
2324
import java.util.Map.Entry;
@@ -411,58 +412,28 @@ public void update(final PartialUpdate<?> update) {
411412
@Override
412413
public Void doInRedis(RedisConnection connection) throws DataAccessException {
413414

414-
List<byte[]> pathsToRemove = new ArrayList<byte[]>(update.getPropertyUpdates().size());
415+
RedisUpdateObject redisUpdateObject = new RedisUpdateObject(redisKey, keyspace, id);
415416

416417
for (PropertyUpdate pUpdate : update.getPropertyUpdates()) {
417418

418419
String propertyPath = pUpdate.getPropertyPath();
419420

420-
if (UpdateCommand.DEL.equals(pUpdate.getCmd())) {
421-
422-
byte[] existingValue = connection.hGet(redisKey, toBytes(propertyPath));
423-
pathsToRemove.add(toBytes(propertyPath));
424-
425-
byte[] existingValueIndexKey = existingValue != null
426-
? ByteUtils.concatAll(toBytes(keyspace), (":" + propertyPath).getBytes(), ":".getBytes(), existingValue)
427-
: null;
428-
429-
if (existingValue != null) {
430-
431-
if (connection.exists(existingValueIndexKey)) {
432-
connection.sRem(existingValueIndexKey, toBytes(id));
433-
}
434-
}
435-
}
436-
437-
if (pUpdate.getValue() instanceof Collection || pUpdate.getValue() instanceof Map
421+
if (UpdateCommand.DEL.equals(pUpdate.getCmd()) || pUpdate.getValue() instanceof Collection
422+
|| pUpdate.getValue() instanceof Map
438423
|| (pUpdate.getValue() != null && pUpdate.getValue().getClass().isArray()) || (pUpdate.getValue() != null
439424
&& !converter.getConversionService().canConvert(pUpdate.getValue().getClass(), byte[].class))) {
440425

441-
Set<byte[]> existingFields = connection.hKeys(redisKey);
442-
443-
for (byte[] hkey : existingFields) {
444-
445-
if (asString(hkey).startsWith(pUpdate.getPropertyPath() + ".")) {
446-
pathsToRemove.add(hkey);
447-
448-
byte[] existingValue = connection.hGet(redisKey, toBytes(hkey));
449-
byte[] existingValueIndexKey = existingValue != null ? ByteUtils.concatAll(toBytes(keyspace),
450-
(":" + propertyPath).getBytes(), ":".getBytes(), existingValue) : null;
451-
452-
if (existingValue != null) {
453-
454-
if (connection.exists(existingValueIndexKey)) {
455-
connection.sRem(existingValueIndexKey, toBytes(id));
456-
}
457-
}
458-
}
459-
}
460-
426+
redisUpdateObject = fetchDeletePathsFromHashAndUpdateIndex(redisUpdateObject, propertyPath, connection);
461427
}
462428
}
463429

464-
if (!pathsToRemove.isEmpty()) {
465-
connection.hDel(redisKey, pathsToRemove.toArray(new byte[pathsToRemove.size()][]));
430+
if (!redisUpdateObject.fieldsToRemove.isEmpty()) {
431+
connection.hDel(redisKey,
432+
redisUpdateObject.fieldsToRemove.toArray(new byte[redisUpdateObject.fieldsToRemove.size()][]));
433+
}
434+
435+
for (byte[] index : redisUpdateObject.indexesToUpdate) {
436+
connection.sRem(index, toBytes(redisUpdateObject.targetId));
466437
}
467438

468439
if (!rdo.getBucket().isEmpty()) {
@@ -497,6 +468,48 @@ public Void doInRedis(RedisConnection connection) throws DataAccessException {
497468
});
498469
}
499470

471+
private RedisUpdateObject fetchDeletePathsFromHashAndUpdateIndex(RedisUpdateObject redisUpdateObject, String path,
472+
RedisConnection connection) {
473+
474+
redisUpdateObject.addFieldToRemove(toBytes(path));
475+
byte[] value = connection.hGet(redisUpdateObject.targetKey, toBytes(path));
476+
477+
if (value != null && value.length > 0) {
478+
479+
byte[] existingValueIndexKey = value != null
480+
? ByteUtils.concatAll(toBytes(redisUpdateObject.keyspace), toBytes((":" + path)), toBytes(":"), value) : null;
481+
482+
if (connection.exists(existingValueIndexKey)) {
483+
redisUpdateObject.addIndexToUpdate(existingValueIndexKey);
484+
}
485+
return redisUpdateObject;
486+
}
487+
488+
Set<byte[]> existingFields = connection.hKeys(redisUpdateObject.targetKey);
489+
490+
for (byte[] field : existingFields) {
491+
492+
if (asString(field).startsWith(path + ".")) {
493+
494+
redisUpdateObject.addFieldToRemove(field);
495+
value = connection.hGet(redisUpdateObject.targetKey, toBytes(field));
496+
497+
if (value != null) {
498+
499+
byte[] existingValueIndexKey = value != null
500+
? ByteUtils.concatAll(toBytes(redisUpdateObject.keyspace), toBytes(":"), field, toBytes(":"), value)
501+
: null;
502+
503+
if (connection.exists(existingValueIndexKey)) {
504+
redisUpdateObject.addIndexToUpdate(existingValueIndexKey);
505+
}
506+
}
507+
}
508+
}
509+
510+
return redisUpdateObject;
511+
}
512+
500513
/**
501514
* Execute {@link RedisCallback} via underlying {@link RedisOperations}.
502515
*
@@ -684,4 +697,33 @@ private boolean isKeyExpirationMessage(Message message) {
684697
}
685698
}
686699

700+
/**
701+
* Container holding update information like fields to remove from the Redis Hash.
702+
*
703+
* @author Christoph Strobl
704+
*/
705+
private static class RedisUpdateObject {
706+
707+
private final String keyspace;
708+
private final Object targetId;
709+
private final byte[] targetKey;
710+
711+
private Set<byte[]> fieldsToRemove = new LinkedHashSet<byte[]>();
712+
private Set<byte[]> indexesToUpdate = new LinkedHashSet<byte[]>();
713+
714+
RedisUpdateObject(byte[] targetKey, String keyspace, Object targetId) {
715+
716+
this.targetKey = targetKey;
717+
this.keyspace = keyspace;
718+
this.targetId = targetId;
719+
}
720+
721+
void addFieldToRemove(byte[] field) {
722+
fieldsToRemove.add(field);
723+
}
724+
725+
void addIndexToUpdate(byte[] indexName) {
726+
indexesToUpdate.add(indexName);
727+
}
728+
}
687729
}

src/test/java/org/springframework/data/redis/core/RedisKeyValueAdapterTests.java

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import static org.junit.Assert.*;
2323

2424
import java.util.Arrays;
25+
import java.util.Collections;
2526
import java.util.Date;
2627
import java.util.LinkedHashMap;
2728
import java.util.List;
@@ -358,6 +359,120 @@ public void updateShouldAlterIndexDataOnNestedObjectPathCorrectly() {
358359
assertThat(template.hasKey("persons:address.country:tear"), is(true));
359360
}
360361

362+
/**
363+
* @see DATAREDIS-471
364+
*/
365+
@Test
366+
public void updateShouldRemoveComplexObjectCorrectly() {
367+
368+
Person rand = new Person();
369+
rand.address = new Address();
370+
rand.address.country = "andor";
371+
rand.address.city = "emond's field";
372+
373+
adapter.put("1", rand, "persons");
374+
375+
PartialUpdate<Person> update = new PartialUpdate<Person>("1", Person.class) //
376+
.del("address");
377+
378+
adapter.update(update);
379+
380+
assertThat(template.opsForHash().hasKey("persons:1", "address.country"), is(false));
381+
assertThat(template.opsForHash().hasKey("persons:1", "address.city"), is(false));
382+
assertThat(template.opsForSet().isMember("persons:address.country:andor", "1"), is(false));
383+
}
384+
385+
/**
386+
* @see DATAREDIS-471
387+
*/
388+
@Test
389+
public void updateShouldRemoveSimpleListValuesCorrectly() {
390+
391+
Person rand = new Person();
392+
rand.nicknames = Arrays.asList("lews therin", "dragon reborn");
393+
394+
adapter.put("1", rand, "persons");
395+
396+
PartialUpdate<Person> update = new PartialUpdate<Person>("1", Person.class) //
397+
.del("nicknames");
398+
399+
adapter.update(update);
400+
401+
assertThat(template.opsForHash().hasKey("persons:1", "nicknames.[0]"), is(false));
402+
assertThat(template.opsForHash().hasKey("persons:1", "nicknames.[1]"), is(false));
403+
}
404+
405+
/**
406+
* @see DATAREDIS-471
407+
*/
408+
@Test
409+
public void updateShouldRemoveComplexListValuesCorrectly() {
410+
411+
Person mat = new Person();
412+
mat.firstname = "mat";
413+
mat.nicknames = Collections.singletonList("prince of ravens");
414+
415+
Person perrin = new Person();
416+
perrin.firstname = "mat";
417+
perrin.nicknames = Collections.singletonList("lord of the two rivers");
418+
419+
Person rand = new Person();
420+
rand.coworkers = Arrays.asList(mat, perrin);
421+
422+
adapter.put("1", rand, "persons");
423+
424+
PartialUpdate<Person> update = new PartialUpdate<Person>("1", Person.class) //
425+
.del("coworkers");
426+
427+
adapter.update(update);
428+
429+
assertThat(template.opsForHash().hasKey("persons:1", "coworkers.[0].firstname"), is(false));
430+
assertThat(template.opsForHash().hasKey("persons:1", "coworkers.[0].nicknames.[0]"), is(false));
431+
assertThat(template.opsForHash().hasKey("persons:1", "coworkers.[1].firstname"), is(false));
432+
assertThat(template.opsForHash().hasKey("persons:1", "coworkers.[1].nicknames.[0]"), is(false));
433+
}
434+
435+
/**
436+
* @see DATAREDIS-471
437+
*/
438+
@Test
439+
public void updateShouldRemoveSimpleMapValuesCorrectly() {
440+
441+
Person rand = new Person();
442+
rand.physicalAttributes = Collections.singletonMap("eye-color", "grey");
443+
444+
adapter.put("1", rand, "persons");
445+
446+
PartialUpdate<Person> update = new PartialUpdate<Person>("1", Person.class) //
447+
.del("physicalAttributes");
448+
449+
adapter.update(update);
450+
451+
assertThat(template.opsForHash().hasKey("persons:1", "physicalAttributes.[eye-color]"), is(false));
452+
}
453+
454+
/**
455+
* @see DATAREDIS-471
456+
*/
457+
@Test
458+
public void updateShouldRemoveComplexMapValuesCorrectly() {
459+
460+
Person tam = new Person();
461+
tam.firstname = "tam";
462+
463+
Person rand = new Person();
464+
rand.relatives = Collections.singletonMap("stepfather", tam);
465+
466+
adapter.put("1", rand, "persons");
467+
468+
PartialUpdate<Person> update = new PartialUpdate<Person>("1", Person.class) //
469+
.del("relatives");
470+
471+
adapter.update(update);
472+
473+
assertThat(template.opsForHash().hasKey("persons:1", "relatives.[stepfather].firstname"), is(false));
474+
}
475+
361476
@KeySpace("persons")
362477
static class Person {
363478

0 commit comments

Comments
 (0)