Skip to content

Commit 3595097

Browse files
DATAREDIS-471 - Hacking.
Only update parts of the hash. Update indexes defined in configuration or found in store.
1 parent 1f44f0a commit 3595097

File tree

6 files changed

+377
-4
lines changed

6 files changed

+377
-4
lines changed
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
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+
/**
23+
* @author Christoph Strobl
24+
* @param <T>
25+
*/
26+
public class PartialUpdate<T> {
27+
28+
private final Object id;
29+
private final Class<T> target;
30+
private T value;
31+
private boolean refreshTtl = false;
32+
33+
private final List<PropertyUpdate> propertyUpdates = new ArrayList<PropertyUpdate>();
34+
35+
public PartialUpdate(Object id, Class<T> type) {
36+
37+
this.id = id;
38+
this.target = type;
39+
}
40+
41+
public PartialUpdate(Object id, T value) {
42+
43+
this.id = id;
44+
this.target = (Class<T>) value.getClass();
45+
this.value = value;
46+
}
47+
48+
public void setValue(T value) {
49+
this.value = value;
50+
}
51+
52+
public T getValue() {
53+
return value;
54+
}
55+
56+
public void set(String path, Object value) {
57+
propertyUpdates.add(new PropertyUpdate(UpdateCommand.SET, path, value));
58+
}
59+
60+
public void del(String path) {
61+
propertyUpdates.add(new PropertyUpdate(UpdateCommand.DEL, path, null));
62+
}
63+
64+
public Class<T> getTarget() {
65+
return target;
66+
}
67+
68+
public Object getId() {
69+
return id;
70+
}
71+
72+
public List<PropertyUpdate> getPropertyUpdates() {
73+
return Collections.unmodifiableList(propertyUpdates);
74+
}
75+
76+
public boolean isRefreshTtl() {
77+
return refreshTtl;
78+
}
79+
80+
public void setRefreshTtl(boolean refreshTtl) {
81+
this.refreshTtl = refreshTtl;
82+
}
83+
84+
static enum UpdateCommand {
85+
SET, DEL
86+
}
87+
88+
static class PropertyUpdate {
89+
90+
private final UpdateCommand cmd;
91+
private final String propertyPath;
92+
private final Object value;
93+
94+
public PropertyUpdate(UpdateCommand cmd, String propertyPath, Object value) {
95+
96+
this.cmd = cmd;
97+
this.propertyPath = propertyPath;
98+
this.value = value;
99+
}
100+
101+
public UpdateCommand getCmd() {
102+
return cmd;
103+
}
104+
105+
public String getPropertyPath() {
106+
return propertyPath;
107+
}
108+
109+
public Object getValue() {
110+
return value;
111+
}
112+
113+
}
114+
115+
}

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

Lines changed: 91 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,14 +39,18 @@
3939
import org.springframework.data.redis.connection.MessageListener;
4040
import org.springframework.data.redis.connection.RedisConnection;
4141
import org.springframework.data.redis.connection.RedisConnectionFactory;
42+
import org.springframework.data.redis.core.PartialUpdate.PropertyUpdate;
43+
import org.springframework.data.redis.core.PartialUpdate.UpdateCommand;
4244
import org.springframework.data.redis.core.convert.CustomConversions;
4345
import org.springframework.data.redis.core.convert.KeyspaceConfiguration;
4446
import org.springframework.data.redis.core.convert.MappingRedisConverter;
4547
import org.springframework.data.redis.core.convert.PathIndexResolver;
4648
import org.springframework.data.redis.core.convert.RedisConverter;
4749
import org.springframework.data.redis.core.convert.RedisData;
4850
import org.springframework.data.redis.core.convert.ReferenceResolverImpl;
51+
import org.springframework.data.redis.core.convert.SimpleIndexedPropertyValue;
4952
import org.springframework.data.redis.core.mapping.RedisMappingContext;
53+
import org.springframework.data.redis.core.mapping.RedisPersistentEntity;
5054
import org.springframework.data.redis.listener.KeyExpirationEventMessageListener;
5155
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
5256
import org.springframework.data.redis.util.ByteUtils;
@@ -163,8 +167,7 @@ public RedisKeyValueAdapter(RedisOperations<?, ?> redisOps, RedisConverter redis
163167
/**
164168
* Default constructor.
165169
*/
166-
protected RedisKeyValueAdapter() {
167-
}
170+
protected RedisKeyValueAdapter() {}
168171

169172
/*
170173
* (non-Javadoc)
@@ -392,6 +395,92 @@ public Long doInRedis(RedisConnection connection) throws DataAccessException {
392395
return count != null ? count.longValue() : 0;
393396
}
394397

398+
@SuppressWarnings("unchecked")
399+
public <T> T update(final PartialUpdate<T> update) {
400+
401+
final RedisPersistentEntity<T> entity = (RedisPersistentEntity<T>) this.converter.getMappingContext()
402+
.getPersistentEntity(update.getTarget());
403+
404+
final String keyspace = entity.getKeySpace();
405+
final Object id = update.getId();
406+
407+
final byte[] redisKey = createKey(keyspace, converter.getConversionService().convert(id, String.class));
408+
409+
final RedisData rdo = new RedisData();
410+
this.converter.write(update.getValue(), rdo);
411+
rdo.getBucket().put("_class", null); // overwrite stuff in here
412+
413+
redisOps.execute(new RedisCallback<Void>() {
414+
415+
@Override
416+
public Void doInRedis(RedisConnection connection) throws DataAccessException {
417+
418+
List<byte[]> pathsToRemove = new ArrayList<byte[]>(update.getPropertyUpdates().size());
419+
420+
for (PropertyUpdate pUpdate : update.getPropertyUpdates()) {
421+
422+
String propertyPath = pUpdate.getPropertyPath();
423+
byte[] existingValue = connection.hGet(redisKey, toBytes(propertyPath));
424+
425+
if (UpdateCommand.SET.equals(pUpdate.getCmd())) {
426+
427+
rdo.getBucket().put(propertyPath, toBytes(pUpdate.getValue()));
428+
429+
if (converter.getIndexDefinitionProvider().hasIndexFor(keyspace, propertyPath) || connection
430+
.keys(ByteUtils.concatAll(toBytes(keyspace), (":" + propertyPath).getBytes(), "*".getBytes()))
431+
.size() > 0) {
432+
rdo.addIndexedData(new SimpleIndexedPropertyValue(keyspace, propertyPath, pUpdate.getValue()));
433+
}
434+
435+
} else if (UpdateCommand.DEL.equals(pUpdate.getCmd())) {
436+
437+
pathsToRemove.add(toBytes(propertyPath));
438+
439+
byte[] existingValueIndexKey = existingValue != null
440+
? ByteUtils.concatAll(toBytes(keyspace), (":" + propertyPath).getBytes(), ":".getBytes(), existingValue)
441+
: null;
442+
443+
if (existingValue != null) {
444+
445+
if (connection.exists(existingValueIndexKey)) {
446+
connection.sRem(existingValueIndexKey, toBytes(id));
447+
}
448+
}
449+
}
450+
}
451+
452+
if (!pathsToRemove.isEmpty()) {
453+
connection.hDel(redisKey, pathsToRemove.toArray(new byte[pathsToRemove.size()][]));
454+
}
455+
456+
if (!rdo.getBucket().isEmpty()) {
457+
458+
if (rdo.getBucket().size() == 1 && rdo.getBucket().asMap().containsKey("_class")) {
459+
// ignore
460+
} else {
461+
connection.hMSet(redisKey, rdo.getBucket().rawMap());
462+
}
463+
}
464+
465+
if (update.isRefreshTtl() && rdo.getTimeToLive() != null && rdo.getTimeToLive().longValue() > 0) {
466+
467+
connection.expire(redisKey, rdo.getTimeToLive().longValue());
468+
469+
// add phantom key so values can be restored
470+
byte[] phantomKey = ByteUtils.concat(redisKey, toBytes(":phantom"));
471+
connection.hMSet(phantomKey, rdo.getBucket().rawMap());
472+
connection.expire(phantomKey, rdo.getTimeToLive().longValue() + 300);
473+
}
474+
475+
new IndexWriter(connection, converter).updateIndexes(toBytes(id), rdo.getIndexedData());
476+
return null;
477+
}
478+
479+
});
480+
481+
return get(toBytes(id), keyspace, update.getTarget());
482+
}
483+
395484
/**
396485
* Execute {@link RedisCallback} via underlying {@link RedisOperations}.
397486
*

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

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,32 @@ public List<T> doInRedis(RedisKeyValueAdapter adapter) {
110110
});
111111
}
112112

113+
/*
114+
* (non-Javadoc)
115+
* @see org.springframework.data.keyvalue.core.KeyValueTemplate#update(java.lang.Object)
116+
*/
117+
@Override
118+
public void update(Object objectToUpdate) {
119+
120+
if (objectToUpdate instanceof PartialUpdate) {
121+
doPartialUpdate((PartialUpdate<Object>) objectToUpdate);
122+
}
123+
124+
super.update(objectToUpdate);
125+
}
126+
127+
protected <T> T doPartialUpdate(final PartialUpdate<T> update) {
128+
129+
return execute(new RedisKeyValueCallback<T>() {
130+
131+
@Override
132+
public T doInRedis(RedisKeyValueAdapter adapter) {
133+
134+
return adapter.update(update);
135+
}
136+
});
137+
}
138+
113139
/**
114140
* Redis specific {@link KeyValueCallback}.
115141
*

src/main/java/org/springframework/data/redis/core/convert/MappingRedisConverter.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
import org.springframework.data.mapping.PropertyHandler;
4848
import org.springframework.data.mapping.model.PersistentEntityParameterValueProvider;
4949
import org.springframework.data.mapping.model.PropertyValueProvider;
50+
import org.springframework.data.redis.core.index.IndexDefinitionProvider;
5051
import org.springframework.data.redis.core.index.Indexed;
5152
import org.springframework.data.redis.core.mapping.RedisMappingContext;
5253
import org.springframework.data.redis.core.mapping.RedisPersistentEntity;
@@ -339,6 +340,10 @@ public void doWithAssociation(Association<KeyValuePersistentProperty> associatio
339340
@SuppressWarnings({ "rawtypes" })
340341
public void write(Object source, final RedisData sink) {
341342

343+
if (source == null) {
344+
return;
345+
}
346+
342347
final RedisPersistentEntity entity = mappingContext.getPersistentEntity(source.getClass());
343348

344349
if (!customConversions.hasCustomWriteTarget(source.getClass())) {
@@ -751,6 +756,11 @@ public void afterPropertiesSet() {
751756
this.initializeConverters();
752757
}
753758

759+
@Override
760+
public IndexDefinitionProvider getIndexDefinitionProvider() {
761+
return this.mappingContext.getMappingConfiguration().getIndexConfiguration();
762+
}
763+
754764
private void initializeConverters() {
755765
customConversions.registerConvertersIn(conversionService);
756766
}

src/main/java/org/springframework/data/redis/core/convert/RedisConverter.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import org.springframework.data.convert.EntityConverter;
1919
import org.springframework.data.keyvalue.core.mapping.KeyValuePersistentEntity;
2020
import org.springframework.data.keyvalue.core.mapping.KeyValuePersistentProperty;
21+
import org.springframework.data.redis.core.index.IndexDefinitionProvider;
2122

2223
/**
2324
* Redis specific {@link EntityConverter}.
@@ -28,4 +29,5 @@
2829
public interface RedisConverter extends
2930
EntityConverter<KeyValuePersistentEntity<?>, KeyValuePersistentProperty, Object, RedisData> {
3031

32+
IndexDefinitionProvider getIndexDefinitionProvider();
3133
}

0 commit comments

Comments
 (0)