DATAREDIS-471 - Add support for partial updates.
We now allow partial update of domain types via PartialUpdate. The according expiration times and secondary index structures are updated accordingly.
In some cases it is not necessary to load and rewrite the entire entity just to set a new value within it. A session timestamp for last active time might be such a scenario where you just want to alter one property.
`PartialUpdate` allows to define `set`, `delete` actions on existing objects while taking care of updating potential expiration times of the entity itself as well as index structures.
.Sample Partial Update
====
[source,java]
----
PartialUpdate<Person> update = new PartialUpdate<Person>("e2c7dcee", Person.class)
.set("firstname", "mat") <1>
.set("address.city", "emond's field") <2>
.del("age"); <3>
template.update(update);
update = new PartialUpdate<Person>("e2c7dcee", Person.class)
.set("address", new Address("caemlyn", "andor")) <4>
.set("attributes", singletonMap("eye-color", "grey")); <5>
template.update(update);
update = new PartialUpdate<Person>("e2c7dcee", Person.class)
.refreshTtl(true); <6>
.set("expiration", 1000);
template.update(update);
----
<1> Set the simple property _firstname_ to _mat_
<2> Set the simple property _address.city_ to _emond's field_ without having to pass in the entire object. This does not work when a custom conversion is registered.
<3> Remove the property _age_.
<4> Set complex property _address_.
<5> Set a map/collection of values removes the previously existing map/collection and replaces the values with the given ones.
<6> Automatically update the server expiration time when altering time to live.
====
NOTE: Updating complex objects as well as map/collection structures requires further interaction with Redis to determine existing values which means that it might turn out that rewriting the entire entity might be faster.
Original pull request: #191.
This commit is contained in:
committed by
Mark Paluch
parent
0e17c9b58d
commit
76229c10a4
@@ -21,22 +21,30 @@ import static org.hamcrest.core.IsInstanceOf.*;
|
||||
import static org.hamcrest.core.IsNot.*;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.springframework.beans.factory.DisposableBean;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.Parameterized;
|
||||
import org.junit.runners.Parameterized.Parameters;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.dao.DataAccessException;
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.annotation.Reference;
|
||||
import org.springframework.data.keyvalue.annotation.KeySpace;
|
||||
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.core.convert.Bucket;
|
||||
import org.springframework.data.redis.core.convert.KeyspaceConfiguration;
|
||||
import org.springframework.data.redis.core.convert.MappingConfiguration;
|
||||
@@ -48,19 +56,35 @@ import org.springframework.data.redis.core.mapping.RedisMappingContext;
|
||||
* @author Christoph Strobl
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
@RunWith(Parameterized.class)
|
||||
public class RedisKeyValueAdapterTests {
|
||||
|
||||
RedisKeyValueAdapter adapter;
|
||||
StringRedisTemplate template;
|
||||
RedisConnectionFactory connectionFactory;
|
||||
|
||||
public RedisKeyValueAdapterTests(RedisConnectionFactory connectionFactory) throws Exception {
|
||||
|
||||
if (connectionFactory instanceof InitializingBean) {
|
||||
((InitializingBean) connectionFactory).afterPropertiesSet();
|
||||
}
|
||||
this.connectionFactory = connectionFactory;
|
||||
ConnectionFactoryTracker.add(connectionFactory);
|
||||
}
|
||||
|
||||
@Parameters
|
||||
public static List<RedisConnectionFactory> params() {
|
||||
return Arrays.<RedisConnectionFactory> asList(new JedisConnectionFactory(), new LettuceConnectionFactory());
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void cleanUp() {
|
||||
ConnectionFactoryTracker.cleanUp();
|
||||
}
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
|
||||
JedisConnectionFactory jedisConnectionFactory = new JedisConnectionFactory();
|
||||
jedisConnectionFactory.afterPropertiesSet();
|
||||
connectionFactory = jedisConnectionFactory;
|
||||
|
||||
template = new StringRedisTemplate(connectionFactory);
|
||||
template.afterPropertiesSet();
|
||||
|
||||
@@ -89,14 +113,6 @@ public class RedisKeyValueAdapterTests {
|
||||
} catch (Exception e) {
|
||||
// ignore
|
||||
}
|
||||
|
||||
try {
|
||||
if (connectionFactory instanceof DisposableBean) {
|
||||
((DisposableBean) connectionFactory).destroy();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -312,6 +328,191 @@ public class RedisKeyValueAdapterTests {
|
||||
assertThat(template.opsForSet().isMember("persons:mat:idx", "persons:firstname:mat"), is(true));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREDIS-471
|
||||
*/
|
||||
@Test
|
||||
public void updateShouldAlterIndexDataCorrectly() {
|
||||
|
||||
Person rand = new Person();
|
||||
rand.firstname = "rand";
|
||||
|
||||
adapter.put("1", rand, "persons");
|
||||
|
||||
assertThat(template.hasKey("persons:firstname:rand"), is(true));
|
||||
|
||||
PartialUpdate<Person> update = new PartialUpdate<Person>("1", Person.class) //
|
||||
.set("firstname", "mat");
|
||||
|
||||
adapter.update(update);
|
||||
|
||||
assertThat(template.hasKey("persons:firstname:rand"), is(false));
|
||||
assertThat(template.hasKey("persons:firstname:mat"), is(true));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREDIS-471
|
||||
*/
|
||||
@Test
|
||||
public void updateShouldAlterIndexDataOnNestedObjectCorrectly() {
|
||||
|
||||
Person rand = new Person();
|
||||
rand.address = new Address();
|
||||
rand.address.country = "andor";
|
||||
|
||||
adapter.put("1", rand, "persons");
|
||||
|
||||
assertThat(template.hasKey("persons:address.country:andor"), is(true));
|
||||
|
||||
PartialUpdate<Person> update = new PartialUpdate<Person>("1", Person.class);
|
||||
Address addressUpdate = new Address();
|
||||
addressUpdate.country = "tear";
|
||||
|
||||
update = update.set("address", addressUpdate);
|
||||
|
||||
adapter.update(update);
|
||||
|
||||
assertThat(template.hasKey("persons:address.country:andor"), is(false));
|
||||
assertThat(template.hasKey("persons:address.country:tear"), is(true));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREDIS-471
|
||||
*/
|
||||
@Test
|
||||
public void updateShouldAlterIndexDataOnNestedObjectPathCorrectly() {
|
||||
|
||||
Person rand = new Person();
|
||||
rand.address = new Address();
|
||||
rand.address.country = "andor";
|
||||
|
||||
adapter.put("1", rand, "persons");
|
||||
|
||||
assertThat(template.hasKey("persons:address.country:andor"), is(true));
|
||||
|
||||
PartialUpdate<Person> update = new PartialUpdate<Person>("1", Person.class) //
|
||||
.set("address.country", "tear");
|
||||
|
||||
adapter.update(update);
|
||||
|
||||
assertThat(template.hasKey("persons:address.country:andor"), is(false));
|
||||
assertThat(template.hasKey("persons:address.country:tear"), is(true));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREDIS-471
|
||||
*/
|
||||
@Test
|
||||
public void updateShouldRemoveComplexObjectCorrectly() {
|
||||
|
||||
Person rand = new Person();
|
||||
rand.address = new Address();
|
||||
rand.address.country = "andor";
|
||||
rand.address.city = "emond's field";
|
||||
|
||||
adapter.put("1", rand, "persons");
|
||||
|
||||
PartialUpdate<Person> update = new PartialUpdate<Person>("1", Person.class) //
|
||||
.del("address");
|
||||
|
||||
adapter.update(update);
|
||||
|
||||
assertThat(template.opsForHash().hasKey("persons:1", "address.country"), is(false));
|
||||
assertThat(template.opsForHash().hasKey("persons:1", "address.city"), is(false));
|
||||
assertThat(template.opsForSet().isMember("persons:address.country:andor", "1"), is(false));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREDIS-471
|
||||
*/
|
||||
@Test
|
||||
public void updateShouldRemoveSimpleListValuesCorrectly() {
|
||||
|
||||
Person rand = new Person();
|
||||
rand.nicknames = Arrays.asList("lews therin", "dragon reborn");
|
||||
|
||||
adapter.put("1", rand, "persons");
|
||||
|
||||
PartialUpdate<Person> update = new PartialUpdate<Person>("1", Person.class) //
|
||||
.del("nicknames");
|
||||
|
||||
adapter.update(update);
|
||||
|
||||
assertThat(template.opsForHash().hasKey("persons:1", "nicknames.[0]"), is(false));
|
||||
assertThat(template.opsForHash().hasKey("persons:1", "nicknames.[1]"), is(false));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREDIS-471
|
||||
*/
|
||||
@Test
|
||||
public void updateShouldRemoveComplexListValuesCorrectly() {
|
||||
|
||||
Person mat = new Person();
|
||||
mat.firstname = "mat";
|
||||
mat.nicknames = Collections.singletonList("prince of ravens");
|
||||
|
||||
Person perrin = new Person();
|
||||
perrin.firstname = "mat";
|
||||
perrin.nicknames = Collections.singletonList("lord of the two rivers");
|
||||
|
||||
Person rand = new Person();
|
||||
rand.coworkers = Arrays.asList(mat, perrin);
|
||||
|
||||
adapter.put("1", rand, "persons");
|
||||
|
||||
PartialUpdate<Person> update = new PartialUpdate<Person>("1", Person.class) //
|
||||
.del("coworkers");
|
||||
|
||||
adapter.update(update);
|
||||
|
||||
assertThat(template.opsForHash().hasKey("persons:1", "coworkers.[0].firstname"), is(false));
|
||||
assertThat(template.opsForHash().hasKey("persons:1", "coworkers.[0].nicknames.[0]"), is(false));
|
||||
assertThat(template.opsForHash().hasKey("persons:1", "coworkers.[1].firstname"), is(false));
|
||||
assertThat(template.opsForHash().hasKey("persons:1", "coworkers.[1].nicknames.[0]"), is(false));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREDIS-471
|
||||
*/
|
||||
@Test
|
||||
public void updateShouldRemoveSimpleMapValuesCorrectly() {
|
||||
|
||||
Person rand = new Person();
|
||||
rand.physicalAttributes = Collections.singletonMap("eye-color", "grey");
|
||||
|
||||
adapter.put("1", rand, "persons");
|
||||
|
||||
PartialUpdate<Person> update = new PartialUpdate<Person>("1", Person.class) //
|
||||
.del("physicalAttributes");
|
||||
|
||||
adapter.update(update);
|
||||
|
||||
assertThat(template.opsForHash().hasKey("persons:1", "physicalAttributes.[eye-color]"), is(false));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREDIS-471
|
||||
*/
|
||||
@Test
|
||||
public void updateShouldRemoveComplexMapValuesCorrectly() {
|
||||
|
||||
Person tam = new Person();
|
||||
tam.firstname = "tam";
|
||||
|
||||
Person rand = new Person();
|
||||
rand.relatives = Collections.singletonMap("stepfather", tam);
|
||||
|
||||
adapter.put("1", rand, "persons");
|
||||
|
||||
PartialUpdate<Person> update = new PartialUpdate<Person>("1", Person.class) //
|
||||
.del("relatives");
|
||||
|
||||
adapter.update(update);
|
||||
|
||||
assertThat(template.opsForHash().hasKey("persons:1", "relatives.[stepfather].firstname"), is(false));
|
||||
}
|
||||
|
||||
@KeySpace("persons")
|
||||
static class Person {
|
||||
|
||||
|
||||
@@ -17,11 +17,15 @@ package org.springframework.data.redis.core;
|
||||
|
||||
import static org.hamcrest.core.Is.*;
|
||||
import static org.hamcrest.core.IsCollectionContaining.*;
|
||||
import static org.hamcrest.core.IsEqual.*;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
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 org.junit.After;
|
||||
import org.junit.AfterClass;
|
||||
@@ -36,9 +40,13 @@ 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.core.index.Indexed;
|
||||
import org.springframework.data.redis.core.mapping.RedisMappingContext;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
/**
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
@@ -48,11 +56,14 @@ 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
|
||||
@@ -61,7 +72,10 @@ public class RedisKeyValueTemplateTests {
|
||||
JedisConnectionFactory jedis = new JedisConnectionFactory();
|
||||
jedis.afterPropertiesSet();
|
||||
|
||||
return Collections.<RedisConnectionFactory> singletonList(jedis);
|
||||
LettuceConnectionFactory lettuce = new LettuceConnectionFactory();
|
||||
lettuce.afterPropertiesSet();
|
||||
|
||||
return Arrays.<RedisConnectionFactory> asList(jedis, lettuce);
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
@@ -76,14 +90,13 @@ public class RedisKeyValueTemplateTests {
|
||||
nativeTemplate.setConnectionFactory(connectionFactory);
|
||||
nativeTemplate.afterPropertiesSet();
|
||||
|
||||
RedisMappingContext context = new RedisMappingContext();
|
||||
|
||||
RedisKeyValueAdapter adapter = new RedisKeyValueAdapter(nativeTemplate, context);
|
||||
context = new RedisMappingContext();
|
||||
adapter = new RedisKeyValueAdapter(nativeTemplate, context);
|
||||
template = new RedisKeyValueTemplate(adapter, context);
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() {
|
||||
public void tearDown() throws Exception {
|
||||
|
||||
nativeTemplate.execute(new RedisCallback<Void>() {
|
||||
|
||||
@@ -94,6 +107,8 @@ public class RedisKeyValueTemplateTests {
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
||||
template.destroy();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -198,16 +213,650 @@ public class RedisKeyValueTemplateTests {
|
||||
assertThat(result.size(), is(0));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREDIS-471
|
||||
*/
|
||||
@Test
|
||||
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.doPartialUpdate(update);
|
||||
|
||||
assertThat(template.findById(rand.id, Person.class), is(equalTo(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.doPartialUpdate(update);
|
||||
|
||||
assertThat(template.findById(rand.id, Person.class), is(equalTo(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(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(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;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREDIS-471
|
||||
*/
|
||||
@Test
|
||||
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;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREDIS-471
|
||||
*/
|
||||
@Test
|
||||
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;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREDIS-471
|
||||
*/
|
||||
@Test
|
||||
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;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREDIS-471
|
||||
*/
|
||||
@Test
|
||||
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;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREDIS-471
|
||||
*/
|
||||
@Test
|
||||
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;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREDIS-471
|
||||
*/
|
||||
@Test
|
||||
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;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREDIS-471
|
||||
*/
|
||||
@Test
|
||||
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;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREDIS-471
|
||||
*/
|
||||
@Test
|
||||
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;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREDIS-471
|
||||
*/
|
||||
@Test
|
||||
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;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@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;
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -228,8 +877,25 @@ public class RedisKeyValueTemplateTests {
|
||||
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 + "]";
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,13 +49,17 @@ import java.util.UUID;
|
||||
|
||||
import org.hamcrest.core.IsEqual;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.runners.MockitoJUnitRunner;
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.data.convert.ReadingConverter;
|
||||
import org.springframework.data.convert.WritingConverter;
|
||||
import org.springframework.data.mapping.model.MappingException;
|
||||
import org.springframework.data.redis.core.PartialUpdate;
|
||||
import org.springframework.data.redis.core.convert.ConversionTestEntities.Address;
|
||||
import org.springframework.data.redis.core.convert.ConversionTestEntities.AddressWithId;
|
||||
import org.springframework.data.redis.core.convert.ConversionTestEntities.AddressWithPostcode;
|
||||
@@ -84,6 +88,7 @@ import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class MappingRedisConverterUnitTests {
|
||||
|
||||
public @Rule ExpectedException exception = ExpectedException.none();
|
||||
@Mock ReferenceResolver resolverMock;
|
||||
MappingRedisConverter converter;
|
||||
Person rand;
|
||||
@@ -95,7 +100,6 @@ public class MappingRedisConverterUnitTests {
|
||||
converter.afterPropertiesSet();
|
||||
|
||||
rand = new Person();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1582,6 +1586,8 @@ public class MappingRedisConverterUnitTests {
|
||||
}
|
||||
|
||||
/**
|
||||
* <<<<<<< HEAD
|
||||
*
|
||||
* @see DATAREDIS-509
|
||||
*/
|
||||
@Test
|
||||
@@ -1611,6 +1617,405 @@ public class MappingRedisConverterUnitTests {
|
||||
.containingUtf8String("arrayOfPrimitives.[1]", "2").containingUtf8String("arrayOfPrimitives.[2]", "3"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREDIS-471
|
||||
*/
|
||||
@Test
|
||||
public void writeShouldNotAppendClassTypeHint() {
|
||||
|
||||
Person value = new Person();
|
||||
value.firstname = "rand";
|
||||
value.age = 24;
|
||||
|
||||
PartialUpdate<Person> update = new PartialUpdate<Person>("123", value);
|
||||
|
||||
assertThat(write(update).getBucket().get("_class"), is(nullValue()));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREDIS-471
|
||||
*/
|
||||
@Test
|
||||
public void writeShouldWritePartialUpdateSimpleValueCorrectly() {
|
||||
|
||||
Person value = new Person();
|
||||
value.firstname = "rand";
|
||||
value.age = 24;
|
||||
|
||||
PartialUpdate<Person> update = new PartialUpdate<Person>("123", value);
|
||||
|
||||
assertThat(write(update).getBucket(),
|
||||
isBucket().containingUtf8String("firstname", "rand").containingUtf8String("age", "24"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREDIS-471
|
||||
*/
|
||||
@Test
|
||||
public void writeShouldWritePartialUpdatePathWithSimpleValueCorrectly() {
|
||||
|
||||
PartialUpdate<Person> update = new PartialUpdate<Person>("123", Person.class).set("firstname", "rand").set("age",
|
||||
24);
|
||||
|
||||
assertThat(write(update).getBucket(),
|
||||
isBucket().containingUtf8String("firstname", "rand").containingUtf8String("age", "24"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREDIS-471
|
||||
*/
|
||||
@Test
|
||||
public void writeShouldWritePartialUpdateNestedPathWithSimpleValueCorrectly() {
|
||||
|
||||
PartialUpdate<Person> update = new PartialUpdate<Person>("123", Person.class).set("address.city", "two rivers");
|
||||
|
||||
assertThat(write(update).getBucket(), isBucket().containingUtf8String("address.city", "two rivers"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREDIS-471
|
||||
*/
|
||||
@Test
|
||||
public void writeShouldWritePartialUpdatePathWithComplexValueCorrectly() {
|
||||
|
||||
Address address = new Address();
|
||||
address.city = "two rivers";
|
||||
address.country = "andor";
|
||||
|
||||
PartialUpdate<Person> update = new PartialUpdate<Person>("123", Person.class).set("address", address);
|
||||
|
||||
assertThat(write(update).getBucket(),
|
||||
isBucket().containingUtf8String("address.city", "two rivers").containingUtf8String("address.country", "andor"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREDIS-471
|
||||
*/
|
||||
@Test
|
||||
public void writeShouldWritePartialUpdatePathWithSimpleListValueCorrectly() {
|
||||
|
||||
PartialUpdate<Person> update = new PartialUpdate<Person>("123", Person.class).set("nicknames",
|
||||
Arrays.asList("dragon", "lews"));
|
||||
|
||||
assertThat(write(update).getBucket(),
|
||||
isBucket().containingUtf8String("nicknames.[0]", "dragon").containingUtf8String("nicknames.[1]", "lews"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREDIS-471
|
||||
*/
|
||||
@Test
|
||||
public void writeShouldWritePartialUpdatePathWithComplexListValueCorrectly() {
|
||||
|
||||
Person mat = new Person();
|
||||
mat.firstname = "mat";
|
||||
mat.age = 24;
|
||||
|
||||
Person perrin = new Person();
|
||||
perrin.firstname = "perrin";
|
||||
|
||||
PartialUpdate<Person> update = new PartialUpdate<Person>("123", Person.class).set("coworkers",
|
||||
Arrays.asList(mat, perrin));
|
||||
|
||||
assertThat(write(update).getBucket(), isBucket().containingUtf8String("coworkers.[0].firstname", "mat")
|
||||
.containingUtf8String("coworkers.[0].age", "24").containingUtf8String("coworkers.[1].firstname", "perrin"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREDIS-471
|
||||
*/
|
||||
@Test
|
||||
public void writeShouldWritePartialUpdatePathWithSimpleListValueWhenNotPassedInAsCollectionCorrectly() {
|
||||
|
||||
PartialUpdate<Person> update = new PartialUpdate<Person>("123", Person.class).set("nicknames", "dragon");
|
||||
|
||||
assertThat(write(update).getBucket(), isBucket().containingUtf8String("nicknames.[0]", "dragon"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREDIS-471
|
||||
*/
|
||||
@Test
|
||||
public void writeShouldWritePartialUpdatePathWithComplexListValueWhenNotPassedInAsCollectionCorrectly() {
|
||||
|
||||
Person mat = new Person();
|
||||
mat.firstname = "mat";
|
||||
mat.age = 24;
|
||||
|
||||
PartialUpdate<Person> update = new PartialUpdate<Person>("123", Person.class).set("coworkers", mat);
|
||||
|
||||
assertThat(write(update).getBucket(), isBucket().containingUtf8String("coworkers.[0].firstname", "mat")
|
||||
.containingUtf8String("coworkers.[0].age", "24"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREDIS-471
|
||||
*/
|
||||
@Test
|
||||
public void writeShouldWritePartialUpdatePathWithSimpleListValueWhenNotPassedInAsCollectionWithPositionalParameterCorrectly() {
|
||||
|
||||
PartialUpdate<Person> update = new PartialUpdate<Person>("123", Person.class).set("nicknames.[5]", "dragon");
|
||||
|
||||
assertThat(write(update).getBucket(), isBucket().containingUtf8String("nicknames.[5]", "dragon"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREDIS-471
|
||||
*/
|
||||
@Test
|
||||
public void writeShouldWritePartialUpdatePathWithComplexListValueWhenNotPassedInAsCollectionWithPositionalParameterCorrectly() {
|
||||
|
||||
Person mat = new Person();
|
||||
mat.firstname = "mat";
|
||||
mat.age = 24;
|
||||
|
||||
PartialUpdate<Person> update = new PartialUpdate<Person>("123", Person.class).set("coworkers.[5]", mat);
|
||||
|
||||
assertThat(write(update).getBucket(), isBucket().containingUtf8String("coworkers.[5].firstname", "mat")
|
||||
.containingUtf8String("coworkers.[5].age", "24"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREDIS-471
|
||||
*/
|
||||
@Test
|
||||
public void writeShouldWritePartialUpdatePathWithSimpleMapValueCorrectly() {
|
||||
|
||||
PartialUpdate<Person> update = new PartialUpdate<Person>("123", Person.class).set("physicalAttributes",
|
||||
Collections.singletonMap("eye-color", "grey"));
|
||||
|
||||
assertThat(write(update).getBucket(), isBucket().containingUtf8String("physicalAttributes.[eye-color]", "grey"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREDIS-471
|
||||
*/
|
||||
@Test
|
||||
public void writeShouldWritePartialUpdatePathWithComplexMapValueCorrectly() {
|
||||
|
||||
Person tam = new Person();
|
||||
tam.firstname = "tam";
|
||||
tam.alive = false;
|
||||
|
||||
PartialUpdate<Person> update = new PartialUpdate<Person>("123", Person.class).set("relatives",
|
||||
Collections.singletonMap("father", tam));
|
||||
|
||||
assertThat(write(update).getBucket(), isBucket().containingUtf8String("relatives.[father].firstname", "tam")
|
||||
.containingUtf8String("relatives.[father].alive", "0"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREDIS-471
|
||||
*/
|
||||
@Test
|
||||
public void writeShouldWritePartialUpdatePathWithSimpleMapValueWhenNotPassedInAsCollectionCorrectly() {
|
||||
|
||||
PartialUpdate<Person> update = new PartialUpdate<Person>("123", Person.class).set("physicalAttributes",
|
||||
Collections.singletonMap("eye-color", "grey").entrySet().iterator().next());
|
||||
|
||||
assertThat(write(update).getBucket(), isBucket().containingUtf8String("physicalAttributes.[eye-color]", "grey"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREDIS-471
|
||||
*/
|
||||
@Test
|
||||
public void writeShouldWritePartialUpdatePathWithComplexMapValueWhenNotPassedInAsCollectionCorrectly() {
|
||||
|
||||
Person tam = new Person();
|
||||
tam.firstname = "tam";
|
||||
tam.alive = false;
|
||||
|
||||
PartialUpdate<Person> update = new PartialUpdate<Person>("123", Person.class).set("relatives",
|
||||
Collections.singletonMap("father", tam).entrySet().iterator().next());
|
||||
|
||||
assertThat(write(update).getBucket(), isBucket().containingUtf8String("relatives.[father].firstname", "tam")
|
||||
.containingUtf8String("relatives.[father].alive", "0"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREDIS-471
|
||||
*/
|
||||
@Test
|
||||
public void writeShouldWritePartialUpdatePathWithSimpleMapValueWhenNotPassedInAsCollectionWithPositionalParameterCorrectly() {
|
||||
|
||||
PartialUpdate<Person> update = new PartialUpdate<Person>("123", Person.class).set("physicalAttributes.[eye-color]",
|
||||
"grey");
|
||||
|
||||
assertThat(write(update).getBucket(), isBucket().containingUtf8String("physicalAttributes.[eye-color]", "grey"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREDIS-471
|
||||
*/
|
||||
@Test
|
||||
public void writeShouldWritePartialUpdatePathWithSimpleMapValueOnNestedElementCorrectly() {
|
||||
|
||||
PartialUpdate<Person> update = new PartialUpdate<Person>("123", Person.class).set("relatives.[father].firstname",
|
||||
"tam");
|
||||
|
||||
assertThat(write(update).getBucket(), isBucket().containingUtf8String("relatives.[father].firstname", "tam"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREDIS-471
|
||||
*/
|
||||
@Test(expected = MappingException.class)
|
||||
public void writeShouldThrowExceptionOnPartialUpdatePathWithSimpleMapValueWhenItsASingleValueWithoutPath() {
|
||||
|
||||
PartialUpdate<Person> update = new PartialUpdate<Person>("123", Person.class).set("physicalAttributes", "grey");
|
||||
|
||||
write(update);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREDIS-471
|
||||
*/
|
||||
@Test
|
||||
public void writeShouldWritePartialUpdatePathWithRegisteredCustomConversionCorrectly() {
|
||||
|
||||
this.converter = new MappingRedisConverter(null, null, resolverMock);
|
||||
this.converter
|
||||
.setCustomConversions(new CustomConversions(Collections.singletonList(new AddressToBytesConverter())));
|
||||
this.converter.afterPropertiesSet();
|
||||
|
||||
Address address = new Address();
|
||||
address.country = "Tel'aran'rhiod";
|
||||
address.city = "unknown";
|
||||
|
||||
PartialUpdate<Person> update = new PartialUpdate<Person>("123", Person.class).set("address", address);
|
||||
|
||||
assertThat(write(update).getBucket(),
|
||||
isBucket().containingUtf8String("address", "{\"city\":\"unknown\",\"country\":\"Tel'aran'rhiod\"}"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREDIS-471
|
||||
*/
|
||||
@Test
|
||||
public void writeShouldWritePartialUpdatePathWithReferenceCorrectly() {
|
||||
|
||||
Location tar = new Location();
|
||||
tar.id = "1";
|
||||
tar.name = "tar valon";
|
||||
|
||||
Location tear = new Location();
|
||||
tear.id = "2";
|
||||
tear.name = "city of tear";
|
||||
|
||||
PartialUpdate<Person> update = new PartialUpdate<Person>("123", Person.class).set("visited",
|
||||
Arrays.asList(tar, tear));
|
||||
|
||||
assertThat(write(update).getBucket(),
|
||||
isBucket().containingUtf8String("visited.[0]", "locations:1").containingUtf8String("visited.[1]", "locations:2") //
|
||||
.without("visited.id") //
|
||||
.without("visited.name"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREDIS-471
|
||||
*/
|
||||
@Test
|
||||
public void writeShouldWritePartialUpdatePathWithListOfReferencesCorrectly() {
|
||||
|
||||
Location location = new Location();
|
||||
location.id = "1";
|
||||
location.name = "tar valon";
|
||||
|
||||
PartialUpdate<Person> update = new PartialUpdate<Person>("123", Person.class) //
|
||||
.set("location", location);
|
||||
|
||||
assertThat(write(update).getBucket(),
|
||||
isBucket().containingUtf8String("location", "locations:1") //
|
||||
.without("location.id") //
|
||||
.without("location.name"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREDIS-471
|
||||
*/
|
||||
@Test
|
||||
public void writeShouldThrowExceptionForUpdateValueNotAssignableToDomainTypeProperty() {
|
||||
|
||||
exception.expect(MappingException.class);
|
||||
exception.expectMessage("java.lang.String cannot be assigned");
|
||||
exception.expectMessage("java.lang.Integer");
|
||||
exception.expectMessage("age");
|
||||
|
||||
PartialUpdate<Person> update = new PartialUpdate<Person>("123", Person.class) //
|
||||
.set("age", "twenty-four");
|
||||
|
||||
assertThat(write(update).getBucket().get("_class"), is(nullValue()));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREDIS-471
|
||||
*/
|
||||
@Test
|
||||
public void writeShouldThrowExceptionForUpdateCollectionValueNotAssignableToDomainTypeProperty() {
|
||||
|
||||
exception.expect(MappingException.class);
|
||||
exception.expectMessage("java.lang.String cannot be assigned");
|
||||
exception.expectMessage(Person.class.getName());
|
||||
exception.expectMessage("coworkers.[0]");
|
||||
|
||||
PartialUpdate<Person> update = new PartialUpdate<Person>("123", Person.class) //
|
||||
.set("coworkers.[0]", "buh buh the bear");
|
||||
|
||||
assertThat(write(update).getBucket().get("_class"), is(nullValue()));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREDIS-471
|
||||
*/
|
||||
@Test
|
||||
public void writeShouldThrowExceptionForUpdateValueInCollectionNotAssignableToDomainTypeProperty() {
|
||||
|
||||
exception.expect(MappingException.class);
|
||||
exception.expectMessage("java.lang.String cannot be assigned");
|
||||
exception.expectMessage(Person.class.getName());
|
||||
exception.expectMessage("coworkers");
|
||||
|
||||
PartialUpdate<Person> update = new PartialUpdate<Person>("123", Person.class) //
|
||||
.set("coworkers", Collections.singletonList("foo"));
|
||||
|
||||
assertThat(write(update).getBucket().get("_class"), is(nullValue()));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREDIS-471
|
||||
*/
|
||||
@Test
|
||||
public void writeShouldThrowExceptionForUpdateMapValueNotAssignableToDomainTypeProperty() {
|
||||
|
||||
exception.expect(MappingException.class);
|
||||
exception.expectMessage("java.lang.String cannot be assigned");
|
||||
exception.expectMessage(Person.class.getName());
|
||||
exception.expectMessage("relatives.[father]");
|
||||
|
||||
PartialUpdate<Person> update = new PartialUpdate<Person>("123", Person.class) //
|
||||
.set("relatives.[father]", "buh buh the bear");
|
||||
|
||||
assertThat(write(update).getBucket().get("_class"), is(nullValue()));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREDIS-471
|
||||
*/
|
||||
@Test
|
||||
public void writeShouldThrowExceptionForUpdateValueInMapNotAssignableToDomainTypeProperty() {
|
||||
|
||||
exception.expect(MappingException.class);
|
||||
exception.expectMessage("java.lang.String cannot be assigned");
|
||||
exception.expectMessage(Person.class.getName());
|
||||
exception.expectMessage("relatives.[father]");
|
||||
|
||||
PartialUpdate<Person> update = new PartialUpdate<Person>("123", Person.class) //
|
||||
.set("relatives", Collections.singletonMap("father", "buh buh the bear"));
|
||||
|
||||
assertThat(write(update).getBucket().get("_class"), is(nullValue()));
|
||||
}
|
||||
|
||||
private RedisData write(Object source) {
|
||||
|
||||
RedisData rdo = new RedisData();
|
||||
|
||||
@@ -32,6 +32,7 @@ import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.hamcrest.core.IsCollectionContaining;
|
||||
import org.hamcrest.core.IsInstanceOf;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
@@ -223,6 +224,7 @@ public class PathIndexResolverUnitTests {
|
||||
|
||||
/**
|
||||
* @see DATAREDIS-425
|
||||
* @see DATAREDIS-471
|
||||
*/
|
||||
@Test
|
||||
public void shouldIgnoreConfiguredIndexesInMapWhenValueIsNull() {
|
||||
@@ -235,7 +237,8 @@ public class PathIndexResolverUnitTests {
|
||||
|
||||
Set<IndexedData> indexes = indexResolver.resolveIndexesFor(ClassTypeInformation.from(Person.class), rand);
|
||||
|
||||
assertThat(indexes.size(), is(0));
|
||||
assertThat(indexes.size(), is(1));
|
||||
assertThat(indexes.iterator().next(), IsInstanceOf.instanceOf(RemoveIndexedData.class));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -21,6 +21,7 @@ import static org.junit.Assert.*;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.springframework.data.redis.core.PartialUpdate;
|
||||
import org.springframework.data.redis.core.RedisHash;
|
||||
import org.springframework.data.redis.core.TimeToLive;
|
||||
import org.springframework.data.redis.core.convert.KeyspaceConfiguration;
|
||||
@@ -176,6 +177,55 @@ public class ConfigAwareTimeToLiveAccessorUnitTests {
|
||||
assertThat(accessor.getTimeToLive(new TypeWithTtlOnMethod(100L)), is(100L));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREDIS-471
|
||||
*/
|
||||
@Test
|
||||
public void getTimeToLiveShouldReturnDefaultValue() {
|
||||
|
||||
Long ttl = accessor
|
||||
.getTimeToLive(new PartialUpdate<TypeWithRedisHashAnnotation>("123", new TypeWithRedisHashAnnotation()));
|
||||
|
||||
assertThat(ttl, is(5L));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREDIS-471
|
||||
*/
|
||||
@Test
|
||||
public void getTimeToLiveShouldReturnValueWhenUpdateModifiesTtlProperty() {
|
||||
|
||||
Long ttl = accessor
|
||||
.getTimeToLive(new PartialUpdate<SimpleTypeWithTTLProperty>("123", new SimpleTypeWithTTLProperty())
|
||||
.set("ttl", 100).refreshTtl(true));
|
||||
|
||||
assertThat(ttl, is(100L));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREDIS-471
|
||||
*/
|
||||
@Test
|
||||
public void getTimeToLiveShouldReturnPropertyValueWhenUpdateModifiesTtlProperty() {
|
||||
|
||||
Long ttl = accessor.getTimeToLive(new PartialUpdate<TypeWithRedisHashAnnotationAndTTLProperty>("123",
|
||||
new TypeWithRedisHashAnnotationAndTTLProperty()).set("ttl", 100).refreshTtl(true));
|
||||
|
||||
assertThat(ttl, is(100L));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREDIS-471
|
||||
*/
|
||||
@Test
|
||||
public void getTimeToLiveShouldReturnDefaultValueWhenUpdateDoesNotModifyTtlProperty() {
|
||||
|
||||
Long ttl = accessor.getTimeToLive(new PartialUpdate<TypeWithRedisHashAnnotationAndTTLProperty>("123",
|
||||
new TypeWithRedisHashAnnotationAndTTLProperty()).refreshTtl(true));
|
||||
|
||||
assertThat(ttl, is(10L));
|
||||
}
|
||||
|
||||
static class SimpleType {}
|
||||
|
||||
static class SimpleTypeWithTTLProperty {
|
||||
|
||||
Reference in New Issue
Block a user