Integração do Redis em Aplicações Spring MVC

1. Adicionando Dependências Maven

Para intergar o Redis em uma aplicação Spring MVC, é necessário incluir as bibliotecas do cliente Redis (Jedis) e a abstração do Spring Data Redis no arquivo pom.xml do seu projeto. Além disso, para a serialização e desserialização de objetos Java para JSON no Redis, é recomendável adicionar uma biblioteca como o Jackson.

<!-- Cliente Redis -->
<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>2.9.0</version>
</dependency>

<!-- Integração Spring Data Redis -->
<dependency>
    <groupId>org.springframework.data</groupId>
    <artifactId>spring-data-redis</artifactId>
    <version>1.8.4.RELEASE</version>
</dependency>

<!-- Serializador JSON (ex: Jackson) para objetos complexos -->
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.8.8</version>
</dependency>

2. Arquivo de Propriedades do Redis

Crie um arquivo de propriedades, por exemplo, configuracao-redis.properties, para gerenciar os parâmetros de conexão e configuração do pool do Redis. Isso facilita a manutenção e a adaptação a diferentes ambientes de implantação.

# Configurações do pool Jedis
redis.cache.maxIdle=300
redis.cache.maxTotal=600
redis.cache.maxWaitMillis=1000
redis.cache.testOnBorrow=true

# Configurações do servidor Redis
redis.server.port=6379
redis.server.password=
redis.server.database=0
redis.server.host=10.10.10.1

3. Configuração XML do Spring Data Redis

Configure os beans do Spring para a integração com o Redis. Este exemplo utiliza um arquivo XML, contexto-cache-redis.xml, para definir o JedisPoolConfig, JedisConnectionFactory e RedisTemplate. O RedisTemplate é a principal interface para interagir com o Redis no Spring, permitindo operações de alto nível. É crucial definir serializadores adequados para chaves e valores, garantindo que os objetos Java sejam armazenados e recuperados corretamente do Redis.

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
                        http://www.springframework.org/schema/beans/spring-beans.xsd
                        http://www.springframework.org/schema/context
                        http://www.springframework.org/schema/context/spring-context.xsd">

    <!-- Carrega as propriedades do Redis -->
    <context:property-placeholder location="classpath:configuracao-redis.properties"/>

    <!-- Configuração do Pool de Conexões Jedis -->
    <bean id="jedisPoolConfiguracao" class="redis.clients.jedis.JedisPoolConfig">
        <property name="maxIdle" value="${redis.cache.maxIdle}" />
        <property name="maxTotal" value="${redis.cache.maxTotal}" />
        <property name="maxWaitMillis" value="${redis.cache.maxWaitMillis}" />
        <property name="testOnBorrow" value="${redis.cache.testOnBorrow}" />
    </bean>

    <!-- Fábrica de Conexões Jedis para o Spring Data Redis -->
    <bean id="jedisConnectionFabrica"
        class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
        <property name="hostName" value="${redis.server.host}" />
        <property name="port" value="${redis.server.port}" />
        <property name="password" value="${redis.server.password}" />
        <property name="database" value="${redis.server.database}" />
        <property name="poolConfig" ref="jedisPoolConfiguracao" />
    </bean>

    <!-- Serializadores para o RedisTemplate, importantes para converter objetos Java para bytes e vice-versa -->
    <bean id="stringSerializer" class="org.springframework.data.redis.serializer.StringRedisSerializer" />
    <bean id="jsonRedisSerializer" class="org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer">
        <!-- Usar Object.class para permitir a serialização de qualquer tipo, o Jackson inferirá o tipo -->
        <constructor-arg type="java.lang.Class" value="java.lang.Object" />
    </bean>

    <!-- Template de Operações Redis, usando os serializadores definidos -->
    <bean id="redisOperacoesTemplate" class="org.springframework.data.redis.core.RedisTemplate">
        <property name="connectionFactory" ref="jedisConnectionFabrica" />
        <property name="keySerializer" ref="stringSerializer" />
        <property name="valueSerializer" ref="jsonRedisSerializer" />
        <property name="hashKeySerializer" ref="stringSerializer" />
        <property name="hashValueSerializer" ref="jsonRedisSerializer" />
        <!-- Opcional: Habilita suporte a transações para operações atômicas -->
        <property name="enableTransactionSupport" value="true" />
    </bean>
</beans>

4. Classe Utilitária para Operações Redis

Para encapsular as operações de cache e facilitar o uso do Redis na aplicação, é reocmendável criar uma classe de serviço ou utilitária. Esta classe injeta o RedisTemplate e oferece métodos convenientes para manipulação de diferentes tipos de dados no Redis (Strings, Hashes, Sets e Lists), abstraindo os detalhes de baixo nível do cliente.

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.RedisStringCommands.SetOption;
import org.springframework.data.redis.connection.Expiration;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;

@Component
public class GerenciadorRedis {

    private final RedisTemplate<String, Object> redisTemplate;

    @Autowired
    public GerenciadorRedis(RedisTemplate<String, Object> redisTemplate) {
        this.redisTemplate = redisTemplate;
    }

    /**
     * Define o tempo de expiração para uma chave existente.
     * @param chave A chave a ser modificada.
     * @param tempo O tempo de expiração.
     * @param unidadeTempo A unidade de tempo (e.g., TimeUnit.SECONDS).
     * @return true se bem-sucedido, false caso contrário.
     */
    public boolean definirExpiracao(String chave, long tempo, TimeUnit unidadeTempo) {
        try {
            if (tempo > 0) {
                redisTemplate.expire(chave, tempo, unidadeTempo);
            }
            return true;
        } catch (Exception e) {
            // Considerar logar a exceção aqui.
            return false;
        }
    }

    /**
     * Obtém o tempo de expiração restante para uma chave.
     * @param chave A chave.
     * @return O tempo restante em segundos. Retorna -1 se a chave não tiver expiração ou -2 se não existir.
     */
    public Long obterTempoExpiracao(String chave) {
        return redisTemplate.getExpire(chave, TimeUnit.SECONDS);
    }

    /**
     * Verifica se uma chave existe no Redis.
     * @param chave A chave.
     * @return true se a chave existe, false caso contrário.
     */
    public Boolean chaveExiste(String chave) {
        try {
            return redisTemplate.hasKey(chave);
        } catch (Exception e) {
            return false;
        }
    }

    /**
     * Remove uma ou mais chaves do cache.
     * @param chaves Um array de chaves a serem removidas.
     */
    @SuppressWarnings("unchecked")
    public void removerChaves(String... chaves) {
        if (chaves != null && chaves.length > 0) {
            if (chaves.length == 1) {
                redisTemplate.delete(chaves[0]);
            } else {
                redisTemplate.delete(CollectionUtils.arrayToList(chaves));
            }
        }
    }

    // ============================ Operações String =============================

    /**
     * Obtém o valor associado a uma chave do tipo String.
     * @param chave A chave.
     * @return O valor.
     */
    public Object obterValor(String chave) {
        return chave == null ? null : redisTemplate.opsForValue().get(chave);
    }

    /**
     * Define um valor para uma chave do tipo String.
     * @param chave A chave.
     * @param valor O valor.
     * @return true se bem-sucedido, false caso contrário.
     */
    public boolean definirValor(String chave, Object valor) {
        try {
            redisTemplate.opsForValue().set(chave, valor);
            return true;
        } catch (Exception e) {
            return false;
        }
    }

    /**
     * Define um valor para uma chave do tipo String com tempo de expiração.
     * @param chave A chave.
     * @param valor O valor.
     * @param tempo O tempo de expiração.
     * @param unidadeTempo A unidade de tempo.
     * @return true se bem-sucedido, false caso contrário.
     */
    public boolean definirValorComExpiracao(String chave, Object valor, long tempo, TimeUnit unidadeTempo) {
        try {
            if (tempo > 0) {
                redisTemplate.opsForValue().set(chave, valor, tempo, unidadeTempo);
            } else {
                definirValor(chave, valor);
            }
            return true;
        } catch (Exception e) {
            return false;
        }
    }

    /**
     * Incrementa o valor numérico de uma chave String.
     * @param chave A chave.
     * @param incremento O valor a ser adicionado (deve ser maior que 0).
     * @return O novo valor após o incremento.
     */
    public Long incrementarValor(String chave, long incremento) {
        if (incremento < 0) {
            throw new IllegalArgumentException("O incremento deve ser maior que 0.");
        }
        return redisTemplate.opsForValue().increment(chave, incremento);
    }

    /**
     * Decrementa o valor numérico de uma chave String.
     * @param chave A chave.
     * @param decremento O valor a ser subtraído (deve ser maior que 0).
     * @return O novo valor após o decremento.
     */
    public Long decrementarValor(String chave, long decremento) {
        if (decremento < 0) {
            throw new IllegalArgumentException("O decremento deve ser maior que 0.");
        }
        return redisTemplate.opsForValue().increment(chave, -decremento);
    }

    /**
     * Define o valor de uma chave se ela não existir, com um tempo de expiração.
     * Implementa o comportamento SET NX PX de forma idiomática com Spring Data Redis.
     * @param chave A chave.
     * @param valor O valor a ser definido (como String).
     * @param tempoExpiracaoMillis O tempo de expiração em milissegundos.
     * @return true se o valor foi definido, false caso contrário.
     */
    public boolean definirSeNaoExiste(String chave, String valor, long tempoExpiracaoMillis) {
        try {
            Boolean result = redisTemplate.execute((RedisConnection connection) -> {
                return connection.set(chave.getBytes(), valor.getBytes(),
                                     SetOption.ifAbsent(),
                                     Expiration.milliseconds(tempoExpiracaoMillis));
            });
            return Boolean.TRUE.equals(result);
        } catch (Exception e) {
            return false;
        }
    }
    
    /**
     * Obtém o valor de uma chave diretamente como String, sem serialização do RedisTemplate.
     * Útil para valores definidos por SET NX PX que são strings puras.
     * @param chave A chave.
     * @return O valor associado à chave como String, ou null se ocorrer um erro ou a chave não existir.
     */
    public String obterValorStringDireto(String chave) {
        try {
            return redisTemplate.execute((RedisConnection connection) -> {
                byte[] valueBytes = connection.get(chave.getBytes());
                return (valueBytes != null) ? new String(valueBytes) : null;
            });
        } catch (Exception e) {
            return null;
        }
    }

    // ================================ Operações Hash =================================

    /**
     * Obtém um valor de um campo dentro de um Hash.
     * @param chaveHash A chave do Hash.
     * @param campo O campo dentro do Hash.
     * @return O valor do campo.
     */
    public Object obterCampoHash(String chaveHash, String campo) {
        return redisTemplate.opsForHash().get(chaveHash, campo);
    }

    /**
     * Obtém todos os pares chave-valor de um Hash.
     * @param chaveHash A chave do Hash.
     * @return Um Map contendo todos os campos e seus valores.
     */
    public Map<Object, Object> obterTodosCamposHash(String chaveHash) {
        return redisTemplate.opsForHash().entries(chaveHash);
    }

    /**
     * Define múltiplos campos e valores em um Hash.
     * @param chaveHash A chave do Hash.
     * @param mapaValores Um Map de campos (String) e seus valores (Object).
     * @return true se bem-sucedido, false caso contrário.
     */
    public boolean definirMultiplosCamposHash(String chaveHash, Map<String, Object> mapaValores) {
        try {
            redisTemplate.opsForHash().putAll(chaveHash, mapaValores);
            return true;
        } catch (Exception e) {
            return false;
        }
    }

    /**
     * Define múltiplos campos e valores em um Hash com tempo de expiração para o Hash.
     * @param chaveHash A chave do Hash.
     * @param mapaValores Um Map de campos (String) e seus valores (Object).
     * @param tempo O tempo de expiração.
     * @param unidadeTempo A unidade de tempo.
     * @return true se bem-sucedido, false caso contrário.
     */
    public boolean definirMultiplosCamposHashComExpiracao(String chaveHash, Map<String, Object> mapaValores, long tempo, TimeUnit unidadeTempo) {
        try {
            redisTemplate.opsForHash().putAll(chaveHash, mapaValores);
            if (tempo > 0) {
                definirExpiracao(chaveHash, tempo, unidadeTempo);
            }
            return true;
        } catch (Exception e) {
            return false;
        }
    }

    /**
     * Define um campo e valor em um Hash. Se a chave do Hash não existir, ela será criada.
     * @param chaveHash A chave do Hash.
     * @param campo O campo.
     * @param valor O valor.
     * @return true se bem-sucedido, false caso contrário.
     */
    public boolean definirCampoHash(String chaveHash, String campo, Object valor) {
        try {
            redisTemplate.opsForHash().put(chaveHash, campo, valor);
            return true;
        } catch (Exception e) {
            return false;
        }
    }

    /**
     * Define um campo e valor em um Hash com tempo de expiração para a chave do Hash.
     * @param chaveHash A chave do Hash.
     * @param campo O campo.
     * @param valor O valor.
     * @param tempo O tempo de expiração.
     * @param unidadeTempo A unidade de tempo.
     * @return true se bem-sucedido, false caso contrário.
     */
    public boolean definirCampoHashComExpiracao(String chaveHash, String campo, Object valor, long tempo, TimeUnit unidadeTempo) {
        try {
            redisTemplate.opsForHash().put(chaveHash, campo, valor);
            if (tempo > 0) {
                definirExpiracao(chaveHash, tempo, unidadeTempo);
            }
            return true;
        } catch (Exception e) {
            return false;
        }
    }

    /**
     * Remove campos específicos de um Hash.
     * @param chaveHash A chave do Hash.
     * @param campos Um ou mais campos a serem removidos.
     */
    public void removerCamposHash(String chaveHash, Object... campos) {
        redisTemplate.opsForHash().delete(chaveHash, campos);
    }

    /**
     * Verifica se um campo existe em um Hash.
     * @param chaveHash A chave do Hash.
     * @param campo O campo.
     * @return true se o campo existe, false caso contrário.
     */
    public boolean campoHashExiste(String chaveHash, String campo) {
        return redisTemplate.opsForHash().hasKey(chaveHash, campo);
    }

    /**
     * Incrementa o valor numérico de um campo em um Hash.
     * @param chaveHash A chave do Hash.
     * @param campo O campo.
     * @param incremento O valor a ser adicionado.
     * @return O novo valor após o incremento.
     */
    public Double incrementarCampoHash(String chaveHash, String campo, double incremento) {
        return redisTemplate.opsForHash().increment(chaveHash, campo, incremento);
    }

    /**
     * Decrementa o valor numérico de um campo em um Hash.
     * @param chaveHash A chave do Hash.
     * @param campo O campo.
     * @param decremento O valor a ser subtraído.
     * @return O novo valor após o decremento.
     */
    public Double decrementarCampoHash(String chaveHash, String campo, double decremento) {
        return redisTemplate.opsForHash().increment(chaveHash, campo, -decremento);
    }

    // ============================ Operações Set =============================

    /**
     * Obtém todos os membros de um Set.
     * @param chaveSet A chave do Set.
     * @return Um Set contendo todos os membros, ou null em caso de erro.
     */
    public Set<Object> obterMembrosSet(String chaveSet) {
        try {
            return redisTemplate.opsForSet().members(chaveSet);
        } catch (Exception e) {
            return null;
        }
    }

    /**
     * Verifica se um membro existe em um Set.
     * @param chaveSet A chave do Set.
     * @param membro O membro.
     * @return true se o membro existe, false caso contrário.
     */
    public boolean setContemMembro(String chaveSet, Object membro) {
        try {
            return redisTemplate.opsForSet().isMember(chaveSet, membro);
        } catch (Exception e) {
            return false;
        }
    }

    /**
     * Adiciona um ou mais membros a um Set.
     * @param chaveSet A chave do Set.
     * @param membros Um ou mais membros.
     * @return O número de membros adicionados.
     */
    public Long adicionarMembrosSet(String chaveSet, Object... membros) {
        try {
            return redisTemplate.opsForSet().add(chaveSet, membros);
        } catch (Exception e) {
            return 0L;
        }
    }

    /**
     * Adiciona um ou mais membros a um Set com tempo de expiração para o Set.
     * @param chaveSet A chave do Set.
     * @param tempo O tempo de expiração.
     * @param unidadeTempo A unidade de tempo.
     * @param membros Um ou mais membros.
     * @return O número de membros adicionados.
     */
    public Long adicionarMembrosSetComExpiracao(String chaveSet, long tempo, TimeUnit unidadeTempo, Object... membros) {
        try {
            Long count = redisTemplate.opsForSet().add(chaveSet, membros);
            if (tempo > 0) {
                definirExpiracao(chaveSet, tempo, unidadeTempo);
            }
            return count;
        } catch (Exception e) {
            return 0L;
        }
    }

    /**
     * Obtém o número de membros em um Set.
     * @param chaveSet A chave do Set.
     * @return O tamanho do Set.
     */
    public Long obterTamanhoSet(String chaveSet) {
        try {
            return redisTemplate.opsForSet().size(chaveSet);
        } catch (Exception e) {
            return 0L;
        }
    }

    /**
     * Remove um ou mais membros de um Set.
     * @param chaveSet A chave do Set.
     * @param membros Um ou mais membros a serem removidos.
     * @return O número de membros removidos.
     */
    public Long removerMembrosSet(String chaveSet, Object... membros) {
        try {
            return redisTemplate.opsForSet().remove(chaveSet, membros);
        } catch (Exception e) {
            return 0L;
        }
    }

    // =============================== Operações List =================================

    /**
     * Obtém um subconjunto de elementos de uma lista.
     * @param chaveLista A chave da lista.
     * @param inicio O índice inicial (0 para o primeiro elemento).
     * @param fim O índice final (-1 para o último elemento).
     * @return Uma lista de objetos, ou null em caso de erro.
     */
    public List<Object> obterElementosLista(String chaveLista, long inicio, long fim) {
        try {
            return redisTemplate.opsForList().range(chaveLista, inicio, fim);
        } catch (Exception e) {
            return null;
        }
    }

    /**
     * Obtém o tamanho de uma lista.
     * @param chaveLista A chave da lista.
     * @return O tamanho da lista.
     */
    public Long obterTamanhoLista(String chaveLista) {
        try {
            return redisTemplate.opsForList().size(chaveLista);
        } catch (Exception e) {
            return 0L;
        }
    }

    /**
     * Obtém um elemento de uma lista pelo índice.
     * @param chaveLista A chave da lista.
     * @param indice O índice (0 para o primeiro, -1 para o último).
     * @return O elemento no índice especificado, ou null em caso de erro.
     */
    public Object obterElementoListaPorIndice(String chaveLista, long indice) {
        try {
            return redisTemplate.opsForList().index(chaveLista, indice);
        } catch (Exception e) {
            return null;
        }
    }

    /**
     * Adiciona um elemento ao final de uma lista (push à direita).
     * @param chaveLista A chave da lista.
     * @param valor O valor a ser adicionado.
     * @return true se bem-sucedido, false caso contrário.
     */
    public boolean adicionarElementoListaDireita(String chaveLista, Object valor) {
        try {
            redisTemplate.opsForList().rightPush(chaveLista, valor);
            return true;
        } catch (Exception e) {
            return false;
        }
    }

    /**
     * Adiciona um elemento ao final de uma lista com tempo de expiração para a lista.
     * @param chaveLista A chave da lista.
     * @param valor O valor a ser adicionado.
     * @param tempo O tempo de expiração.
     * @param unidadeTempo A unidade de tempo.
     * @return true se bem-sucedido, false caso contrário.
     */
    public boolean adicionarElementoListaDireitaComExpiracao(String chaveLista, Object valor, long tempo, TimeUnit unidadeTempo) {
        try {
            redisTemplate.opsForList().rightPush(chaveLista, valor);
            if (tempo > 0) {
                definirExpiracao(chaveLista, tempo, unidadeTempo);
            }
            return true;
        } catch (Exception e) {
            return false;
        }
    }

    /**
     * Adiciona múltiplos elementos ao final de uma lista (push à direita).
     * @param chaveLista A chave da lista.
     * @param valores Uma lista de valores a serem adicionados.
     * @return true se bem-sucedido, false caso contrário.
     */
    public boolean adicionarElementosListaDireita(String chaveLista, List<Object> valores) {
        try {
            redisTemplate.opsForList().rightPushAll(chaveLista, valores);
            return true;
        } catch (Exception e) {
            return false;
        }
    }

    /**
     * Adiciona múltiplos elementos ao final de uma lista com tempo de expiração para a lista.
     * @param chaveLista A chave da lista.
     * @param valores Uma lista de valores a serem adicionados.
     * @param tempo O tempo de expiração.
     * @param unidadeTempo A unidade de tempo.
     * @return true se bem-sucedido, false caso contrário.
     */
    public boolean adicionarElementosListaDireitaComExpiracao(String chaveLista, List<Object> valores, long tempo, TimeUnit unidadeTempo) {
        try {
            redisTemplate.opsForList().rightPushAll(chaveLista, valores);
            if (tempo > 0) {
                definirExpiracao(chaveLista, tempo, unidadeTempo);
            }
            return true;
        } catch (Exception e) {
            return false;
        }
    }

    /**
     * Atualiza um elemento em uma lista pelo índice.
     * @param chaveLista A chave da lista.
     * @param indice O índice do elemento a ser atualizado.
     * @param novoValor O novo valor.
     * @return true se bem-sucedido, false caso contrário.
     */
    public boolean atualizarElementoListaPorIndice(String chaveLista, long indice, Object novoValor) {
        try {
            redisTemplate.opsForList().set(chaveLista, indice, novoValor);
            return true;
        } catch (Exception e) {
            return false;
        }
    }

    /**
     * Remove 'contagem' ocorrências de 'valor' de uma lista.
     * @param chaveLista A chave da lista.
     * @param contagem O número de ocorrências a serem removidas.
     * @param valor O valor a ser removido.
     * @return O número de elementos removidos.
     */
    public Long removerElementosLista(String chaveLista, long contagem, Object valor) {
        try {
            return redisTemplate.opsForList().remove(chaveLista, contagem, valor);
        } catch (Exception e) {
            return 0L;
        }
    }
}

Tags: Spring MVC Redis Spring Data Redis Jedis cache

Publicado em 6-20 20:18