Integração do Redis em projetos Java: Jedis, Spring Boot e serialização de objetos

  1. 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);
        }
    }
}

  1. 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);
    }
}

  1. 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.

Tags: Redis Jedis Lettuce Spring Boot Spring Data Redis

Publicado em 10-21 23:28