Skip to content

Commit 76229c1

Browse files
christophstroblmp911de
authored andcommitted
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.
1 parent 0e17c9b commit 76229c1

20 files changed

+2184
-59
lines changed

src/main/asciidoc/reference/redis-repositories.adoc

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -493,6 +493,44 @@ mother = persons:a9d4b3a0-50d3-4538-a2fc-f7fc2581ee56 <1>
493493
WARNING: Referenced Objects are not subject of persisting changes when saving the referencing object. Please make sure to persist changes on referenced objects separately, since only the reference will be stored.
494494
Indexes set on properties of referenced types will not be resolved.
495495

496+
[[redis.repositories.partial-updates]]
497+
== Persisting Partial Updates
498+
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.
499+
`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.
500+
501+
.Sample Partial Update
502+
====
503+
[source,java]
504+
----
505+
PartialUpdate<Person> update = new PartialUpdate<Person>("e2c7dcee", Person.class)
506+
.set("firstname", "mat") <1>
507+
.set("address.city", "emond's field") <2>
508+
.del("age"); <3>
509+
510+
template.update(update);
511+
512+
update = new PartialUpdate<Person>("e2c7dcee", Person.class)
513+
.set("address", new Address("caemlyn", "andor")) <4>
514+
.set("attributes", singletonMap("eye-color", "grey")); <5>
515+
516+
template.update(update);
517+
518+
update = new PartialUpdate<Person>("e2c7dcee", Person.class)
519+
.refreshTtl(true); <6>
520+
.set("expiration", 1000);
521+
522+
template.update(update);
523+
----
524+
<1> Set the simple property _firstname_ to _mat_
525+
<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.
526+
<3> Remove the property _age_.
527+
<4> Set complex property _address_.
528+
<5> Set a map/collection of values removes the previously existing map/collection and replaces the values with the given ones.
529+
<6> Automatically update the server expiration time when altering <<redis.repositories.expirations>>.
530+
====
531+
532+
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.
533+
496534
[[redis.repositories.queries]]
497535
== Queries and Query Methods
498536
Query methods allow automatic derivation of simple finder queries from the method name.

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

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import org.springframework.data.redis.connection.RedisConnection;
2222
import org.springframework.data.redis.core.convert.IndexedData;
2323
import org.springframework.data.redis.core.convert.RedisConverter;
24+
import org.springframework.data.redis.core.convert.RemoveIndexedData;
2425
import org.springframework.data.redis.core.convert.SimpleIndexedPropertyValue;
2526
import org.springframework.data.redis.util.ByteUtils;
2627
import org.springframework.util.Assert;
@@ -32,7 +33,7 @@
3233
* Redis. Depending on the type of {@link IndexedData} it uses eg. Sets with specific names to add actually referenced
3334
* keys to. While doing so {@link IndexWriter} also keeps track of all indexes associated with the root types key, which
3435
* allows to remove the root key from all indexes in case of deletion.
35-
*
36+
*
3637
* @author Christoph Strobl
3738
* @author Rob Winch
3839
* @since 1.7
@@ -44,7 +45,7 @@ class IndexWriter {
4445

4546
/**
4647
* Creates new {@link IndexWriter}.
47-
*
48+
*
4849
* @param keyspace The key space to write index values to. Must not be {@literal null}.
4950
* @param connection must not be {@literal null}.
5051
* @param converter must not be {@literal null}.
@@ -104,7 +105,7 @@ private void createOrUpdateIndexes(Object key, Iterable<IndexedData> indexValues
104105

105106
/**
106107
* Removes a key from all available indexes.
107-
*
108+
*
108109
* @param key must not be {@literal null}.
109110
*/
110111
public void removeKeyFromIndexes(String keyspace, Object key) {
@@ -142,7 +143,7 @@ private void removeKeyFromExistingIndexes(byte[] key, Iterable<IndexedData> inde
142143

143144
/**
144145
* Remove given key from all indexes matching {@link IndexedData#getIndexName()}:
145-
*
146+
*
146147
* @param key
147148
* @param indexedData
148149
*/
@@ -168,7 +169,7 @@ private void addKeyToIndexes(byte[] key, Iterable<IndexedData> indexValues) {
168169

169170
/**
170171
* Adds a given key to the index for {@link IndexedData#getIndexName()}.
171-
*
172+
*
172173
* @param key must not be {@literal null}.
173174
* @param indexedData must not be {@literal null}.
174175
*/
@@ -177,7 +178,11 @@ protected void addKeyToIndex(byte[] key, IndexedData indexedData) {
177178
Assert.notNull(key, "Key must not be null!");
178179
Assert.notNull(indexedData, "IndexedData must not be null!");
179180

180-
if (indexedData instanceof SimpleIndexedPropertyValue) {
181+
if (indexedData instanceof RemoveIndexedData) {
182+
return;
183+
}
184+
185+
else if (indexedData instanceof SimpleIndexedPropertyValue) {
181186

182187
Object value = ((SimpleIndexedPropertyValue) indexedData).getValue();
183188

Lines changed: 236 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,236 @@
1+
/*
2+
* Copyright 2016 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.redis.core;
17+
18+
import java.util.ArrayList;
19+
import java.util.Collections;
20+
import java.util.List;
21+
22+
import org.springframework.util.Assert;
23+
import org.springframework.util.ClassUtils;
24+
25+
/**
26+
* {@link PartialUpdate} allows to issue individual property updates without the need of rewriting the whole entity. It
27+
* allows to define {@literal set}, {@literal delete} actions on existing objects while taking care of updating
28+
* potential expiration times of the entity itself as well as index structures.
29+
*
30+
* @author Christoph Strobl
31+
* @param <T>
32+
* @since 1.8
33+
*/
34+
public class PartialUpdate<T> {
35+
36+
private final Object id;
37+
private final Class<T> target;
38+
private final T value;
39+
private boolean refreshTtl = false;
40+
41+
private final List<PropertyUpdate> propertyUpdates = new ArrayList<PropertyUpdate>();
42+
43+
private PartialUpdate(Object id, Class<T> target, T value, boolean refreshTtl, List<PropertyUpdate> propertyUpdates) {
44+
45+
this.id = id;
46+
this.target = target;
47+
this.value = value;
48+
this.refreshTtl = refreshTtl;
49+
this.propertyUpdates.addAll(propertyUpdates);
50+
}
51+
52+
/**
53+
* Create new {@link PartialUpdate} for given id and type.
54+
*
55+
* @param id must not be {@literal null}.
56+
* @param targetType must not be {@literal null}.
57+
*/
58+
@SuppressWarnings("unchecked")
59+
public PartialUpdate(Object id, Class<T> targetType) {
60+
61+
Assert.notNull(id, "Id must not be null!");
62+
Assert.notNull(targetType, "TargetType must not be null!");
63+
64+
this.id = id;
65+
this.target = (Class<T>) ClassUtils.getUserClass(targetType);
66+
this.value = null;
67+
}
68+
69+
/**
70+
* Create new {@link PartialUpdate} for given id and object.
71+
*
72+
* @param id must not be {@literal null}.
73+
* @param value must not be {@literal null}.
74+
*/
75+
@SuppressWarnings("unchecked")
76+
public PartialUpdate(Object id, T value) {
77+
78+
Assert.notNull(id, "Id must not be null!");
79+
Assert.notNull(value, "Value must not be null!");
80+
81+
this.id = id;
82+
this.target = (Class<T>) ClassUtils.getUserClass(value.getClass());
83+
this.value = value;
84+
}
85+
86+
/**
87+
* Create new {@link PartialUpdate} for given id and type.
88+
*
89+
* @param id must not be {@literal null}.
90+
* @param targetType must not be {@literal null}.
91+
*/
92+
public static <S> PartialUpdate<S> newPartialUpdate(Object id, Class<S> targetType) {
93+
return new PartialUpdate<S>(id, targetType);
94+
}
95+
96+
/**
97+
* @return can be {@literal null}.
98+
*/
99+
public T getValue() {
100+
return value;
101+
}
102+
103+
/**
104+
* Set the value of a simple or complex {@literal value} reachable via given {@literal path}.
105+
*
106+
* @param path must not be {@literal null}.
107+
* @param value must not be {@literal null}. If you want to remove a value use {@link #del(String)}.
108+
* @return a new {@link PartialUpdate}.
109+
*/
110+
public PartialUpdate<T> set(String path, Object value) {
111+
112+
Assert.hasText(path, "Path to set must not be null or empty!");
113+
114+
PartialUpdate<T> update = new PartialUpdate<T>(this.id, this.target, this.value, this.refreshTtl,
115+
this.propertyUpdates);
116+
update.propertyUpdates.add(new PropertyUpdate(UpdateCommand.SET, path, value));
117+
return update;
118+
}
119+
120+
/**
121+
* Remove the value reachable via given {@literal path}.
122+
*
123+
* @param path path must not be {@literal null}.
124+
* @return a new {@link PartialUpdate}.
125+
*/
126+
public PartialUpdate<T> del(String path) {
127+
128+
Assert.hasText(path, "Path to remove must not be null or empty!");
129+
130+
PartialUpdate<T> update = new PartialUpdate<T>(this.id, this.target, this.value, this.refreshTtl,
131+
this.propertyUpdates);
132+
update.propertyUpdates.add(new PropertyUpdate(UpdateCommand.DEL, path));
133+
return update;
134+
}
135+
136+
/**
137+
* Get the target type.
138+
*
139+
* @return never {@literal null}.
140+
*/
141+
public Class<T> getTarget() {
142+
return target;
143+
}
144+
145+
/**
146+
* Get the id of the element to update.
147+
*
148+
* @return never {@literal null}.
149+
*/
150+
public Object getId() {
151+
return id;
152+
}
153+
154+
/**
155+
* Get the list of individual property updates.
156+
*
157+
* @return never {@literal null}.
158+
*/
159+
public List<PropertyUpdate> getPropertyUpdates() {
160+
return Collections.unmodifiableList(propertyUpdates);
161+
}
162+
163+
/**
164+
* @return true if expiration time of target should be updated.
165+
*/
166+
public boolean isRefreshTtl() {
167+
return refreshTtl;
168+
}
169+
170+
/**
171+
* Set indicator for updating expiration time of target.
172+
*
173+
* @param refreshTtl
174+
* @return a new {@link PartialUpdate}.
175+
*/
176+
public PartialUpdate<T> refreshTtl(boolean refreshTtl) {
177+
return new PartialUpdate<T>(this.id, this.target, this.value, refreshTtl, this.propertyUpdates);
178+
}
179+
180+
/**
181+
* @author Christoph Strobl
182+
* @since 1.8
183+
*/
184+
public static class PropertyUpdate {
185+
186+
private final UpdateCommand cmd;
187+
private final String propertyPath;
188+
private final Object value;
189+
190+
private PropertyUpdate(UpdateCommand cmd, String propertyPath) {
191+
this(cmd, propertyPath, null);
192+
}
193+
194+
private PropertyUpdate(UpdateCommand cmd, String propertyPath, Object value) {
195+
196+
this.cmd = cmd;
197+
this.propertyPath = propertyPath;
198+
this.value = value;
199+
}
200+
201+
/**
202+
* Get the associated {@link UpdateCommand}.
203+
*
204+
* @return never {@literal null}.
205+
*/
206+
public UpdateCommand getCmd() {
207+
return cmd;
208+
}
209+
210+
/**
211+
* Get the target path.
212+
*
213+
* @return never {@literal null}.
214+
*/
215+
public String getPropertyPath() {
216+
return propertyPath;
217+
}
218+
219+
/**
220+
* Get the value to set.
221+
*
222+
* @return can be {@literal null}.
223+
*/
224+
public Object getValue() {
225+
return value;
226+
}
227+
}
228+
229+
/**
230+
* @author Christoph Strobl
231+
* @since 1.8
232+
*/
233+
public static enum UpdateCommand {
234+
SET, DEL
235+
}
236+
}

0 commit comments

Comments
 (0)