DATAREDIS-531 - Scan commands should use a dedicated bound connection.
SCAN, SSCAN, HSCAN and ZSCAN now reference a dedicated connection that is bound to the Cursor. This allows creation of multiple cursors for different threads without the risk of potentially sharing a single connection. As before the caller is responsible for closing the Cursor correctly after usage. Original pull request: #218.
This commit is contained in:
committed by
Mark Paluch
parent
5fd719bd8a
commit
58d23dea37
@@ -3790,6 +3790,10 @@ public class JedisConnection extends AbstractRedisConnection {
|
||||
JedisConverters.stringListToByteList().convert(result.getResult()));
|
||||
}
|
||||
|
||||
protected void doClose() {
|
||||
JedisConnection.this.close();
|
||||
};
|
||||
|
||||
}.open();
|
||||
|
||||
}
|
||||
@@ -3828,6 +3832,10 @@ public class JedisConnection extends AbstractRedisConnection {
|
||||
JedisConverters.tuplesToTuples().convert(result.getResult()));
|
||||
}
|
||||
|
||||
protected void doClose() {
|
||||
JedisConnection.this.close();
|
||||
};
|
||||
|
||||
}.open();
|
||||
}
|
||||
|
||||
@@ -3863,6 +3871,10 @@ public class JedisConnection extends AbstractRedisConnection {
|
||||
redis.clients.jedis.ScanResult<byte[]> result = jedis.sscan(key, JedisConverters.toBytes(cursorId), params);
|
||||
return new ScanIteration<byte[]>(Long.valueOf(result.getStringCursor()), result.getResult());
|
||||
}
|
||||
|
||||
protected void doClose() {
|
||||
JedisConnection.this.close();
|
||||
};
|
||||
}.open();
|
||||
}
|
||||
|
||||
@@ -3898,6 +3910,11 @@ public class JedisConnection extends AbstractRedisConnection {
|
||||
ScanResult<Entry<byte[], byte[]>> result = jedis.hscan(key, JedisConverters.toBytes(cursorId), params);
|
||||
return new ScanIteration<Map.Entry<byte[], byte[]>>(Long.valueOf(result.getStringCursor()), result.getResult());
|
||||
}
|
||||
|
||||
protected void doClose() {
|
||||
JedisConnection.this.close();
|
||||
};
|
||||
|
||||
}.open();
|
||||
}
|
||||
|
||||
|
||||
@@ -3719,6 +3719,11 @@ public class LettuceConnection extends AbstractRedisConnection {
|
||||
|
||||
return new ScanIteration<byte[]>(Long.valueOf(nextCursorId), (keys));
|
||||
}
|
||||
|
||||
protected void doClose() {
|
||||
LettuceConnection.this.close();
|
||||
}
|
||||
|
||||
}.open();
|
||||
|
||||
}
|
||||
@@ -3759,6 +3764,10 @@ public class LettuceConnection extends AbstractRedisConnection {
|
||||
return new ScanIteration<Entry<byte[], byte[]>>(Long.valueOf(nextCursorId), values.entrySet());
|
||||
}
|
||||
|
||||
protected void doClose() {
|
||||
LettuceConnection.this.close();
|
||||
}
|
||||
|
||||
}.open();
|
||||
}
|
||||
|
||||
@@ -3798,6 +3807,11 @@ public class LettuceConnection extends AbstractRedisConnection {
|
||||
List<byte[]> values = failsafeReadScanValues(valueScanCursor.getValues(), null);
|
||||
return new ScanIteration<byte[]>(Long.valueOf(nextCursorId), values);
|
||||
}
|
||||
|
||||
protected void doClose() {
|
||||
LettuceConnection.this.close();
|
||||
}
|
||||
|
||||
}.open();
|
||||
}
|
||||
|
||||
@@ -3839,6 +3853,11 @@ public class LettuceConnection extends AbstractRedisConnection {
|
||||
List<Tuple> values = failsafeReadScanValues(result, LettuceConverters.scoredValuesToTupleList());
|
||||
return new ScanIteration<Tuple>(Long.valueOf(nextCursorId), values);
|
||||
}
|
||||
|
||||
protected void doClose() {
|
||||
LettuceConnection.this.close();
|
||||
}
|
||||
|
||||
}.open();
|
||||
}
|
||||
|
||||
|
||||
@@ -235,7 +235,7 @@ class DefaultHashOperations<K, HK, HV> extends AbstractOperations<K, Object> imp
|
||||
public Cursor<Entry<HK, HV>> scan(K key, final ScanOptions options) {
|
||||
|
||||
final byte[] rawKey = rawKey(key);
|
||||
return execute(new RedisCallback<Cursor<Map.Entry<HK, HV>>>() {
|
||||
return template.executeWithStickyConnection(new RedisCallback<Cursor<Map.Entry<HK, HV>>>() {
|
||||
|
||||
@Override
|
||||
public Cursor<Entry<HK, HV>> doInRedis(RedisConnection connection) throws DataAccessException {
|
||||
@@ -268,7 +268,7 @@ class DefaultHashOperations<K, HK, HV> extends AbstractOperations<K, Object> imp
|
||||
});
|
||||
}
|
||||
|
||||
}, true);
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -255,7 +255,7 @@ class DefaultSetOperations<K, V> extends AbstractOperations<K, V> implements Set
|
||||
public Cursor<V> scan(K key, final ScanOptions options) {
|
||||
|
||||
final byte[] rawKey = rawKey(key);
|
||||
return execute(new RedisCallback<Cursor<V>>() {
|
||||
return template.executeWithStickyConnection(new RedisCallback<Cursor<V>>() {
|
||||
|
||||
@Override
|
||||
public Cursor<V> doInRedis(RedisConnection connection) throws DataAccessException {
|
||||
@@ -267,7 +267,7 @@ class DefaultSetOperations<K, V> extends AbstractOperations<K, V> implements Set
|
||||
}
|
||||
});
|
||||
}
|
||||
}, true);
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -409,13 +409,13 @@ class DefaultZSetOperations<K, V> extends AbstractOperations<K, V> implements ZS
|
||||
public Cursor<TypedTuple<V>> scan(K key, final ScanOptions options) {
|
||||
|
||||
final byte[] rawKey = rawKey(key);
|
||||
Cursor<Tuple> cursor = execute(new RedisCallback<Cursor<Tuple>>() {
|
||||
Cursor<Tuple> cursor = template.executeWithStickyConnection(new RedisCallback<Cursor<Tuple>>() {
|
||||
|
||||
@Override
|
||||
public Cursor<Tuple> doInRedis(RedisConnection connection) throws DataAccessException {
|
||||
return connection.zScan(rawKey, options);
|
||||
}
|
||||
}, true);
|
||||
});
|
||||
|
||||
return new ConvertingCursor<Tuple, TypedTuple<V>>(cursor, new Converter<Tuple, TypedTuple<V>>() {
|
||||
|
||||
|
||||
@@ -150,7 +150,8 @@ public interface HashOperations<H, HK, HV> {
|
||||
RedisOperations<H, ?> getOperations();
|
||||
|
||||
/**
|
||||
* Use a {@link Cursor} to iterate over entries in hash at {@code key}.
|
||||
* Use a {@link Cursor} to iterate over entries in hash at {@code key}. <br />
|
||||
* <strong>Important:</strong> Call {@link Cursor#close()} when done to avoid resource leak.
|
||||
*
|
||||
* @since 1.4
|
||||
* @param key must not be {@literal null}.
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
package org.springframework.data.redis.core;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.util.Collection;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
@@ -22,6 +23,7 @@ import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.springframework.data.redis.connection.DataType;
|
||||
import org.springframework.data.redis.connection.RedisConnection;
|
||||
import org.springframework.data.redis.core.query.SortQuery;
|
||||
import org.springframework.data.redis.core.script.RedisScript;
|
||||
import org.springframework.data.redis.core.types.RedisClientInfo;
|
||||
@@ -129,6 +131,16 @@ public interface RedisOperations<K, V> {
|
||||
<T> T execute(RedisScript<T> script, RedisSerializer<?> argsSerializer, RedisSerializer<T> resultSerializer,
|
||||
List<K> keys, Object... args);
|
||||
|
||||
/**
|
||||
* Allocates and binds a new {@link RedisConnection} to the actual return type of the method. It is up to the caller
|
||||
* to free resources after use.
|
||||
*
|
||||
* @param callback must not be {@literal null}.
|
||||
* @return
|
||||
* @since 1.8
|
||||
*/
|
||||
<T extends Closeable> T executeWithStickyConnection(RedisCallback<T> callback);
|
||||
|
||||
Boolean hasKey(K key);
|
||||
|
||||
void delete(K key);
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
package org.springframework.data.redis.core;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.lang.reflect.Proxy;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
@@ -304,6 +305,23 @@ public class RedisTemplate<K, V> extends RedisAccessor implements RedisOperation
|
||||
return scriptExecutor.execute(script, argsSerializer, resultSerializer, keys, args);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.redis.core.RedisOperations#executeWithStickyConnection(org.springframework.data.redis.core.RedisCallback)
|
||||
*/
|
||||
public <T extends Closeable> T executeWithStickyConnection(RedisCallback<T> callback) {
|
||||
|
||||
Assert.isTrue(initialized, "template not initialized; call afterPropertiesSet() before using it");
|
||||
Assert.notNull(callback, "Callback object must not be null");
|
||||
|
||||
RedisConnectionFactory factory = getConnectionFactory();
|
||||
|
||||
RedisConnection connection = preProcessConnection(RedisConnectionUtils.doGetConnection(factory, true, false, false),
|
||||
false);
|
||||
|
||||
return callback.doInRedis(connection);
|
||||
}
|
||||
|
||||
private Object executeSession(SessionCallback<?> session) {
|
||||
return session.execute(this);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2011-2014 the original author or authors.
|
||||
* Copyright 2011-2016 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.
|
||||
@@ -75,8 +75,9 @@ public interface SetOperations<K, V> {
|
||||
RedisOperations<K, V> getOperations();
|
||||
|
||||
/**
|
||||
* Iterate over elements in set at {@code key}.
|
||||
*
|
||||
* Iterate over elements in set at {@code key}. <br />
|
||||
* <strong>Important:</strong> Call {@link Cursor#close()} when done to avoid resource leak.
|
||||
*
|
||||
* @since 1.4
|
||||
* @param key
|
||||
* @param options
|
||||
|
||||
@@ -136,6 +136,9 @@ public interface ZSetOperations<K, V> {
|
||||
RedisOperations<K, V> getOperations();
|
||||
|
||||
/**
|
||||
* Iterate over elements in zset at {@code key}. <br />
|
||||
* <strong>Important:</strong> Call {@link Cursor#close()} when done to avoid resource leak.
|
||||
*
|
||||
* @since 1.4
|
||||
* @param key
|
||||
* @param options
|
||||
|
||||
Reference in New Issue
Block a user