diff --git a/pom.xml b/pom.xml index e6d868411f..52a5d64880 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.springframework.data spring-data-redis - 1.8.0.BUILD-SNAPSHOT + 1.8.0.DATAREDIS-530-SNAPSHOT Spring Data Redis diff --git a/src/main/java/org/springframework/data/redis/core/IndexWriter.java b/src/main/java/org/springframework/data/redis/core/IndexWriter.java index 054ca9c27d..cc4ff0df58 100644 --- a/src/main/java/org/springframework/data/redis/core/IndexWriter.java +++ b/src/main/java/org/springframework/data/redis/core/IndexWriter.java @@ -76,6 +76,16 @@ public void createIndexes(Object key, Iterable indexValues) { * @param indexValues can be {@literal null}. */ public void updateIndexes(Object key, Iterable indexValues) { + createOrUpdateIndexes(key, indexValues, IndexWriteMode.PARTIAL_UPDATE); + } + + /** + * Updates indexes by first removing key from existing one and then persisting new index data. + * + * @param key must not be {@literal null}. + * @param indexValues can be {@literal null}. + */ + public void deleteAndUpdateIndexes(Object key, Iterable indexValues) { createOrUpdateIndexes(key, indexValues, IndexWriteMode.UPDATE); } @@ -96,7 +106,7 @@ private void createOrUpdateIndexes(Object key, Iterable indexValues removeKeyFromIndexes(data.getKeyspace(), binKey); } } - + } else if (ObjectUtils.nullSafeEquals(IndexWriteMode.PARTIAL_UPDATE, writeMode)) { removeKeyFromExistingIndexes(binKey, indexValues); } @@ -229,6 +239,6 @@ private byte[] toBytes(Object source) { */ private static enum IndexWriteMode { - CREATE, UPDATE + CREATE, UPDATE, PARTIAL_UPDATE } } diff --git a/src/main/java/org/springframework/data/redis/core/RedisKeyValueAdapter.java b/src/main/java/org/springframework/data/redis/core/RedisKeyValueAdapter.java index 70f5e6384f..5a1e221867 100644 --- a/src/main/java/org/springframework/data/redis/core/RedisKeyValueAdapter.java +++ b/src/main/java/org/springframework/data/redis/core/RedisKeyValueAdapter.java @@ -236,7 +236,7 @@ public Object doInRedis(RedisConnection connection) throws DataAccessException { if (isNew) { indexWriter.createIndexes(key, rdo.getIndexedData()); } else { - indexWriter.updateIndexes(key, rdo.getIndexedData()); + indexWriter.deleteAndUpdateIndexes(key, rdo.getIndexedData()); } return null; } diff --git a/src/main/java/org/springframework/data/redis/core/RedisKeyValueTemplate.java b/src/main/java/org/springframework/data/redis/core/RedisKeyValueTemplate.java index 8a1f86e615..a130114c36 100644 --- a/src/main/java/org/springframework/data/redis/core/RedisKeyValueTemplate.java +++ b/src/main/java/org/springframework/data/redis/core/RedisKeyValueTemplate.java @@ -23,7 +23,6 @@ import org.springframework.data.keyvalue.core.KeyValueAdapter; import org.springframework.data.keyvalue.core.KeyValueCallback; import org.springframework.data.keyvalue.core.KeyValueTemplate; -import org.springframework.data.redis.RedisSystemException; import org.springframework.data.redis.core.mapping.RedisMappingContext; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; @@ -135,6 +134,7 @@ public void update(Object objectToUpdate) { if (objectToUpdate instanceof PartialUpdate) { doPartialUpdate((PartialUpdate) objectToUpdate); + return; } super.update(objectToUpdate); diff --git a/src/test/java/org/springframework/data/redis/core/RedisKeyValueAdapterUnitTests.java b/src/test/java/org/springframework/data/redis/core/RedisKeyValueAdapterUnitTests.java index a91920af9c..8bdfbfe18a 100644 --- a/src/test/java/org/springframework/data/redis/core/RedisKeyValueAdapterUnitTests.java +++ b/src/test/java/org/springframework/data/redis/core/RedisKeyValueAdapterUnitTests.java @@ -19,7 +19,6 @@ import static org.hamcrest.CoreMatchers.*; import static org.junit.Assert.*; import static org.mockito.Matchers.*; -import static org.mockito.Matchers.any; import static org.mockito.Mockito.*; import static org.springframework.test.util.ReflectionTestUtils.*; @@ -98,6 +97,7 @@ public void destroyShouldNotDestroyConnectionFactory() throws Exception { /** * @see DATAREDIS-512 + * @see DATAREDIS-530 */ @Test public void putShouldRemoveExistingIndexValuesWhenUpdating() { @@ -105,13 +105,14 @@ public void putShouldRemoveExistingIndexValuesWhenUpdating() { RedisData rd = new RedisData(Bucket.newBucketFromStringMap(Collections.singletonMap("_id", "1"))); rd.addIndexedData(new SimpleIndexedPropertyValue("persons", "firstname", "rand")); - when(redisConnectionMock.keys(any(byte[].class))) + when(redisConnectionMock.sMembers(org.mockito.Matchers.any(byte[].class))) .thenReturn(new LinkedHashSet(Arrays.asList("persons:firstname:rand".getBytes()))); when(redisConnectionMock.del((byte[][]) anyVararg())).thenReturn(1L); adapter.put("1", rd, "persons"); - verify(redisConnectionMock, times(1)).sRem(any(byte[].class), any(byte[].class)); + verify(redisConnectionMock, times(1)).sRem(org.mockito.Matchers.any(byte[].class), + org.mockito.Matchers.any(byte[].class)); } /** @@ -123,20 +124,20 @@ public void putShouldNotTryToRemoveExistingIndexValuesWhenInsertingNew() { RedisData rd = new RedisData(Bucket.newBucketFromStringMap(Collections.singletonMap("_id", "1"))); rd.addIndexedData(new SimpleIndexedPropertyValue("persons", "firstname", "rand")); - when(redisConnectionMock.sMembers(any(byte[].class))) + when(redisConnectionMock.sMembers(org.mockito.Matchers.any(byte[].class))) .thenReturn(new LinkedHashSet(Arrays.asList("persons:firstname:rand".getBytes()))); when(redisConnectionMock.del((byte[][]) anyVararg())).thenReturn(0L); adapter.put("1", rd, "persons"); - verify(redisConnectionMock, never()).sRem(any(byte[].class), (byte[][]) anyVararg()); + verify(redisConnectionMock, never()).sRem(org.mockito.Matchers.any(byte[].class), (byte[][]) anyVararg()); } /** * @see DATAREDIS-491 */ @Test - public void shouldInitKeyExpirationListenerOnStartup() throws Exception{ + public void shouldInitKeyExpirationListenerOnStartup() throws Exception { adapter.destroy(); diff --git a/src/test/java/org/springframework/data/redis/core/RedisKeyValueTemplateTests.java b/src/test/java/org/springframework/data/redis/core/RedisKeyValueTemplateTests.java index a01ebd0c1c..3385746394 100644 --- a/src/test/java/org/springframework/data/redis/core/RedisKeyValueTemplateTests.java +++ b/src/test/java/org/springframework/data/redis/core/RedisKeyValueTemplateTests.java @@ -790,6 +790,81 @@ public Void doInRedis(RedisConnection connection) throws DataAccessException { }); } + /** + * @see DATAREDIS-530 + */ + @Test + public void partialUpdateShouldLeaveIndexesNotInvolvedInUpdateUntouched() { + + final Person rand = new Person(); + rand.firstname = "rand"; + rand.lastname = "al-thor"; + rand.email = "rand@twof.com"; + + template.insert(rand); + + /* + * Set the lastname and make sure we've an index on it afterwards + */ + PartialUpdate update = PartialUpdate.newPartialUpdate(rand.id, Person.class).set("lastname", "doe"); + + template.doPartialUpdate(update); + + nativeTemplate.execute(new RedisCallback() { + + @Override + public Void doInRedis(RedisConnection connection) throws DataAccessException { + + assertThat(connection.hGet(("template-test-person:" + rand.id).getBytes(), "lastname".getBytes()), + is(equalTo("doe".getBytes()))); + assertThat(connection.exists("template-test-person:email:rand@twof.com".getBytes()), is(true)); + assertThat(connection.exists("template-test-person:lastname:al-thor".getBytes()), is(false)); + assertThat(connection.sIsMember("template-test-person:lastname:doe".getBytes(), rand.id.getBytes()), is(true)); + return null; + } + }); + } + + /** + * @see DATAREDIS-530 + */ + @Test + public void updateShouldAlterIndexesCorrectlyWhenValuesGetRemovedFromHash() { + + final Person rand = new Person(); + rand.firstname = "rand"; + rand.lastname = "al-thor"; + rand.email = "rand@twof.com"; + + template.insert(rand); + + nativeTemplate.execute(new RedisCallback() { + + @Override + public Void doInRedis(RedisConnection connection) throws DataAccessException { + + assertThat(connection.exists("template-test-person:email:rand@twof.com".getBytes()), is(true)); + assertThat(connection.exists("template-test-person:lastname:al-thor".getBytes()), is(true)); + return null; + } + }); + + rand.lastname = null; + + template.update(rand); + + nativeTemplate.execute(new RedisCallback() { + + @Override + public Void doInRedis(RedisConnection connection) throws DataAccessException { + + assertThat(connection.exists("template-test-person:email:rand@twof.com".getBytes()), is(true)); + assertThat(connection.exists("template-test-person:lastname:al-thor".getBytes()), is(false)); + return null; + } + }); + } + @EqualsAndHashCode @RedisHash("template-test-type-mapping") static class VariousTypes { @@ -828,6 +903,7 @@ static class Person { @Id String id; String firstname; @Indexed String lastname; + @Indexed String email; Integer age; List nicknames;