All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.springframework.data.redis.core.RedisTemplate Maven / Gradle / Ivy

There is a newer version: 3.2.5
Show newest version
/*
 * Copyright 2011-2018 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.springframework.data.redis.core;

import java.io.Closeable;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;

import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.data.redis.RedisSystemException;
import org.springframework.data.redis.connection.DataType;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.RedisKeyCommands;
import org.springframework.data.redis.connection.RedisServerCommands;
import org.springframework.data.redis.connection.RedisTxCommands;
import org.springframework.data.redis.connection.RedisZSetCommands.Tuple;
import org.springframework.data.redis.connection.SortParameters;
import org.springframework.data.redis.core.ZSetOperations.TypedTuple;
import org.springframework.data.redis.core.query.QueryUtils;
import org.springframework.data.redis.core.query.SortQuery;
import org.springframework.data.redis.core.script.DefaultScriptExecutor;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.data.redis.core.script.ScriptExecutor;
import org.springframework.data.redis.core.types.RedisClientInfo;
import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.SerializationUtils;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.lang.Nullable;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.CollectionUtils;

/**
 * Helper class that simplifies Redis data access code.
 * 

* Performs automatic serialization/deserialization between the given objects and the underlying binary data in the * Redis store. By default, it uses Java serialization for its objects (through {@link JdkSerializationRedisSerializer} * ). For String intensive operations consider the dedicated {@link StringRedisTemplate}. *

* The central method is execute, supporting Redis access code implementing the {@link RedisCallback} interface. It * provides {@link RedisConnection} handling such that neither the {@link RedisCallback} implementation nor the calling * code needs to explicitly care about retrieving/closing Redis connections, or handling Connection lifecycle * exceptions. For typical single step actions, there are various convenience methods. *

* Once configured, this class is thread-safe. *

* Note that while the template is generified, it is up to the serializers/deserializers to properly convert the given * Objects to and from binary data. *

* This is the central class in Redis support. * * @author Costin Leau * @author Christoph Strobl * @author Ninad Divadkar * @author Anqing Shao * @author Mark Paluch * @param the Redis key type against which the template works (usually a String) * @param the Redis value type against which the template works * @see StringRedisTemplate */ public class RedisTemplate extends RedisAccessor implements RedisOperations, BeanClassLoaderAware { private boolean enableTransactionSupport = false; private boolean exposeConnection = false; private boolean initialized = false; private boolean enableDefaultSerializer = true; private @Nullable RedisSerializer defaultSerializer; private @Nullable ClassLoader classLoader; @SuppressWarnings("rawtypes") private @Nullable RedisSerializer keySerializer = null; @SuppressWarnings("rawtypes") private @Nullable RedisSerializer valueSerializer = null; @SuppressWarnings("rawtypes") private @Nullable RedisSerializer hashKeySerializer = null; @SuppressWarnings("rawtypes") private @Nullable RedisSerializer hashValueSerializer = null; private RedisSerializer stringSerializer = RedisSerializer.string(); private @Nullable ScriptExecutor scriptExecutor; // cache singleton objects (where possible) private @Nullable ValueOperations valueOps; private @Nullable ListOperations listOps; private @Nullable SetOperations setOps; private @Nullable ZSetOperations zSetOps; private @Nullable GeoOperations geoOps; private @Nullable HyperLogLogOperations hllOps; /** * Constructs a new RedisTemplate instance. */ public RedisTemplate() {} /* * (non-Javadoc) * @see org.springframework.data.redis.core.RedisAccessor#afterPropertiesSet() */ @Override public void afterPropertiesSet() { super.afterPropertiesSet(); boolean defaultUsed = false; if (defaultSerializer == null) { defaultSerializer = new JdkSerializationRedisSerializer( classLoader != null ? classLoader : this.getClass().getClassLoader()); } if (enableDefaultSerializer) { if (keySerializer == null) { keySerializer = defaultSerializer; defaultUsed = true; } if (valueSerializer == null) { valueSerializer = defaultSerializer; defaultUsed = true; } if (hashKeySerializer == null) { hashKeySerializer = defaultSerializer; defaultUsed = true; } if (hashValueSerializer == null) { hashValueSerializer = defaultSerializer; defaultUsed = true; } } if (enableDefaultSerializer && defaultUsed) { Assert.notNull(defaultSerializer, "default serializer null and not all serializers initialized"); } if (scriptExecutor == null) { this.scriptExecutor = new DefaultScriptExecutor<>(this); } initialized = true; } /* * (non-Javadoc) * @see org.springframework.data.redis.core.RedisOperations#execute(org.springframework.data.redis.core.RedisCallback) */ @Override @Nullable public T execute(RedisCallback action) { return execute(action, isExposeConnection()); } /** * Executes the given action object within a connection, which can be exposed or not. * * @param return type * @param action callback object that specifies the Redis action * @param exposeConnection whether to enforce exposure of the native Redis Connection to callback code * @return object returned by the action */ @Nullable public T execute(RedisCallback action, boolean exposeConnection) { return execute(action, exposeConnection, false); } /** * Executes the given action object within a connection that can be exposed or not. Additionally, the connection can * be pipelined. Note the results of the pipeline are discarded (making it suitable for write-only scenarios). * * @param return type * @param action callback object to execute * @param exposeConnection whether to enforce exposure of the native Redis Connection to callback code * @param pipeline whether to pipeline or not the connection for the execution * @return object returned by the action */ @Nullable public T execute(RedisCallback action, boolean exposeConnection, boolean pipeline) { Assert.isTrue(initialized, "template not initialized; call afterPropertiesSet() before using it"); Assert.notNull(action, "Callback object must not be null"); RedisConnectionFactory factory = getRequiredConnectionFactory(); RedisConnection conn = null; try { if (enableTransactionSupport) { // only bind resources in case of potential transaction synchronization conn = RedisConnectionUtils.bindConnection(factory, enableTransactionSupport); } else { conn = RedisConnectionUtils.getConnection(factory); } boolean existingConnection = TransactionSynchronizationManager.hasResource(factory); RedisConnection connToUse = preProcessConnection(conn, existingConnection); boolean pipelineStatus = connToUse.isPipelined(); if (pipeline && !pipelineStatus) { connToUse.openPipeline(); } RedisConnection connToExpose = (exposeConnection ? connToUse : createRedisConnectionProxy(connToUse)); T result = action.doInRedis(connToExpose); // close pipeline if (pipeline && !pipelineStatus) { connToUse.closePipeline(); } // TODO: any other connection processing? return postProcessResult(result, connToUse, existingConnection); } finally { RedisConnectionUtils.releaseConnection(conn, factory); } } /* * (non-Javadoc) * @see org.springframework.data.redis.core.RedisOperations#execute(org.springframework.data.redis.core.SessionCallback) */ @Override public T execute(SessionCallback session) { Assert.isTrue(initialized, "template not initialized; call afterPropertiesSet() before using it"); Assert.notNull(session, "Callback object must not be null"); RedisConnectionFactory factory = getRequiredConnectionFactory(); // bind connection RedisConnectionUtils.bindConnection(factory, enableTransactionSupport); try { return session.execute(this); } finally { RedisConnectionUtils.unbindConnection(factory); } } /* * (non-Javadoc) * @see org.springframework.data.redis.core.RedisOperations#executePipelined(org.springframework.data.redis.core.SessionCallback) */ @Override public List executePipelined(SessionCallback session) { return executePipelined(session, valueSerializer); } /* * (non-Javadoc) * @see org.springframework.data.redis.core.RedisOperations#executePipelined(org.springframework.data.redis.core.SessionCallback, org.springframework.data.redis.serializer.RedisSerializer) */ @Override public List executePipelined(SessionCallback session, @Nullable RedisSerializer resultSerializer) { Assert.isTrue(initialized, "template not initialized; call afterPropertiesSet() before using it"); Assert.notNull(session, "Callback object must not be null"); RedisConnectionFactory factory = getRequiredConnectionFactory(); // bind connection RedisConnectionUtils.bindConnection(factory, enableTransactionSupport); try { return execute((RedisCallback>) connection -> { connection.openPipeline(); boolean pipelinedClosed = false; try { Object result = executeSession(session); if (result != null) { throw new InvalidDataAccessApiUsageException( "Callback cannot return a non-null value as it gets overwritten by the pipeline"); } List closePipeline = connection.closePipeline(); pipelinedClosed = true; return deserializeMixedResults(closePipeline, resultSerializer, hashKeySerializer, hashValueSerializer); } finally { if (!pipelinedClosed) { connection.closePipeline(); } } }); } finally { RedisConnectionUtils.unbindConnection(factory); } } /* * (non-Javadoc) * @see org.springframework.data.redis.core.RedisOperations#executePipelined(org.springframework.data.redis.core.RedisCallback) */ @Override public List executePipelined(RedisCallback action) { return executePipelined(action, valueSerializer); } /* * (non-Javadoc) * @see org.springframework.data.redis.core.RedisOperations#executePipelined(org.springframework.data.redis.core.RedisCallback, org.springframework.data.redis.serializer.RedisSerializer) */ @Override public List executePipelined(RedisCallback action, @Nullable RedisSerializer resultSerializer) { return execute((RedisCallback>) connection -> { connection.openPipeline(); boolean pipelinedClosed = false; try { Object result = action.doInRedis(connection); if (result != null) { throw new InvalidDataAccessApiUsageException( "Callback cannot return a non-null value as it gets overwritten by the pipeline"); } List closePipeline = connection.closePipeline(); pipelinedClosed = true; return deserializeMixedResults(closePipeline, resultSerializer, hashKeySerializer, hashValueSerializer); } finally { if (!pipelinedClosed) { connection.closePipeline(); } } }); } /* * (non-Javadoc) * @see org.springframework.data.redis.core.RedisOperations#execute(org.springframework.data.redis.core.script.RedisScript, java.util.List, java.lang.Object[]) */ @Override public T execute(RedisScript script, List keys, Object... args) { return scriptExecutor.execute(script, keys, args); } /* * (non-Javadoc) * @see org.springframework.data.redis.core.RedisOperations#execute(org.springframework.data.redis.core.script.RedisScript, org.springframework.data.redis.serializer.RedisSerializer, org.springframework.data.redis.serializer.RedisSerializer, java.util.List, java.lang.Object[]) */ @Override public T execute(RedisScript script, RedisSerializer argsSerializer, RedisSerializer resultSerializer, List keys, Object... args) { return scriptExecutor.execute(script, argsSerializer, resultSerializer, keys, args); } /* * (non-Javadoc) * @see org.springframework.data.redis.core.RedisOperations#executeWithStickyConnection(org.springframework.data.redis.core.RedisCallback) */ @Override public T executeWithStickyConnection(RedisCallback callback) { Assert.isTrue(initialized, "template not initialized; call afterPropertiesSet() before using it"); Assert.notNull(callback, "Callback object must not be null"); RedisConnectionFactory factory = getRequiredConnectionFactory(); RedisConnection connection = preProcessConnection(RedisConnectionUtils.doGetConnection(factory, true, false, false), false); return callback.doInRedis(connection); } private Object executeSession(SessionCallback session) { return session.execute(this); } protected RedisConnection createRedisConnectionProxy(RedisConnection pm) { Class[] ifcs = ClassUtils.getAllInterfacesForClass(pm.getClass(), getClass().getClassLoader()); return (RedisConnection) Proxy.newProxyInstance(pm.getClass().getClassLoader(), ifcs, new CloseSuppressingInvocationHandler(pm)); } /** * Processes the connection (before any settings are executed on it). Default implementation returns the connection as * is. * * @param connection redis connection */ protected RedisConnection preProcessConnection(RedisConnection connection, boolean existingConnection) { return connection; } @Nullable protected T postProcessResult(@Nullable T result, RedisConnection conn, boolean existingConnection) { return result; } /** * Returns whether to expose the native Redis connection to RedisCallback code, or rather a connection proxy (the * default). * * @return whether to expose the native Redis connection or not */ public boolean isExposeConnection() { return exposeConnection; } /** * Sets whether to expose the Redis connection to {@link RedisCallback} code. Default is "false": a proxy will be * returned, suppressing quit and disconnect calls. * * @param exposeConnection */ public void setExposeConnection(boolean exposeConnection) { this.exposeConnection = exposeConnection; } /** * @return Whether or not the default serializer should be used. If not, any serializers not explicitly set will * remain null and values will not be serialized or deserialized. */ public boolean isEnableDefaultSerializer() { return enableDefaultSerializer; } /** * @param enableDefaultSerializer Whether or not the default serializer should be used. If not, any serializers not * explicitly set will remain null and values will not be serialized or deserialized. */ public void setEnableDefaultSerializer(boolean enableDefaultSerializer) { this.enableDefaultSerializer = enableDefaultSerializer; } /** * Returns the default serializer used by this template. * * @return template default serializer */ @Nullable public RedisSerializer getDefaultSerializer() { return defaultSerializer; } /** * Sets the default serializer to use for this template. All serializers (expect the * {@link #setStringSerializer(RedisSerializer)}) are initialized to this value unless explicitly set. Defaults to * {@link JdkSerializationRedisSerializer}. * * @param serializer default serializer to use */ public void setDefaultSerializer(RedisSerializer serializer) { this.defaultSerializer = serializer; } /** * Sets the key serializer to be used by this template. Defaults to {@link #getDefaultSerializer()}. * * @param serializer the key serializer to be used by this template. */ public void setKeySerializer(RedisSerializer serializer) { this.keySerializer = serializer; } /** * Returns the key serializer used by this template. * * @return the key serializer used by this template. */ @Override public RedisSerializer getKeySerializer() { return keySerializer; } /** * Sets the value serializer to be used by this template. Defaults to {@link #getDefaultSerializer()}. * * @param serializer the value serializer to be used by this template. */ public void setValueSerializer(RedisSerializer serializer) { this.valueSerializer = serializer; } /** * Returns the value serializer used by this template. * * @return the value serializer used by this template. */ @Override public RedisSerializer getValueSerializer() { return valueSerializer; } /** * Returns the hashKeySerializer. * * @return Returns the hashKeySerializer */ @Override public RedisSerializer getHashKeySerializer() { return hashKeySerializer; } /** * Sets the hash key (or field) serializer to be used by this template. Defaults to {@link #getDefaultSerializer()}. * * @param hashKeySerializer The hashKeySerializer to set. */ public void setHashKeySerializer(RedisSerializer hashKeySerializer) { this.hashKeySerializer = hashKeySerializer; } /** * Returns the hashValueSerializer. * * @return Returns the hashValueSerializer */ @Override public RedisSerializer getHashValueSerializer() { return hashValueSerializer; } /** * Sets the hash value serializer to be used by this template. Defaults to {@link #getDefaultSerializer()}. * * @param hashValueSerializer The hashValueSerializer to set. */ public void setHashValueSerializer(RedisSerializer hashValueSerializer) { this.hashValueSerializer = hashValueSerializer; } /** * Returns the stringSerializer. * * @return Returns the stringSerializer */ public RedisSerializer getStringSerializer() { return stringSerializer; } /** * Sets the string value serializer to be used by this template (when the arguments or return types are always * strings). Defaults to {@link StringRedisSerializer}. * * @see ValueOperations#get(Object, long, long) * @param stringSerializer The stringValueSerializer to set. */ public void setStringSerializer(RedisSerializer stringSerializer) { this.stringSerializer = stringSerializer; } /** * @param scriptExecutor The {@link ScriptExecutor} to use for executing Redis scripts */ public void setScriptExecutor(ScriptExecutor scriptExecutor) { this.scriptExecutor = scriptExecutor; } @SuppressWarnings("unchecked") private byte[] rawKey(Object key) { Assert.notNull(key, "non null key required"); if (keySerializer == null && key instanceof byte[]) { return (byte[]) key; } return keySerializer.serialize(key); } private byte[] rawString(String key) { return stringSerializer.serialize(key); } @SuppressWarnings("unchecked") private byte[] rawValue(Object value) { if (valueSerializer == null && value instanceof byte[]) { return (byte[]) value; } return valueSerializer.serialize(value); } private byte[][] rawKeys(Collection keys) { final byte[][] rawKeys = new byte[keys.size()][]; int i = 0; for (K key : keys) { rawKeys[i++] = rawKey(key); } return rawKeys; } @SuppressWarnings("unchecked") private K deserializeKey(byte[] value) { return keySerializer != null ? (K) keySerializer.deserialize(value) : (K) value; } @SuppressWarnings({ "unchecked", "rawtypes" }) @Nullable private List deserializeMixedResults(@Nullable List rawValues, @Nullable RedisSerializer valueSerializer, @Nullable RedisSerializer hashKeySerializer, @Nullable RedisSerializer hashValueSerializer) { if (rawValues == null) { return null; } List values = new ArrayList<>(); for (Object rawValue : rawValues) { if (rawValue instanceof byte[] && valueSerializer != null) { values.add(valueSerializer.deserialize((byte[]) rawValue)); } else if (rawValue instanceof List) { // Lists are the only potential Collections of mixed values.... values.add(deserializeMixedResults((List) rawValue, valueSerializer, hashKeySerializer, hashValueSerializer)); } else if (rawValue instanceof Set && !(((Set) rawValue).isEmpty())) { values.add(deserializeSet((Set) rawValue, valueSerializer)); } else if (rawValue instanceof Map && !(((Map) rawValue).isEmpty()) && ((Map) rawValue).values().iterator().next() instanceof byte[]) { values.add(SerializationUtils.deserialize((Map) rawValue, hashKeySerializer, hashValueSerializer)); } else { values.add(rawValue); } } return values; } @SuppressWarnings({ "rawtypes", "unchecked" }) private Set deserializeSet(Set rawSet, @Nullable RedisSerializer valueSerializer) { if (rawSet.isEmpty()) { return rawSet; } Object setValue = rawSet.iterator().next(); if (setValue instanceof byte[] && valueSerializer != null) { return (SerializationUtils.deserialize(rawSet, valueSerializer)); } else if (setValue instanceof Tuple) { return convertTupleValues(rawSet, valueSerializer); } else { return rawSet; } } @SuppressWarnings({ "unchecked", "rawtypes" }) private Set> convertTupleValues(Set rawValues, @Nullable RedisSerializer valueSerializer) { Set> set = new LinkedHashSet<>(rawValues.size()); for (Tuple rawValue : rawValues) { Object value = rawValue.getValue(); if (valueSerializer != null) { value = valueSerializer.deserialize(rawValue.getValue()); } set.add(new DefaultTypedTuple(value, rawValue.getScore())); } return set; } // // RedisOperations // /** * Execute a transaction, using the default {@link RedisSerializer}s to deserialize any results that are byte[]s or * Collections or Maps of byte[]s or Tuples. Other result types (Long, Boolean, etc) are left as-is in the converted * results. If conversion of tx results has been disabled in the {@link RedisConnectionFactory}, the results of exec * will be returned without deserialization. This check is mostly for backwards compatibility with 1.0. * * @return The (possibly deserialized) results of transaction exec */ @Override public List exec() { List results = execRaw(); if (getRequiredConnectionFactory().getConvertPipelineAndTxResults()) { return deserializeMixedResults(results, valueSerializer, hashKeySerializer, hashValueSerializer); } else { return results; } } /* * (non-Javadoc) * @see org.springframework.data.redis.core.RedisOperations#exec(org.springframework.data.redis.serializer.RedisSerializer) */ @Override public List exec(RedisSerializer valueSerializer) { return deserializeMixedResults(execRaw(), valueSerializer, valueSerializer, valueSerializer); } protected List execRaw() { List raw = execute(RedisTxCommands::exec); return raw == null ? Collections.emptyList() : raw; } /* * (non-Javadoc) * @see org.springframework.data.redis.core.RedisOperations#delete(java.lang.Object) */ @Override public Boolean delete(K key) { byte[] rawKey = rawKey(key); Long result = execute(connection -> connection.del(rawKey), true); return result != null && result.intValue() == 1; } /* * (non-Javadoc) * @see org.springframework.data.redis.core.RedisOperations#delete(java.util.Collection) */ @Override public Long delete(Collection keys) { if (CollectionUtils.isEmpty(keys)) { return 0L; } byte[][] rawKeys = rawKeys(keys); return execute(connection -> connection.del(rawKeys), true); } /* * (non-Javadoc) * @see org.springframework.data.redis.core.RedisOperations#unlink(java.lang.Object) */ @Override public Boolean unlink(K key) { byte[] rawKey = rawKey(key); Long result = execute(connection -> connection.unlink(rawKey), true); return result != null && result.intValue() == 1; } /* * (non-Javadoc) * @see org.springframework.data.redis.core.RedisOperations#unlink(java.util.Collection) */ @Override public Long unlink(Collection keys) { if (CollectionUtils.isEmpty(keys)) { return 0L; } byte[][] rawKeys = rawKeys(keys); return execute(connection -> connection.unlink(rawKeys), true); } /* * (non-Javadoc) * @see org.springframework.data.redis.core.RedisOperations#hasKey(java.lang.Object) */ @Override public Boolean hasKey(K key) { byte[] rawKey = rawKey(key); return execute(connection -> connection.exists(rawKey), true); } /* * (non-Javadoc) * @see org.springframework.data.redis.core.RedisOperations#countExistingKeys(java.util.Collection) */ @Override public Long countExistingKeys(Collection keys) { Assert.notNull(keys, "Keys must not be null!"); byte[][] rawKeys = rawKeys(keys); return execute(connection -> connection.exists(rawKeys), true); } /* * (non-Javadoc) * @see org.springframework.data.redis.core.RedisOperations#expire(java.lang.Object, long, java.util.concurrent.TimeUnit) */ @Override public Boolean expire(K key, final long timeout, final TimeUnit unit) { byte[] rawKey = rawKey(key); long rawTimeout = TimeoutUtils.toMillis(timeout, unit); return execute(connection -> { try { return connection.pExpire(rawKey, rawTimeout); } catch (Exception e) { // Driver may not support pExpire or we may be running on Redis 2.4 return connection.expire(rawKey, TimeoutUtils.toSeconds(timeout, unit)); } }, true); } /* * (non-Javadoc) * @see org.springframework.data.redis.core.RedisOperations#expireAt(java.lang.Object, java.util.Date) */ @Override public Boolean expireAt(K key, final Date date) { byte[] rawKey = rawKey(key); return execute(connection -> { try { return connection.pExpireAt(rawKey, date.getTime()); } catch (Exception e) { return connection.expireAt(rawKey, date.getTime() / 1000); } }, true); } /* * (non-Javadoc) * @see org.springframework.data.redis.core.RedisOperations#convertAndSend(java.lang.String, java.lang.Object) */ @Override public void convertAndSend(String channel, Object message) { Assert.hasText(channel, "a non-empty channel is required"); byte[] rawChannel = rawString(channel); byte[] rawMessage = rawValue(message); execute(connection -> { connection.publish(rawChannel, rawMessage); return null; }, true); } // // Value operations // /* * (non-Javadoc) * @see org.springframework.data.redis.core.RedisOperations#getExpire(java.lang.Object) */ @Override public Long getExpire(K key) { byte[] rawKey = rawKey(key); return execute(connection -> connection.ttl(rawKey), true); } /* * (non-Javadoc) * @see org.springframework.data.redis.core.RedisOperations#getExpire(java.lang.Object, java.util.concurrent.TimeUnit) */ @Override public Long getExpire(K key, final TimeUnit timeUnit) { byte[] rawKey = rawKey(key); return execute(connection -> { try { return connection.pTtl(rawKey, timeUnit); } catch (Exception e) { // Driver may not support pTtl or we may be running on Redis 2.4 return connection.ttl(rawKey, timeUnit); } }, true); } /* * (non-Javadoc) * @see org.springframework.data.redis.core.RedisOperations#keys(java.lang.Object) */ @Override @SuppressWarnings("unchecked") public Set keys(K pattern) { byte[] rawKey = rawKey(pattern); Set rawKeys = execute(connection -> connection.keys(rawKey), true); return keySerializer != null ? SerializationUtils.deserialize(rawKeys, keySerializer) : (Set) rawKeys; } /* * (non-Javadoc) * @see org.springframework.data.redis.core.RedisOperations#persist(java.lang.Object) */ @Override public Boolean persist(K key) { byte[] rawKey = rawKey(key); return execute(connection -> connection.persist(rawKey), true); } /* * (non-Javadoc) * @see org.springframework.data.redis.core.RedisOperations#move(java.lang.Object, int) */ @Override public Boolean move(K key, final int dbIndex) { byte[] rawKey = rawKey(key); return execute(connection -> connection.move(rawKey, dbIndex), true); } /* * (non-Javadoc) * @see org.springframework.data.redis.core.RedisOperations#randomKey() */ @Override public K randomKey() { byte[] rawKey = execute(RedisKeyCommands::randomKey, true); return deserializeKey(rawKey); } /* * (non-Javadoc) * @see org.springframework.data.redis.core.RedisOperations#rename(java.lang.Object, java.lang.Object) */ @Override public void rename(K oldKey, K newKey) { byte[] rawOldKey = rawKey(oldKey); byte[] rawNewKey = rawKey(newKey); execute(connection -> { connection.rename(rawOldKey, rawNewKey); return null; }, true); } /* * (non-Javadoc) * @see org.springframework.data.redis.core.RedisOperations#renameIfAbsent(java.lang.Object, java.lang.Object) */ @Override public Boolean renameIfAbsent(K oldKey, K newKey) { byte[] rawOldKey = rawKey(oldKey); byte[] rawNewKey = rawKey(newKey); return execute(connection -> connection.renameNX(rawOldKey, rawNewKey), true); } /* * (non-Javadoc) * @see org.springframework.data.redis.core.RedisOperations#type(java.lang.Object) */ @Override public DataType type(K key) { byte[] rawKey = rawKey(key); return execute(connection -> connection.type(rawKey), true); } /** * Executes the Redis dump command and returns the results. Redis uses a non-standard serialization mechanism and * includes checksum information, thus the raw bytes are returned as opposed to deserializing with valueSerializer. * Use the return value of dump as the value argument to restore * * @param key The key to dump * @return results The results of the dump operation */ @Override public byte[] dump(K key) { byte[] rawKey = rawKey(key); return execute(connection -> connection.dump(rawKey), true); } /** * Executes the Redis restore command. The value passed in should be the exact serialized data returned from * {@link #dump(Object)}, since Redis uses a non-standard serialization mechanism. * * @param key The key to restore * @param value The value to restore, as returned by {@link #dump(Object)} * @param timeToLive An expiration for the restored key, or 0 for no expiration * @param unit The time unit for timeToLive * @param replace use {@literal true} to replace a potentially existing value instead of erroring. * @throws RedisSystemException if the key you are attempting to restore already exists and {@code replace} is set to * {@literal false}. */ @Override public void restore(K key, final byte[] value, long timeToLive, TimeUnit unit, boolean replace) { byte[] rawKey = rawKey(key); long rawTimeout = TimeoutUtils.toMillis(timeToLive, unit); execute(connection -> { connection.restore(rawKey, rawTimeout, value, replace); return null; }, true); } /* * (non-Javadoc) * @see org.springframework.data.redis.core.RedisOperations#multi() */ @Override public void multi() { execute(connection -> { connection.multi(); return null; }, true); } /* * (non-Javadoc) * @see org.springframework.data.redis.core.RedisOperations#discard() */ @Override public void discard() { execute(connection -> { connection.discard(); return null; }, true); } /* * (non-Javadoc) * @see org.springframework.data.redis.core.RedisOperations#watch(java.lang.Object) */ @Override public void watch(K key) { byte[] rawKey = rawKey(key); execute(connection -> { connection.watch(rawKey); return null; }, true); } /* * (non-Javadoc) * @see org.springframework.data.redis.core.RedisOperations#watch(java.util.Collection) */ @Override public void watch(Collection keys) { byte[][] rawKeys = rawKeys(keys); execute(connection -> { connection.watch(rawKeys); return null; }, true); } /* * (non-Javadoc) * @see org.springframework.data.redis.core.RedisOperations#unwatch() */ @Override public void unwatch() { execute(connection -> { connection.unwatch(); return null; }, true); } // Sort operations /* * (non-Javadoc) * @see org.springframework.data.redis.core.RedisOperations#sort(org.springframework.data.redis.core.query.SortQuery) */ @Override @SuppressWarnings("unchecked") public List sort(SortQuery query) { return sort(query, valueSerializer); } /* * (non-Javadoc) * @see org.springframework.data.redis.core.RedisOperations#sort(org.springframework.data.redis.core.query.SortQuery, org.springframework.data.redis.serializer.RedisSerializer) */ @Override public List sort(SortQuery query, @Nullable RedisSerializer resultSerializer) { byte[] rawKey = rawKey(query.getKey()); SortParameters params = QueryUtils.convertQuery(query, stringSerializer); List vals = execute(connection -> connection.sort(rawKey, params), true); return SerializationUtils.deserialize(vals, resultSerializer); } /* * (non-Javadoc) * @see org.springframework.data.redis.core.RedisOperations#sort(org.springframework.data.redis.core.query.SortQuery, org.springframework.data.redis.core.BulkMapper) */ @Override @SuppressWarnings("unchecked") public List sort(SortQuery query, BulkMapper bulkMapper) { return sort(query, bulkMapper, valueSerializer); } /* * (non-Javadoc) * @see org.springframework.data.redis.core.RedisOperations#sort(org.springframework.data.redis.core.query.SortQuery, org.springframework.data.redis.core.BulkMapper, org.springframework.data.redis.serializer.RedisSerializer) */ @Override public List sort(SortQuery query, BulkMapper bulkMapper, @Nullable RedisSerializer resultSerializer) { List values = sort(query, resultSerializer); if (values == null || values.isEmpty()) { return Collections.emptyList(); } int bulkSize = query.getGetPattern().size(); List result = new ArrayList<>(values.size() / bulkSize + 1); List bulk = new ArrayList<>(bulkSize); for (S s : values) { bulk.add(s); if (bulk.size() == bulkSize) { result.add(bulkMapper.mapBulk(Collections.unmodifiableList(bulk))); // create a new list (we could reuse the old one but the client might hang on to it for some reason) bulk = new ArrayList<>(bulkSize); } } return result; } /* * (non-Javadoc) * @see org.springframework.data.redis.core.RedisOperations#sort(org.springframework.data.redis.core.query.SortQuery, java.lang.Object) */ @Override public Long sort(SortQuery query, K storeKey) { byte[] rawStoreKey = rawKey(storeKey); byte[] rawKey = rawKey(query.getKey()); SortParameters params = QueryUtils.convertQuery(query, stringSerializer); return execute(connection -> connection.sort(rawKey, params, rawStoreKey), true); } /* * (non-Javadoc) * @see org.springframework.data.redis.core.RedisOperations#killClient(java.lang.Object) */ @Override public void killClient(final String host, final int port) { execute((RedisCallback) connection -> { connection.killClient(host, port); return null; }); } /* * (non-Javadoc) * @see org.springframework.data.redis.core.RedisOperations#getClientList() */ @Override public List getClientList() { return execute(RedisServerCommands::getClientList); } /* * @see org.springframework.data.redis.core.RedisOperations#slaveOf(java.lang.String, int) */ @Override public void slaveOf(final String host, final int port) { execute((RedisCallback) connection -> { connection.slaveOf(host, port); return null; }); } /* * (non-Javadoc) * @see org.springframework.data.redis.core.RedisOperations#slaveOfNoOne() */ @Override public void slaveOfNoOne() { execute((RedisCallback) connection -> { connection.slaveOfNoOne(); return null; }); } /* * (non-Javadoc) * @see org.springframework.data.redis.core.RedisOperations#opsForCluster() */ @Override public ClusterOperations opsForCluster() { return new DefaultClusterOperations<>(this); } /* * (non-Javadoc) * @see org.springframework.data.redis.core.RedisOperations#opsForGeo() */ @Override public GeoOperations opsForGeo() { if (geoOps == null) { geoOps = new DefaultGeoOperations<>(this); } return geoOps; } /* * (non-Javadoc) * @see org.springframework.data.redis.core.RedisOperations#boundGeoOps(java.lang.Object) */ @Override public BoundGeoOperations boundGeoOps(K key) { return new DefaultBoundGeoOperations<>(key, this); } /* * (non-Javadoc) * @see org.springframework.data.redis.core.RedisOperations#boundHashOps(java.lang.Object) */ @Override public BoundHashOperations boundHashOps(K key) { return new DefaultBoundHashOperations<>(key, this); } /* * (non-Javadoc) * @see org.springframework.data.redis.core.RedisOperations#opsForHash() */ @Override public HashOperations opsForHash() { return new DefaultHashOperations<>(this); } /* * (non-Javadoc) * @see org.springframework.data.redis.core.RedisOperations#opsForHyperLogLog() */ @Override public HyperLogLogOperations opsForHyperLogLog() { if (hllOps == null) { hllOps = new DefaultHyperLogLogOperations<>(this); } return hllOps; } /* * (non-Javadoc) * @see org.springframework.data.redis.core.RedisOperations#opsForList() */ @Override public ListOperations opsForList() { if (listOps == null) { listOps = new DefaultListOperations<>(this); } return listOps; } /* * (non-Javadoc) * @see org.springframework.data.redis.core.RedisOperations#boundListOps(java.lang.Object) */ @Override public BoundListOperations boundListOps(K key) { return new DefaultBoundListOperations<>(key, this); } /* * (non-Javadoc) * @see org.springframework.data.redis.core.RedisOperations#boundSetOps(java.lang.Object) */ @Override public BoundSetOperations boundSetOps(K key) { return new DefaultBoundSetOperations<>(key, this); } /* * (non-Javadoc) * @see org.springframework.data.redis.core.RedisOperations#opsForSet() */ @Override public SetOperations opsForSet() { if (setOps == null) { setOps = new DefaultSetOperations<>(this); } return setOps; } /* * (non-Javadoc) * @see org.springframework.data.redis.core.RedisOperations#boundValueOps(java.lang.Object) */ @Override public BoundValueOperations boundValueOps(K key) { return new DefaultBoundValueOperations<>(key, this); } /* * (non-Javadoc) * @see org.springframework.data.redis.core.RedisOperations#opsForValue() */ @Override public ValueOperations opsForValue() { if (valueOps == null) { valueOps = new DefaultValueOperations<>(this); } return valueOps; } /* * (non-Javadoc) * @see org.springframework.data.redis.core.RedisOperations#boundZSetOps(java.lang.Object) */ @Override public BoundZSetOperations boundZSetOps(K key) { return new DefaultBoundZSetOperations<>(key, this); } /* * (non-Javadoc) * @see org.springframework.data.redis.core.RedisOperations#opsForZSet() */ @Override public ZSetOperations opsForZSet() { if (zSetOps == null) { zSetOps = new DefaultZSetOperations<>(this); } return zSetOps; } /** * If set to {@code true} {@link RedisTemplate} will use {@literal MULTI...EXEC|DISCARD} to keep track of operations. * * @param enableTransactionSupport * @since 1.3 */ public void setEnableTransactionSupport(boolean enableTransactionSupport) { this.enableTransactionSupport = enableTransactionSupport; } /** * Set the {@link ClassLoader} to be used for the default {@link JdkSerializationRedisSerializer} in case no other * {@link RedisSerializer} is explicitly set as the default one. * * @param classLoader can be {@literal null}. * @see org.springframework.beans.factory.BeanClassLoaderAware#setBeanClassLoader * @since 1.8 */ @Override public void setBeanClassLoader(ClassLoader classLoader) { this.classLoader = classLoader; } }