Skip to content

Redis ⚠️

PHP 中使用 Redis 极其简单:Redis::get('key')。Java 中则相当繁琐,主要是静态类型 + Redis 命令返回值多样性导致的。


核心概念

概念PHPJava
客户端predis / phpredisRedisTemplate<String, Object>
基本操作Redis::get('key')redisTemplate.opsForValue().get("key")
序列化自动(字符串)需要配置(Jdk/Jackson/String)
连接配置写在 .env写在 application.yml + 配置类

配置

yaml
# application.yml
spring:
  data:
    redis:
      host: 127.0.0.1
      port: 6379
      password:
      timeout: 3000ms
      lettuce:
        pool:
          max-active: 16   # 最大连接数(类比 PHP-FPM 的 pm.max_children)
          max-idle: 8
          min-idle: 4

配置完即可注入 RedisTemplate。不需要手动 new Redis()


基础操作

java
@Service
public class CacheService {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    public void set(String key, Object value) {
        // PHP: Redis::set('key', 'value')
        redisTemplate.opsForValue().set(key, value);
    }

    public Object get(String key) {
        // PHP: Redis::get('key')
        return redisTemplate.opsForValue().get(key);
    }

    public void delete(String key) {
        redisTemplate.delete(key);
    }

    public boolean hasKey(String key) {
        return Boolean.TRUE.equals(redisTemplate.hasKey(key));
    }
}

⚠️ 为什么这么啰嗦?

原因说明
静态类型Redis 的返回值可能是 String / List / Set / Map / Long,Java 必须提前知道你要操作什么类型
操作分类opsForValue() / opsForList() / opsForSet() / opsForHash() 分别对应不同的 Redis 数据结构。PHP 中 Redis::lpush() 直接调,Java 需要先拿到对应的操作对象
序列化Java 对象不能直接存到 Redis,需要先转成字节或 JSON。RedisTemplate 默认用 JDK 序列化,序列化后的值人类不可读

操作不同类型

java
// String(对应 PHP 的 Redis::set / get)
redisTemplate.opsForValue().set("key", "value");

// List(对应 PHP 的 Redis::lpush / rpop)
redisTemplate.opsForList().leftPush("list", "a");
redisTemplate.opsForList().range("list", 0, -1);

// Set(对应 PHP 的 Redis::sadd / smembers)
redisTemplate.opsForSet().add("set", "a", "b", "c");
redisTemplate.opsForSet().members("set");

// Hash(对应 PHP 的 Redis::hset / hget)
redisTemplate.opsForHash().put("hash", "field", "value");
redisTemplate.opsForHash().get("hash", "field");

// 设置过期时间
redisTemplate.expire("key", 3600, TimeUnit.SECONDS);

// 原子递增(对应 PHP 的 Redis::incr)
redisTemplate.opsForValue().increment("counter");

配置序列化器(解决值不可读问题)

java
@Configuration
public class RedisConfig {

    @Bean
    public RedisTemplate<String, Object> redisTemplate(
            RedisConnectionFactory factory) {

        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(factory);

        // JSON 序列化(替代默认的 JDK 序列化)
        Jackson2JsonRedisSerializer<Object> jsonSerializer =
            new Jackson2JsonRedisSerializer<>(Object.class);

        // Key 用 String 序列化
        template.setKeySerializer(new StringRedisSerializer());
        // Value 用 JSON 序列化
        template.setValueSerializer(jsonSerializer);
        // Hash 的 Key/Value 也统一
        template.setHashKeySerializer(new StringRedisSerializer());
        template.setHashValueSerializer(jsonSerializer);

        template.afterPropertiesSet();
        return template;
    }
}

不配置的话,redisTemplate.opsForValue().get("key") 返回的是二进制乱码,因为你存的时候是 JDK 序列化,读的时候也是 JDK 反序列化,实际上程序能正常工作,只是用 redis-cli 看是人眼不可读的。


StringRedisTemplate(纯字符串场景)

如果只存字符串(不用存 Java 对象),直接用 StringRedisTemplate,省掉序列化配置:

java
@Service
public class SessionService {

    @Autowired
    private StringRedisTemplate stringRedis;

    public void setToken(String userId, String token) {
        // PHP: Redis::setex("token:123", 3600, "abc")
        stringRedis.opsForValue()
            .set("token:" + userId, token, 3600, TimeUnit.SECONDS);
    }

    public String getToken(String userId) {
        return stringRedis.opsForValue().get("token:" + userId);
    }
}

缓存注解(少写代码)

java
@Service
public class UserService {

    @Cacheable(value = "users", key = "#id")                 // 类比 Cache::remember("users." . $id, 3600, fn())
    public User findById(Long id) {
        return userRepository.findById(id)
            .orElseThrow(() -> new RuntimeException("not found"));
    }

    @CacheEvict(value = "users", key = "#id")                // 类比 Cache::forget("users." . $id)
    public void delete(Long id) {
        userRepository.deleteById(id);
    }

    @CachePut(value = "users", key = "#user.id")             // 类比 Cache::put("users." . $user->id, $user, 3600)
    public User update(User user) {
        return userRepository.save(user);
    }
}

需要 @EnableCaching 开启:

java
@SpringBootApplication
@EnableCaching
public class DemoApplication { ... }

⚠️ 常见坑

1. opsForXxx 每次调用都创建新对象?不是的。

java
// 同一个 RedisTemplate 实例,多次调用 opsForValue() 拿到的是不同的操作对象
// 但它们是轻量级的,每次调用都创建新对象是正常行为

2. RedisTemplate 的泛型参数决定了行为

java
RedisTemplate<String, String>   // Key 和 Value 都是 String
RedisTemplate<String, Object>   // Key 是 String,Value 是 Object(需 JSON 序列化)
RedisTemplate<Object, Object>   // 全用 JDK 序列化(不推荐,不可读)

3. @Cacheable 的 key 用的是 SpEL 表达式

java
@Cacheable(value = "users", key = "#id")              // 参数 id
@Cacheable(value = "users", key = "#user.id")         // 对象属性
@Cacheable(value = "users", key = "'all_' + #page")   // 拼接

SpEL 是 Spring Expression Language,类比 PHP 的双引号字符串变量插值 ${},但语法不同。

4. 事务中的 Redis 操作

Spring Redis 默认不支持事务,每次操作都是独立连接。如果你需要 MULTI / EXEC

java
redisTemplate.setEnableTransactionSupport(true);
// 然后用 @Transactional 注解

5. ⭐ PHP 和 Java 在 Redis 使用上的根本差异

  • PHP:原生函数调一次就完事,简单直观
  • Java:静态类型 + 序列化 + 操作分类,每个概念都是为了在大规模项目中约束行为

这相当于"JavaScript 的 == 很方便"和"Java 的 equals() 很啰嗦"的区别——啰嗦的背后是严谨。团队规模越大,Java 的方式优势越明显。

面向 PHP 开发者的 Spring Boot 文档