Redis ⚠️
PHP 中使用 Redis 极其简单:
Redis::get('key')。Java 中则相当繁琐,主要是静态类型 + Redis 命令返回值多样性导致的。
核心概念
| 概念 | PHP | Java |
|---|---|---|
| 客户端 | predis / phpredis | RedisTemplate<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:javaredisTemplate.setEnableTransactionSupport(true); // 然后用 @Transactional 注解
5. ⭐ PHP 和 Java 在 Redis 使用上的根本差异
- PHP:原生函数调一次就完事,简单直观
- Java:静态类型 + 序列化 + 操作分类,每个概念都是为了在大规模项目中约束行为
这相当于"JavaScript 的
==很方便"和"Java 的equals()很啰嗦"的区别——啰嗦的背后是严谨。团队规模越大,Java 的方式优势越明显。