/* * Copyright 2015-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.springframework.data.redis.core; import static org.hamcrest.CoreMatchers.*; import static org.hamcrest.number.IsCloseTo.*; import static org.junit.Assert.*; import lombok.Data; import lombok.EqualsAndHashCode; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Optional; import org.junit.After; import org.junit.AfterClass; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; import org.junit.runners.Parameterized.Parameters; import org.springframework.dao.DataAccessException; import org.springframework.data.annotation.Id; import org.springframework.data.redis.ConnectionFactoryTracker; import org.springframework.data.redis.connection.RedisConnection; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.connection.jedis.JedisConnectionFactory; import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory; import org.springframework.data.redis.connection.lettuce.LettuceTestClientResources; import org.springframework.data.redis.core.index.Indexed; import org.springframework.data.redis.core.mapping.RedisMappingContext; import org.springframework.util.ObjectUtils; /** * Integration tests for {@link RedisKeyValueTemplate}. * * @author Christoph Strobl * @author Mark Paluch */ @RunWith(Parameterized.class) public class RedisKeyValueTemplateTests { RedisConnectionFactory connectionFactory; RedisKeyValueTemplate template; RedisTemplate<Object, Object> nativeTemplate; RedisMappingContext context; RedisKeyValueAdapter adapter; public RedisKeyValueTemplateTests(RedisConnectionFactory connectionFactory) { this.connectionFactory = connectionFactory; ConnectionFactoryTracker.add(connectionFactory); } @Parameters public static List<RedisConnectionFactory> params() { JedisConnectionFactory jedis = new JedisConnectionFactory(); jedis.afterPropertiesSet(); LettuceConnectionFactory lettuce = new LettuceConnectionFactory(); lettuce.setClientResources(LettuceTestClientResources.getSharedClientResources()); lettuce.afterPropertiesSet(); return Arrays.<RedisConnectionFactory> asList(jedis, lettuce); } @AfterClass public static void cleanUp() { ConnectionFactoryTracker.cleanUp(); } @Before public void setUp() { nativeTemplate = new RedisTemplate<Object, Object>(); nativeTemplate.setConnectionFactory(connectionFactory); nativeTemplate.afterPropertiesSet(); context = new RedisMappingContext(); adapter = new RedisKeyValueAdapter(nativeTemplate, context); template = new RedisKeyValueTemplate(adapter, context); } @After public void tearDown() throws Exception { nativeTemplate.execute(new RedisCallback<Void>() { @Override public Void doInRedis(RedisConnection connection) throws DataAccessException { connection.flushDb(); return null; } }); template.destroy(); adapter.destroy(); } @Test // DATAREDIS-425 public void savesObjectCorrectly() { final Person rand = new Person(); rand.firstname = "rand"; template.insert(rand); nativeTemplate.execute(new RedisCallback<Void>() { @Override public Void doInRedis(RedisConnection connection) throws DataAccessException { assertThat(connection.exists(("template-test-person:" + rand.id).getBytes()), is(true)); return null; } }); } @Test // DATAREDIS-425 public void findProcessesCallbackReturningSingleIdCorrectly() { Person rand = new Person(); rand.firstname = "rand"; final Person mat = new Person(); mat.firstname = "mat"; template.insert(rand); template.insert(mat); List<Person> result = template.find(new RedisCallback<byte[]>() { @Override public byte[] doInRedis(RedisConnection connection) throws DataAccessException { return mat.id.getBytes(); } }, Person.class); assertThat(result.size(), is(1)); assertThat(result, hasItems(mat)); } @Test // DATAREDIS-425 public void findProcessesCallbackReturningMultipleIdsCorrectly() { final Person rand = new Person(); rand.firstname = "rand"; final Person mat = new Person(); mat.firstname = "mat"; template.insert(rand); template.insert(mat); List<Person> result = template.find(new RedisCallback<List<byte[]>>() { @Override public List<byte[]> doInRedis(RedisConnection connection) throws DataAccessException { return Arrays.asList(rand.id.getBytes(), mat.id.getBytes()); } }, Person.class); assertThat(result.size(), is(2)); assertThat(result, hasItems(rand, mat)); } @Test // DATAREDIS-425 public void findProcessesCallbackReturningNullCorrectly() { Person rand = new Person(); rand.firstname = "rand"; Person mat = new Person(); mat.firstname = "mat"; template.insert(rand); template.insert(mat); List<Person> result = template.find(new RedisCallback<List<byte[]>>() { @Override public List<byte[]> doInRedis(RedisConnection connection) throws DataAccessException { return null; } }, Person.class); assertThat(result.size(), is(0)); } @Test // DATAREDIS-471 public void partialUpdate() { final Person rand = new Person(); rand.firstname = "rand"; template.insert(rand); /* * Set the lastname and make sure we've an index on it afterwards */ Person update1 = new Person(rand.id, null, "al-thor"); PartialUpdate<Person> update = new PartialUpdate<Person>(rand.id, update1); template.insert(update); assertThat(template.findById(rand.id, Person.class), is(equalTo(Optional.of(new Person(rand.id, "rand", "al-thor"))))); nativeTemplate.execute(new RedisCallback<Void>() { @Override public Void doInRedis(RedisConnection connection) throws DataAccessException { assertThat(connection.hGet(("template-test-person:" + rand.id).getBytes(), "firstname".getBytes()), is(equalTo("rand".getBytes()))); assertThat(connection.exists("template-test-person:lastname:al-thor".getBytes()), is(true)); assertThat(connection.sIsMember("template-test-person:lastname:al-thor".getBytes(), rand.id.getBytes()), is(true)); return null; } }); /* * Set the firstname and make sure lastname index and value is not affected */ update = new PartialUpdate<Person>(rand.id, Person.class).set("firstname", "frodo"); template.update(update); assertThat(template.findById(rand.id, Person.class), is(equalTo(Optional.of(new Person(rand.id, "frodo", "al-thor"))))); nativeTemplate.execute(new RedisCallback<Void>() { @Override public Void doInRedis(RedisConnection connection) throws DataAccessException { assertThat(connection.exists("template-test-person:lastname:al-thor".getBytes()), is(true)); assertThat(connection.sIsMember("template-test-person:lastname:al-thor".getBytes(), rand.id.getBytes()), is(true)); return null; } }); /* * Remote firstname and update lastname. Make sure lastname index is updated */ update = new PartialUpdate<Person>(rand.id, Person.class) // .del("firstname").set("lastname", "baggins"); template.doPartialUpdate(update); assertThat(template.findById(rand.id, Person.class), is(equalTo(Optional.of(new Person(rand.id, null, "baggins"))))); nativeTemplate.execute(new RedisCallback<Void>() { @Override public Void doInRedis(RedisConnection connection) throws DataAccessException { assertThat(connection.exists("template-test-person:lastname:al-thor".getBytes()), is(false)); assertThat(connection.exists("template-test-person:lastname:baggins".getBytes()), is(true)); assertThat(connection.sIsMember("template-test-person:lastname:baggins".getBytes(), rand.id.getBytes()), is(true)); return null; } }); /* * Remove lastname and make sure the index vanishes */ update = new PartialUpdate<Person>(rand.id, Person.class) // .del("lastname"); template.doPartialUpdate(update); assertThat(template.findById(rand.id, Person.class), is(equalTo(Optional.of(new Person(rand.id, null, null))))); nativeTemplate.execute(new RedisCallback<Void>() { @Override public Void doInRedis(RedisConnection connection) throws DataAccessException { assertThat(connection.keys("template-test-person:lastname:*".getBytes()).size(), is(0)); return null; } }); } @Test // DATAREDIS-471 public void partialUpdateSimpleType() { final VariousTypes source = new VariousTypes(); source.stringValue = "some-value"; template.insert(source); PartialUpdate<VariousTypes> update = new PartialUpdate<VariousTypes>(source.id, VariousTypes.class) // .set("stringValue", "hooya!"); template.doPartialUpdate(update); nativeTemplate.execute(new RedisCallback<Void>() { @Override public Void doInRedis(RedisConnection connection) throws DataAccessException { assertThat(connection.hGet(("template-test-type-mapping:" + source.id).getBytes(), "stringValue".getBytes()), is("hooya!".getBytes())); assertThat(connection.hExists(("template-test-type-mapping:" + source.id).getBytes(), "simpleTypedMap._class".getBytes()), is(false)); return null; } }); } @Test // DATAREDIS-471 public void partialUpdateComplexType() { Item callandor = new Item(); callandor.name = "Callandor"; callandor.dimension = new Dimension(); callandor.dimension.length = 100; final VariousTypes source = new VariousTypes(); source.complexValue = callandor; template.insert(source); Item portalStone = new Item(); portalStone.name = "Portal Stone"; portalStone.dimension = new Dimension(); portalStone.dimension.height = 350; portalStone.dimension.width = 70; PartialUpdate<VariousTypes> update = new PartialUpdate<VariousTypes>(source.id, VariousTypes.class) // .set("complexValue", portalStone); template.doPartialUpdate(update); nativeTemplate.execute(new RedisCallback<Void>() { @Override public Void doInRedis(RedisConnection connection) throws DataAccessException { assertThat( connection.hGet(("template-test-type-mapping:" + source.id).getBytes(), "complexValue.name".getBytes()), is("Portal Stone".getBytes())); assertThat(connection.hGet(("template-test-type-mapping:" + source.id).getBytes(), "complexValue.dimension.height".getBytes()), is("350".getBytes())); assertThat(connection.hGet(("template-test-type-mapping:" + source.id).getBytes(), "complexValue.dimension.width".getBytes()), is("70".getBytes())); assertThat(connection.hExists(("template-test-type-mapping:" + source.id).getBytes(), "complexValue.dimension.length".getBytes()), is(false)); return null; } }); } @Test // DATAREDIS-471 public void partialUpdateObjectType() { Item callandor = new Item(); callandor.name = "Callandor"; callandor.dimension = new Dimension(); callandor.dimension.length = 100; final VariousTypes source = new VariousTypes(); source.objectValue = callandor; template.insert(source); Item portalStone = new Item(); portalStone.name = "Portal Stone"; portalStone.dimension = new Dimension(); portalStone.dimension.height = 350; portalStone.dimension.width = 70; PartialUpdate<VariousTypes> update = new PartialUpdate<VariousTypes>(source.id, VariousTypes.class) // .set("objectValue", portalStone); template.doPartialUpdate(update); nativeTemplate.execute(new RedisCallback<Void>() { @Override public Void doInRedis(RedisConnection connection) throws DataAccessException { assertThat( connection.hGet(("template-test-type-mapping:" + source.id).getBytes(), "objectValue._class".getBytes()), is(Item.class.getName().getBytes())); assertThat( connection.hGet(("template-test-type-mapping:" + source.id).getBytes(), "objectValue.name".getBytes()), is("Portal Stone".getBytes())); assertThat(connection.hGet(("template-test-type-mapping:" + source.id).getBytes(), "objectValue.dimension.height".getBytes()), is("350".getBytes())); assertThat(connection.hGet(("template-test-type-mapping:" + source.id).getBytes(), "objectValue.dimension.width".getBytes()), is("70".getBytes())); assertThat(connection.hExists(("template-test-type-mapping:" + source.id).getBytes(), "objectValue".getBytes()), is(false)); return null; } }); } @Test // DATAREDIS-471 public void partialUpdateSimpleTypedMap() { final VariousTypes source = new VariousTypes(); source.simpleTypedMap = new LinkedHashMap<String, String>(); source.simpleTypedMap.put("key-1", "rand"); source.simpleTypedMap.put("key-2", "mat"); source.simpleTypedMap.put("key-3", "perrin"); template.insert(source); PartialUpdate<VariousTypes> update = new PartialUpdate<VariousTypes>(source.id, VariousTypes.class) // .set("simpleTypedMap", Collections.singletonMap("spring", "data")); template.doPartialUpdate(update); nativeTemplate.execute(new RedisCallback<Void>() { @Override public Void doInRedis(RedisConnection connection) throws DataAccessException { assertThat(connection.hGet(("template-test-type-mapping:" + source.id).getBytes(), "simpleTypedMap.[spring]".getBytes()), is("data".getBytes())); assertThat(connection.hExists(("template-test-type-mapping:" + source.id).getBytes(), "simpleTypedMap.[key-1]".getBytes()), is(false)); assertThat(connection.hExists(("template-test-type-mapping:" + source.id).getBytes(), "simpleTypedMap.[key-2]".getBytes()), is(false)); assertThat(connection.hExists(("template-test-type-mapping:" + source.id).getBytes(), "simpleTypedMap.[key-2]".getBytes()), is(false)); return null; } }); } @Test // DATAREDIS-471 public void partialUpdateComplexTypedMap() { final VariousTypes source = new VariousTypes(); source.complexTypedMap = new LinkedHashMap<String, Item>(); Item callandor = new Item(); callandor.name = "Callandor"; callandor.dimension = new Dimension(); callandor.dimension.height = 100; Item portalStone = new Item(); portalStone.name = "Portal Stone"; portalStone.dimension = new Dimension(); portalStone.dimension.height = 350; portalStone.dimension.width = 70; source.complexTypedMap.put("callandor", callandor); source.complexTypedMap.put("portal-stone", portalStone); template.insert(source); Item hornOfValere = new Item(); hornOfValere.name = "Horn of Valere"; hornOfValere.dimension = new Dimension(); hornOfValere.dimension.height = 70; hornOfValere.dimension.width = 25; PartialUpdate<VariousTypes> update = new PartialUpdate<VariousTypes>(source.id, VariousTypes.class) // .set("complexTypedMap", Collections.singletonMap("horn-of-valere", hornOfValere)); template.doPartialUpdate(update); nativeTemplate.execute(new RedisCallback<Void>() { @Override public Void doInRedis(RedisConnection connection) throws DataAccessException { assertThat(connection.hGet(("template-test-type-mapping:" + source.id).getBytes(), "complexTypedMap.[horn-of-valere].name".getBytes()), is("Horn of Valere".getBytes())); assertThat(connection.hGet(("template-test-type-mapping:" + source.id).getBytes(), "complexTypedMap.[horn-of-valere].dimension.height".getBytes()), is("70".getBytes())); assertThat(connection.hGet(("template-test-type-mapping:" + source.id).getBytes(), "complexTypedMap.[horn-of-valere].dimension.width".getBytes()), is("25".getBytes())); assertThat(connection.hExists(("template-test-type-mapping:" + source.id).getBytes(), "complexTypedMap.[callandor].name".getBytes()), is(false)); assertThat(connection.hExists(("template-test-type-mapping:" + source.id).getBytes(), "complexTypedMap.[callandor].dimension.height".getBytes()), is(false)); assertThat(connection.hExists(("template-test-type-mapping:" + source.id).getBytes(), "complexTypedMap.[callandor].dimension.width".getBytes()), is(false)); assertThat(connection.hExists(("template-test-type-mapping:" + source.id).getBytes(), "complexTypedMap.[portal-stone].name".getBytes()), is(false)); assertThat(connection.hExists(("template-test-type-mapping:" + source.id).getBytes(), "complexTypedMap.[portal-stone].dimension.height".getBytes()), is(false)); assertThat(connection.hExists(("template-test-type-mapping:" + source.id).getBytes(), "complexTypedMap.[portal-stone].dimension.width".getBytes()), is(false)); return null; } }); } @Test // DATAREDIS-471 public void partialUpdateObjectTypedMap() { final VariousTypes source = new VariousTypes(); source.untypedMap = new LinkedHashMap<String, Object>(); Item callandor = new Item(); callandor.name = "Callandor"; callandor.dimension = new Dimension(); callandor.dimension.height = 100; Item portalStone = new Item(); portalStone.name = "Portal Stone"; portalStone.dimension = new Dimension(); portalStone.dimension.height = 350; portalStone.dimension.width = 70; source.untypedMap.put("callandor", callandor); source.untypedMap.put("just-a-string", "some-string-value"); source.untypedMap.put("portal-stone", portalStone); template.insert(source); Item hornOfValere = new Item(); hornOfValere.name = "Horn of Valere"; hornOfValere.dimension = new Dimension(); hornOfValere.dimension.height = 70; hornOfValere.dimension.width = 25; Map<String, Object> map = new LinkedHashMap<String, Object>(); map.put("spring", "data"); map.put("horn-of-valere", hornOfValere); map.put("some-number", 100L); PartialUpdate<VariousTypes> update = new PartialUpdate<VariousTypes>(source.id, VariousTypes.class) // .set("untypedMap", map); template.doPartialUpdate(update); nativeTemplate.execute(new RedisCallback<Void>() { @Override public Void doInRedis(RedisConnection connection) throws DataAccessException { assertThat(connection.hGet(("template-test-type-mapping:" + source.id).getBytes(), "untypedMap.[horn-of-valere].name".getBytes()), is("Horn of Valere".getBytes())); assertThat(connection.hGet(("template-test-type-mapping:" + source.id).getBytes(), "untypedMap.[horn-of-valere].dimension.height".getBytes()), is("70".getBytes())); assertThat(connection.hGet(("template-test-type-mapping:" + source.id).getBytes(), "untypedMap.[horn-of-valere].dimension.width".getBytes()), is("25".getBytes())); assertThat(connection.hGet(("template-test-type-mapping:" + source.id).getBytes(), "untypedMap.[spring]._class".getBytes()), is("java.lang.String".getBytes())); assertThat( connection.hGet(("template-test-type-mapping:" + source.id).getBytes(), "untypedMap.[spring]".getBytes()), is("data".getBytes())); assertThat(connection.hGet(("template-test-type-mapping:" + source.id).getBytes(), "untypedMap.[some-number]._class".getBytes()), is("java.lang.Long".getBytes())); assertThat(connection.hGet(("template-test-type-mapping:" + source.id).getBytes(), "untypedMap.[some-number]".getBytes()), is("100".getBytes())); assertThat(connection.hExists(("template-test-type-mapping:" + source.id).getBytes(), "untypedMap.[callandor].name".getBytes()), is(false)); assertThat(connection.hExists(("template-test-type-mapping:" + source.id).getBytes(), "untypedMap.[callandor].dimension.height".getBytes()), is(false)); assertThat(connection.hExists(("template-test-type-mapping:" + source.id).getBytes(), "untypedMap.[callandor].dimension.width".getBytes()), is(false)); assertThat(connection.hExists(("template-test-type-mapping:" + source.id).getBytes(), "untypedMap.[portal-stone].name".getBytes()), is(false)); assertThat(connection.hExists(("template-test-type-mapping:" + source.id).getBytes(), "untypedMap.[portal-stone].dimension.height".getBytes()), is(false)); assertThat(connection.hExists(("template-test-type-mapping:" + source.id).getBytes(), "untypedMap.[portal-stone].dimension.width".getBytes()), is(false)); return null; } }); } @Test // DATAREDIS-471 public void partialUpdateSimpleTypedList() { final VariousTypes source = new VariousTypes(); source.simpleTypedList = new ArrayList<String>(); source.simpleTypedList.add("rand"); source.simpleTypedList.add("mat"); source.simpleTypedList.add("perrin"); template.insert(source); PartialUpdate<VariousTypes> update = new PartialUpdate<VariousTypes>(source.id, VariousTypes.class) // .set("simpleTypedList", Collections.singletonList("spring")); template.doPartialUpdate(update); nativeTemplate.execute(new RedisCallback<Void>() { @Override public Void doInRedis(RedisConnection connection) throws DataAccessException { assertThat( connection.hGet(("template-test-type-mapping:" + source.id).getBytes(), "simpleTypedList.[0]".getBytes()), is("spring".getBytes())); assertThat(connection.hExists(("template-test-type-mapping:" + source.id).getBytes(), "simpleTypedList.[1]".getBytes()), is(false)); assertThat(connection.hExists(("template-test-type-mapping:" + source.id).getBytes(), "simpleTypedList.[2]".getBytes()), is(false)); assertThat(connection.hExists(("template-test-type-mapping:" + source.id).getBytes(), "simpleTypedList.[3]".getBytes()), is(false)); return null; } }); } @Test // DATAREDIS-471 public void partialUpdateComplexTypedList() { final VariousTypes source = new VariousTypes(); source.complexTypedList = new ArrayList<Item>(); Item callandor = new Item(); callandor.name = "Callandor"; callandor.dimension = new Dimension(); callandor.dimension.height = 100; Item portalStone = new Item(); portalStone.name = "Portal Stone"; portalStone.dimension = new Dimension(); portalStone.dimension.height = 350; portalStone.dimension.width = 70; source.complexTypedList.add(callandor); source.complexTypedList.add(portalStone); template.insert(source); Item hornOfValere = new Item(); hornOfValere.name = "Horn of Valere"; hornOfValere.dimension = new Dimension(); hornOfValere.dimension.height = 70; hornOfValere.dimension.width = 25; PartialUpdate<VariousTypes> update = new PartialUpdate<VariousTypes>(source.id, VariousTypes.class) // .set("complexTypedList", Collections.singletonList(hornOfValere)); template.doPartialUpdate(update); nativeTemplate.execute(new RedisCallback<Void>() { @Override public Void doInRedis(RedisConnection connection) throws DataAccessException { assertThat(connection.hGet(("template-test-type-mapping:" + source.id).getBytes(), "complexTypedList.[0].name".getBytes()), is("Horn of Valere".getBytes())); assertThat(connection.hGet(("template-test-type-mapping:" + source.id).getBytes(), "complexTypedList.[0].dimension.height".getBytes()), is("70".getBytes())); assertThat(connection.hGet(("template-test-type-mapping:" + source.id).getBytes(), "complexTypedList.[0].dimension.width".getBytes()), is("25".getBytes())); assertThat(connection.hExists(("template-test-type-mapping:" + source.id).getBytes(), "complexTypedMap.[1].name".getBytes()), is(false)); assertThat(connection.hExists(("template-test-type-mapping:" + source.id).getBytes(), "complexTypedMap.[1].dimension.height".getBytes()), is(false)); assertThat(connection.hExists(("template-test-type-mapping:" + source.id).getBytes(), "complexTypedMap.[1].dimension.width".getBytes()), is(false)); return null; } }); } @Test // DATAREDIS-471 public void partialUpdateObjectTypedList() { final VariousTypes source = new VariousTypes(); source.untypedList = new ArrayList<Object>(); Item callandor = new Item(); callandor.name = "Callandor"; callandor.dimension = new Dimension(); callandor.dimension.height = 100; Item portalStone = new Item(); portalStone.name = "Portal Stone"; portalStone.dimension = new Dimension(); portalStone.dimension.height = 350; portalStone.dimension.width = 70; source.untypedList.add(callandor); source.untypedList.add("some-string-value"); source.untypedList.add(portalStone); template.insert(source); Item hornOfValere = new Item(); hornOfValere.name = "Horn of Valere"; hornOfValere.dimension = new Dimension(); hornOfValere.dimension.height = 70; hornOfValere.dimension.width = 25; List<Object> list = new ArrayList<Object>(); list.add("spring"); list.add(hornOfValere); list.add(100L); PartialUpdate<VariousTypes> update = new PartialUpdate<VariousTypes>(source.id, VariousTypes.class) // .set("untypedList", list); template.doPartialUpdate(update); nativeTemplate.execute(new RedisCallback<Void>() { @Override public Void doInRedis(RedisConnection connection) throws DataAccessException { assertThat(connection.hGet(("template-test-type-mapping:" + source.id).getBytes(), "untypedList.[0]._class".getBytes()), is("java.lang.String".getBytes())); assertThat( connection.hGet(("template-test-type-mapping:" + source.id).getBytes(), "untypedList.[0]".getBytes()), is("spring".getBytes())); assertThat( connection.hGet(("template-test-type-mapping:" + source.id).getBytes(), "untypedList.[1].name".getBytes()), is("Horn of Valere".getBytes())); assertThat(connection.hGet(("template-test-type-mapping:" + source.id).getBytes(), "untypedList.[1].dimension.height".getBytes()), is("70".getBytes())); assertThat(connection.hGet(("template-test-type-mapping:" + source.id).getBytes(), "untypedList.[1].dimension.width".getBytes()), is("25".getBytes())); assertThat(connection.hGet(("template-test-type-mapping:" + source.id).getBytes(), "untypedList.[2]._class".getBytes()), is("java.lang.Long".getBytes())); assertThat( connection.hGet(("template-test-type-mapping:" + source.id).getBytes(), "untypedList.[2]".getBytes()), is("100".getBytes())); return null; } }); } @Test // DATAREDIS-530 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<Person> update = PartialUpdate.newPartialUpdate(rand.id, Person.class).set("lastname", "doe"); template.doPartialUpdate(update); nativeTemplate.execute(new RedisCallback<Void>() { @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; } }); } @Test // DATAREDIS-530 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<Void>() { @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<Void>() { @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; } }); } @Test // DATAREDIS-523 public void shouldReadBackExplicitTimeToLive() throws InterruptedException { WithTtl source = new WithTtl(); source.id = "ttl-1"; source.ttl = 5L; source.value = "5 seconds"; template.insert(source); Thread.sleep(1100); Optional<WithTtl> target = template.findById(source.id, WithTtl.class); assertThat(target.get().ttl, is(notNullValue())); assertThat(target.get().ttl.doubleValue(), is(closeTo(3D, 1D))); } @Test // DATAREDIS-523 public void shouldReadBackExplicitTimeToLiveToPrimitiveField() throws InterruptedException { WithPrimitiveTtl source = new WithPrimitiveTtl(); source.id = "ttl-1"; source.ttl = 5; source.value = "5 seconds"; template.insert(source); Thread.sleep(1100); Optional<WithPrimitiveTtl> target = template.findById(source.id, WithPrimitiveTtl.class); assertThat((double) target.get().ttl, is(closeTo(3D, 1D))); } @Test // DATAREDIS-523 public void shouldReadBackExplicitTimeToLiveWhenFetchingList() throws InterruptedException { WithTtl source = new WithTtl(); source.id = "ttl-1"; source.ttl = 5L; source.value = "5 seconds"; template.insert(source); Thread.sleep(1100); WithTtl target = template.findAll(WithTtl.class).iterator().next(); assertThat(target.ttl, is(notNullValue())); assertThat(target.ttl.doubleValue(), is(closeTo(3D, 1D))); } @Test // DATAREDIS-523 public void shouldReadBackExplicitTimeToLiveAndSetItToMinusOnelIfPersisted() throws InterruptedException { WithTtl source = new WithTtl(); source.id = "ttl-1"; source.ttl = 5L; source.value = "5 seconds"; template.insert(source); nativeTemplate.execute(new RedisCallback<Boolean>() { @Override public Boolean doInRedis(RedisConnection connection) throws DataAccessException { return connection.persist((WithTtl.class.getName() + ":ttl-1").getBytes()); } }); Optional<WithTtl> target = template.findById(source.id, WithTtl.class); assertThat(target.get().ttl, is(-1L)); } @EqualsAndHashCode @RedisHash("template-test-type-mapping") static class VariousTypes { @Id String id; String stringValue; Integer integerValue; Item complexValue; Object objectValue; List<String> simpleTypedList; List<Item> complexTypedList; List<Object> untypedList; Map<String, String> simpleTypedMap; Map<String, Item> complexTypedMap; Map<String, Object> untypedMap; } static class Item { String name; Dimension dimension; } static class Dimension { Integer height; Integer width; Integer length; } @RedisHash("template-test-person") static class Person { @Id String id; String firstname; @Indexed String lastname; @Indexed String email; Integer age; List<String> nicknames; public Person() {} public Person(String firstname, String lastname) { this(null, firstname, lastname, null); } public Person(String id, String firstname, String lastname) { this(id, firstname, lastname, null); } public Person(String id, String firstname, String lastname, Integer age) { this.id = id; this.firstname = firstname; this.lastname = lastname; this.age = age; } @Override public int hashCode() { int result = ObjectUtils.nullSafeHashCode(firstname); result += ObjectUtils.nullSafeHashCode(lastname); result += ObjectUtils.nullSafeHashCode(age); result += ObjectUtils.nullSafeHashCode(nicknames); return result + ObjectUtils.nullSafeHashCode(id); } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null) { return false; } if (!(obj instanceof Person)) { return false; } Person that = (Person) obj; if (!ObjectUtils.nullSafeEquals(this.firstname, that.firstname)) { return false; } if (!ObjectUtils.nullSafeEquals(this.lastname, that.lastname)) { return false; } if (!ObjectUtils.nullSafeEquals(this.age, that.age)) { return false; } if (!ObjectUtils.nullSafeEquals(this.nicknames, that.nicknames)) { return false; } return ObjectUtils.nullSafeEquals(this.id, that.id); } @Override public String toString() { return "Person [id=" + id + ", firstname=" + firstname + ", lastname=" + lastname + ", age=" + age + ", nicknames=" + nicknames + "]"; } } @Data static class WithTtl { @Id String id; String value; @TimeToLive Long ttl; } @Data static class WithPrimitiveTtl { @Id String id; String value; @TimeToLive int ttl; } }