- Uso básico com Jedis (sem Spring)
Para acessar o Redis a partir de uma aplicação Java pura, a biblioteca mais tardicional é o Jedis. Além do JAR do Jedis, é necessário incluir o commons-pool2, que fornece o pool de conexões.
Exemplo de dependências (versões sugeridas):
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>5.1.2</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
<version>2.12.0</version>
</dependency>
Classe utilitária que encapsula a criação do pool e a obtenção de conexões:
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
public class RedisPoolHelper {
private static final String HOST = "127.0.0.1";
private static final int PORT = 6379;
private static final int TIMEOUT = 10_000;
private static final int MAX_TOTAL = 64;
private static final int MAX_IDLE = 16;
private static final long MAX_WAIT = 20_000L;
private static final JedisPool pool;
static {
JedisPoolConfig cfg = new JedisPoolConfig();
cfg.setMaxTotal(MAX_TOTAL);
cfg.setMaxIdle(MAX_IDLE);
cfg.setMaxWaitMillis(MAX_WAIT);
cfg.setTestOnBorrow(true);
pool = new JedisPool(cfg, HOST, PORT, TIMEOUT);
}
private RedisPoolHelper() {}
public static Jedis borrow() {
return pool.getResource();
}
}
Exemplo de uso com try-with-resources, garentindo que a conexão seja devolvida ao pool automaticamente:
import redis.clients.jedis.Jedis;
public class RedisExample {
private static final int DATABASE = 0;
public static void main(String[] args) {
try (Jedis client = RedisPoolHelper.borrow()) {
if (!"PONG".equals(client.ping())) {
System.err.println("Redis não respondeu corretamente");
return;
}
client.select(DATABASE);
client.set("usuario:1", "João");
String valor = client.get("usuario:1");
System.out.println("Valor recuperado: " + valor);
}
}
}
- Uso no Spring Boot
A partir do Spring Boot 2.x, o starter padrão utiliza o driver Lettuce. Para habilitar o pool de conexões, adicione também o commons-pool2.
2.1 Dependências Maven
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
2.2 Configuração no application.yml
spring:
redis:
host: 127.0.0.1
port: 6379
database: 0
timeout: 5000ms
lettuce:
pool:
max-active: 8
max-idle: 8
min-idle: 0
max-wait: -1ms
2.3 Personalizando a serialização
O padrão do RedisTemplate usa serialização binária baseada em JDK, o que dificulta a leitura dos dados no Redis e pode causar problemas com objetos complexos. A configuração abaixo utiliza chaves como String e valores serializados em JSON com Jackson:
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
@Configuration
public class RedisSerializationConfig {
@Bean
public RedisTemplate<String, Object> redisTemplate(
RedisConnectionFactory connectionFactory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(connectionFactory);
ObjectMapper mapper = new ObjectMapper();
mapper.findAndRegisterModules();
Jackson2JsonRedisSerializer<Object> jsonSerializer =
new Jackson2JsonRedisSerializer<>(Object.class);
jsonSerializer.setObjectMapper(mapper);
template.setKeySerializer(new StringRedisSerializer());
template.setValueSerializer(jsonSerializer);
template.setHashKeySerializer(new StringRedisSerializer());
template.setHashValueSerializer(jsonSerializer);
template.afterPropertiesSet();
return template;
}
}
2.4 Serviço de cache
Com a configuração anterior, cria-se um componente que abstrai as operações mais comuns:
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import java.util.concurrent.TimeUnit;
@Component
public class RedisCache {
private final RedisTemplate<String, Object> redisTemplate;
public RedisCache(RedisTemplate<String, Object> redisTemplate) {
this.redisTemplate = redisTemplate;
}
public boolean isAlive() {
String response = redisTemplate.execute(
(connection) -> connection.ping());
return "PONG".equals(response);
}
public boolean set(String key, Object value) {
try {
redisTemplate.opsForValue().set(key, value);
return true;
} catch (RuntimeException ex) {
return false;
}
}
public boolean set(String key, Object value, long seconds) {
try {
redisTemplate.opsForValue().set(key, value, seconds, TimeUnit.SECONDS);
return true;
} catch (RuntimeException ex) {
return false;
}
}
public Object get(String key) {
return redisTemplate.opsForValue().get(key);
}
public boolean hasKey(String key) {
return Boolean.TRUE.equals(redisTemplate.hasKey(key));
}
public boolean expire(String key, long seconds) {
return Boolean.TRUE.equals(redisTemplate.expire(key, seconds, TimeUnit.SECONDS));
}
public boolean delete(String key) {
return Boolean.TRUE.equals(redisTemplate.delete(key));
}
public void hashPut(String key, String field, Object value) {
redisTemplate.opsForHash().put(key, field, value);
}
public Object hashGet(String key, String field) {
return redisTemplate.opsForHash().get(key, field);
}
public Long listRightPush(String key, Object value) {
return redisTemplate.opsForList().rightPush(key, value);
}
public Object listRangeFirst(String key) {
return redisTemplate.opsForList().index(key, 0);
}
}
2.5 Exemplo de uso
import org.springframework.stereotype.Service;
@Service
public class ClienteService {
private final RedisCache cache;
public ClienteService(RedisCache cache) {
this.cache = cache;
}
public void salvarCliente(String id, String nome) {
if (!cache.isAlive()) {
throw new IllegalStateException("Redis indisponível");
}
cache.set("cliente:" + id, nome, 300);
Object cached = cache.get("cliente:" + id);
System.out.println("Cliente em cache: " + cached);
}
}
- Armazenando e recuperando objetos
Objetos Java podem ser persistidos no Redis de várias formas. Abaixo está uma alternativa via serialização nativa, convertendo o objeto em um array de bytes e armazenando como valor binário:
import java.io.*;
public class ObjectSerializer {
public static byte[] toBytes(Serializable target) {
try (ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos)) {
oos.writeObject(target);
return baos.toByteArray();
} catch (IOException ex) {
throw new RuntimeException("Falha ao serializar objeto", ex);
}
}
public static Object fromBytes(byte[] data) {
try (ByteArrayInputStream bais = new ByteArrayInputStream(data);
ObjectInputStream ois = new ObjectInputStream(bais)) {
return ois.readObject();
} catch (IOException | ClassNotFoundException ex) {
throw new RuntimeException("Falha ao desserializar objeto", ex);
}
}
}
Exemplo prático de gravação e leitura com Jedis:
import redis.clients.jedis.Jedis;
import java.nio.charset.StandardCharsets;
public class ObjectRedisExample {
public static void main(String[] args) {
try (Jedis jedis = RedisPoolHelper.borrow()) {
Cliente cliente = new Cliente(1L, "Maria Silva");
byte[] chave = "cliente:1".getBytes(StandardCharsets.UTF_8);
jedis.set(chave, ObjectSerializer.toBytes(cliente));
byte[] raw = jedis.get(chave);
Cliente recovered = (Cliente) ObjectSerializer.fromBytes(raw);
System.out.println("Cliente recuperado: " + recovered.getNome());
}
}
}
Para evitar a restrição de Serializable e facilitar a lietura por outras aplicações, a alternativa recomendada é serializar o objeto como JSON e armazená-lo como String, aproveitando o Jackson2JsonRedisSerializer apresentado na seção anterior.