Redis Java 教程:从入门到实战
本教程将教你如何使用 Java 语言与 Redis 进行交互,涵盖连接、基本操作、数据类型、连接池、Spring Boot 集成以及一些高级用法。

目录
- 准备工作
- 安装并启动 Redis
- 配置 Java 开发环境
- 添加 Redis Java 客户端依赖
- 建立与 Redis 的连接
- 使用 Jedis
- 使用 Lettuce (推荐)
- Redis 基本数据类型操作
- String (字符串)
- Hash (哈希)
- List (列表)
- Set (集合)
- Sorted Set (有序集合)
- 其他常用命令
- Key (键) 操作
- TTL (生存时间)
- 高级特性
- 管道
- 事务
- 发布/订阅
- 连接池配置 (最佳实践)
- Jedis 连接池
- Lettuce 连接池
- 在 Spring Boot 中使用 Redis
- 添加依赖
- 配置
application.properties - 使用
@Cacheable注解实现缓存 - 使用
RedisTemplate进行编程式操作
- 总结与最佳实践
准备工作
1 安装并启动 Redis
如果你还没有安装 Redis,请先完成安装。
- Linux/macOS: 大多通过包管理器安装,如
sudo apt-get install redis-server。 - Windows: 可以使用 WSL (Windows Subsystem for Linux) 或下载预编译的二进制文件。
安装完成后,启动 Redis 服务器:
redis-server
为了方便测试,你可以打开另一个终端,启动 Redis 客户端:
redis-cli
2 配置 Java 开发环境
确保你已经安装了 JDK (建议 8 或以上版本) 和 Maven 或 Gradle。

3 添加 Redis Java 客户端依赖
目前最主流的 Redis Java 客户端有两个:Jedis 和 Lettuce。
- Jedis: 一个轻量级、直接的网络通信库,API 比较直接,但连接管理需要自己处理(通常用连接池)。
- Lettuce: 基于 Netty 的、更现代化的客户端,支持同步、异步和响应式编程,内置了连接池管理,是目前更推荐的选择。
这里我们以 Maven 为例,添加 Lettuce 依赖,如果你使用 Gradle,build.gradle 文件中的配置类似。
Maven (pom.xml):
<dependencies>
<!-- Lettuce 客户端 -->
<dependency>
<groupId>io.lettuce</groupId>
<artifactId>lettuce-core</artifactId>
<version>6.2.6.RELEASE</version> <!-- 建议使用最新稳定版 -->
</dependency>
<!-- 为了方便测试,我们添加 JUnit -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>5.9.2</version>
<scope>test</scope>
</dependency>
</dependencies>
建立与 Redis 的连接
我们将重点介绍 Lettuce 的用法,因为它更现代且功能强大。
1 使用 Lettuce
Lettuce 使用 RedisClient 和 RedisCommands 接口来操作 Redis。
import io.lettuce.core.RedisClient;
import io.lettuce.core.RedisURI;
import io.lettuce.core.api.StatefulRedisConnection;
import io.lettuce.core.api.sync.RedisCommands;
public class LettuceExample {
public static void main(String[] args) {
// 1. 创建 RedisURI,指定连接信息
RedisURI redisUri = RedisURI.create("redis://localhost:6379");
// 2. 创建 RedisClient 客户端
RedisClient redisClient = RedisClient.create(redisUri);
// 3. 获取一个连接
// 注意:每次连接和关闭都有开销,实际应用中应使用连接池
StatefulRedisConnection<String, String> connection = redisClient.connect();
// 4. 获取同步操作的命令接口
RedisCommands<String, String> syncCommands = connection.sync();
// 5. 执行命令
syncCommands.set("user:1001:name", "张三");
String name = syncCommands.get("user:1001:name");
System.out.println("获取到的名字是: " + name);
// 6. 关闭连接
connection.close();
redisClient.shutdown();
}
}
运行这段代码,你应该能在控制台看到 获取到的名字是: 张三。
Redis 基本数据类型操作
Lettuce 的 API 设计非常直观,命令名称和 Redis 原生命名基本一致。
1 String (字符串)
字符串是 Redis 最基本的数据类型。
// 设置值,如果键已存在则覆盖
syncCommands.set("key1", "hello redis");
// 获取值
String value = syncCommands.get("key1");
System.out.println(value); // 输出: hello redis
// 追加值
syncCommands.append("key1", "!");
System.out.println(syncCommands.get("key1")); // 输出: hello redis!
// 获取字符串长度
long len = syncCommands.strlen("key1");
System.out.println(len); // 输出: 12
// 设置并获取旧值
String oldValue = syncCommands.getset("key1", "new value");
System.out.println(oldValue); // 输出: hello redis!
2 Hash (哈希)
Hash 是一个键值对集合,适合存储对象。
// 设置单个字段
syncCommands.hset("user:1001", "name", "李四");
syncCommands.hset("user:1001", "age", "25");
syncCommands.hset("user:1001", "city", "北京");
// 获取单个字段
String name = syncCommands.hget("user:1001", "name");
System.out.println(name); // 输出: 李四
// 获取所有字段和值
Map<String, String> allFields = syncCommands.hgetall("user:1001");
System.out.println(allFields);
// 输出: {name=李四, age=25, city=北京}
// 获取所有字段
List<String> fields = syncCommands.hkeys("user:1001");
System.out.println(fields); // 输出: [name, age, city]
// 获取所有值
List<String> values = syncCommands.hvals("user:1001");
System.out.println(values); // 输出: [李四, 25, 北京]
// 删除字段
syncCommands.hdel("user:1001", "city");
3 List (列表)
列表是简单的字符串列表,按插入顺序排序,可以充当栈或队列。
// 将元素推入列表左侧 (LPUSH)
syncCommands.lpush("mylist", "world", "hello");
// 获取列表范围 (0 到 -1 表示全部)
List<String> myList = syncCommands.lrange("mylist", 0, -1);
System.out.println(myList); // 输出: [hello, world] (注意顺序,因为是从左推入)
// 获取列表长度
long len = syncCommands.llen("mylist");
System.out.println(len); // 输出: 2
// 从右侧弹出元素 (RPOP)
String element = syncCommands.rpop("mylist");
System.out.println(element); // 输出: world
4 Set (集合)
集合是无序的、唯一的字符串集合。
// 添加元素 (如果元素已存在,则忽略)
syncCommands.sadd("myset", "apple", "banana", "orange", "apple");
// 获取集合中所有成员
Set<String> mySet = syncCommands.smembers("myset");
System.out.println(mySet); // 输出: [orange, banana, apple] (顺序不固定)
// 检查元素是否存在
boolean exists = syncCommands.sismember("myset", "banana");
System.out.println(exists); // 输出: true
// 获取集合大小
long size = syncCommands.scard("myset");
System.out.println(size); // 输出: 3
// 移除元素
syncCommands.srem("myset", "apple");
5 Sorted Set (有序集合)
有序集合是 Set 的升级版,每个元素都会关联一个 double 类型的分数,Redis 通过分数来为集合中的成员进行从小到大的排序。
// 添加成员及其分数
syncCommands.zadd("zset1", 100, "member1");
syncCommands.zadd("zset1", 200, "member2");
syncCommands.zadd("zset1", 50, "member3");
// 获取排名区间的成员 (从低到高)
// ZRANGE zset1 0 -1
Set<String> lowToHigh = syncCommands.zrange("zset1", 0, -1);
System.out.println(lowToHigh); // 输出: [member3, member1, member2]
// 获取排名区间的成员及其分数 (WITHSCORES)
// ZRANGE zset1 0 -1 WITHSCORES
Map<String, Double> lowToHighWithScores = syncCommands.zrangeWithScores("zset1", 0, -1);
System.out.println(lowToHighWithScores);
// 输出: {member3=50.0, member1=100.0, member2=200.0}
// 获取分数区间的成员 (ZRANGEBYSCORE)
// ZRANGEBYSCORE zset1 60 150
Set<String> byScoreRange = syncCommands.zrangebyscore("zset1", 60, 150);
System.out.println(byScoreRange); // 输出: [member1, member2]
// 获取成员的排名 (从低到高)
long rank = syncCommands.zrank("zset1", "member1");
System.out.println(rank); // 输出: 1
其他常用命令
1 Key (键) 操作
// 检查键是否存在
boolean exists = syncCommands.exists("key1");
System.out.println(exists);
// 删除键
syncCommands.del("key1");
// 设置键的过期时间 (单位: 秒)
syncCommands.setex("mykey", 10, "this will expire in 10 seconds");
// 查找所有符合模式的键 (注意:生产环境慎用 KEYS 命令,可能会阻塞服务器)
// 使用 SCAN 命令代替
Set<String> allKeys = syncCommands.keys("*");
System.out.println(allKeys);
2 TTL (生存时间)
TTL 命令用于查看一个键的剩余生存时间(以秒为单位)。
// 设置一个有过期时间的键
syncCommands.setex("temp_key", 60, "value");
// 查看剩余时间
// -2 表示键不存在
// -1 表示键存在但没有设置过期时间
long ttl = syncCommands.ttl("temp_key");
System.out.println("剩余时间: " + ttl + " 秒");
// 移除过期时间
syncCommands.persist("temp_key");
long newTtl = syncCommands.ttl("temp_key");
System.out.println("移除过期时间后的TTL: " + newTtl); // 输出: -1
高级特性
1 管道
管道可以一次性发送多个命令到 Redis 服务器,并一次性获取所有结果,减少了网络往返次数,大大提高了性能。
StatefulRedisConnection<String, String> connection = redisClient.connect();
RedisCommands<String, String> syncCommands = connection.sync();
// 使用 pipeline
RedisClusterCommands<String, String> pipeline = connection.multi(); // 对于单机版,使用 multi() 或直接使用 syncCommands 的批量操作
// 在实际应用中,Lettuce 的 Pipeline 使用方式如下:
// 你可以累积命令,然后在一个事务中执行,或者使用异步命令来提高吞吐量。
// 这里展示一个简单的批量执行方式。
List<String> keys = List.of("key1", "key2", "key3");
List<String> values = List.of("val1", "val2", "val3");
// 批量设置
for (int i = 0; i < keys.size(); i++) {
syncCommands.set(keys.get(i), values.get(i));
}
// 批量获取
List<String> results = new ArrayList<>();
for (String key : keys) {
results.add(syncCommands.get(key));
}
System.out.println("批量获取结果: " + results);
// 更高级的用法是使用异步命令或事务
connection.close();
2 事务
Redis 事务可以一次性执行多个命令,这些命令会被序列化、按顺序执行,并且在执行期间不会被其他客户端的命令打断。
// MULTI 标记事务开始
// EXEC 执行事务中的所有命令
// DISCARD 取消事务
syncCommands.multi(); // 发送 MULTI 命令
syncCommands.set("tx_key1", "tx_val1");
syncCommands.set("tx_key2", "tx_val2");
syncCommands.incr("tx_counter"); // 假设 tx_counter 是一个数字
// 发送 EXEC 命令,执行事务
List<Object> results = syncCommands.exec(); // 结果是一个列表,按顺序对应每个命令的返回值
System.out.println("事务执行结果: " + results);
// 输出可能类似: [OK, OK, 1] (tx_counter 原来是 0)
3 发布/订阅
发布/订阅是一种消息通信模式,发送者(发布者)发送消息,而订阅者接收消息。
发布者代码:
StatefulRedisConnection<String, String> pubConnection = redisClient.connect();
RedisCommands<String, String> pubCommands = pubConnection.sync();
System.out.println("发布者启动,正在向 'news' 频道发送消息...");
pubCommands.publish("news", "今天天气不错!");
pubCommands.publish("news", "Redis 教程更新了!");
pubCommands.publish("sports", "中国队赢了!");
pubConnection.close();
订阅者代码 (需要单独运行):
StatefulRedisConnection<String, String> subConnection = redisClient.connect();
RedisPubSubListener<String, String> listener = new RedisPubSubListener<String, String>() {
@Override
public void message(String channel, String message) {
System.out.println("收到来自频道 '" + channel + "' 的消息: " + message);
}
@Override
public void subscribed(String channel, long count) {
System.out.println("订阅了频道: " + channel);
}
@Override
public void unsubscribed(String channel, long count) {
System.out.println("取消订阅了频道: " + channel);
}
};
// 订阅一个或多个频道
subConnection.sync().subscribe(listener, "news");
// 为了保持订阅,主线程不能退出,这里让线程睡眠一会儿。
try {
Thread.sleep(10000); // 睡眠10秒,接收消息
} catch (InterruptedException e) {
e.printStackTrace();
}
// 取消订阅
subConnection.sync().unsubscribe("news");
subConnection.close();
连接池配置 (最佳实践)
每次操作都创建和关闭连接是非常低效的,在实际应用中,必须使用连接池。
1 Lettuce 连接池
Lettuce 从 5.1 版本开始,官方推荐使用 LettuceConnectionFactory,它会自动管理连接池。
如果你想在纯 Java 环境中使用连接池,可以这样配置:
import io.lettuce.core.RedisClient;
import io.lettuce.core.RedisURI;
import io.lettuce.core.api.StatefulRedisConnection;
import io.lettuce.core.support.ConnectionPoolSupport;
import org.apache.commons.pool2.impl.GenericObjectPool;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
public class LettucePoolExample {
public static void main(String[] args) {
RedisURI redisUri = RedisURI.create("redis://localhost:6379");
RedisClient redisClient = RedisClient.create(redisUri);
// 1. 创建连接池配置
GenericObjectPoolConfig<StatefulRedisConnection<String, String>> poolConfig = new GenericObjectPoolConfig<>();
poolConfig.setMaxTotal(20); // 最大连接数
poolConfig.setMaxIdle(10); // 最大空闲连接数
poolConfig.setMinIdle(5); // 最小空闲连接数
// 2. 创建连接池
GenericObjectPool<StatefulRedisConnection<String, String>> connectionPool =
ConnectionPoolSupport.createGenericObjectPool(() -> redisClient.connect(), poolConfig);
try (StatefulRedisConnection<String, String> connection = connectionPool.borrowObject()) {
RedisCommands<String, String> commands = connection.sync();
commands.set("pool_key", "pool_value");
System.out.println("从连接池获取并操作: " + commands.get("pool_key"));
} catch (Exception e) {
e.printStackTrace();
} finally {
// 3. 关闭连接池和客户端
connectionPool.close();
redisClient.shutdown();
}
}
}
注意: 上面的例子使用了 Apache Commons Pool2,Spring Boot 已经为你封装好了这一切,无需手动配置。
在 Spring Boot 中使用 Redis
Spring Boot 对 Redis 的集成非常友好,是开发中最高效的方式。
1 添加依赖
在 pom.xml 中添加 spring-boot-starter-data-redis,它会自动为你配置好 Lettuce 和连接池。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
2 配置 application.properties
在 src/main/resources/application.properties 中配置 Redis 连接信息。
# Redis服务器地址 spring.redis.host=localhost # Redis服务器连接端口 spring.redis.port=6379 # Redis服务器连接密码(默认为空) # spring.redis.password= # 连接池配置 spring.redis lettuce.pool.max-active=20 spring.redis lettuce.pool.max-idle=10 spring.redis lettuce.pool.min-idle=5
3 使用 @Cacheable 注解实现缓存
Spring Cache 提供了基于注解的缓存功能,非常方便。
-
在主启动类上添加
@EnableCachingimport org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cache.annotation.EnableCaching; @SpringBootApplication @EnableCaching // 启用缓存 public class MyApplication { public static void main(String[] args) { SpringApplication.run(MyApplication.class, args); } } -
创建一个 Service
import org.springframework.cache.annotation.Cacheable; import org.springframework.stereotype.Service; @Service public class UserService { // 模拟从数据库查询 public User getUserFromDB(String userId) { System.out.println("正在从数据库查询用户: " + userId); // 模拟耗时操作 try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } if ("1001".equals(userId)) { return new User("1001", "张三"); } return null; } // @Cacheable 注解会拦截方法调用 // 如果缓存中有,则直接返回缓存结果,不执行方法体 // 如果缓存中没有,则执行方法体,并将返回值存入缓存 // cacheNames 指定缓存的名称,key 指定缓存的键 @Cacheable(cacheNames = "users", key = "#userId") public User getUser(String userId) { return getUserFromDB(userId); } } class User { private String id; private String name; // ... 构造方法, getter, setter public User(String id, String name) { this.id = id; this.name = name; } @Override public String toString() { return "User{" + "id='" + id + '\'' + ", name='" + name + '\'' + '}'; } } -
测试
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.CommandLineRunner; import org.springframework.stereotype.Component; @Component public class MyRunner implements CommandLineRunner { @Autowired private UserService userService; @Override public void run(String... args) throws Exception { System.out.println("--- 第一次调用,会执行方法体 ---"); User user1 = userService.getUser("1001"); System.out.println("获取到用户: " + user1); System.out.println("\n--- 第二次调用,会从缓存中读取,不会执行方法体 ---"); User user2 = userService.getUser("1001"); System.out.println("获取到用户: " + user2); } }
运行程序,你会发现第一次调用会打印 "正在从数据库查询用户...",而第二次则不会,说明缓存生效了。
4 使用 RedisTemplate 进行编程式操作
当你需要更精细的控制时,可以直接注入 RedisTemplate。
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Map;
import java.util.Set;
@Service
public class RedisTemplateService {
@Autowired
private RedisTemplate<String, Object> redisTemplate; // Spring Boot 默认配置的 RedisTemplate
public void operate() {
// 操作 String
redisTemplate.opsForValue().set("template:msg", "Hello RedisTemplate");
System.out.println(redisTemplate.opsForValue().get("template:msg"));
// 操作 Hash
redisTemplate.opsForHash().put("template:user:1001", "name", "王五");
redisTemplate.opsForHash().put("template:user:1001", "age", 30);
Map<Object, Object> user = redisTemplate.opsForHash().entries("template:user:1001");
System.out.println(user);
// 操作 List
redisTemplate.opsForList().leftPushAll("template:list", "a", "b", "c");
List<Object> list = redisTemplate.opsForList().range("template:list", 0, -1);
System.out.println(list);
// 操作 Set
redisTemplate.opsForSet().add("template:set", "apple", "banana", "orange");
Set<Object> set = redisTemplate.opsForSet().members("template:set");
System.out.println(set);
}
}
总结与最佳实践
- 选择合适的客户端:对于新项目,优先选择 Lettuce,它功能更现代,支持异步和响应式编程。
- 使用连接池:无论使用哪个客户端,在生产环境中都必须使用连接池,避免频繁创建和销毁连接带来的性能开销,Spring Boot 已经为你做好了配置。
- 为 Key 设计合理的命名规范:例如使用 分隔,如
user:1001:name,这能让 Redis 的内存结构更清晰,也方便通过KEYS命令(或更安全的SCAN)进行管理。 - 注意数据类型的选择:根据业务场景选择最合适的数据类型,例如用 Hash 存储对象,用 List 实现队列,用 Sorted Set 实现排行榜。
- 合理设置 TTL:为缓存数据设置合理的过期时间,防止数据长期占用内存。
- 慎用
KEYS命令:KEYS *会阻塞 Redis 服务器,在生产环境中应使用SCAN命令进行迭代式的查找。 - 利用 Spring Cache:在 Spring 生态中,优先使用
@Cacheable等注解,可以极大地简化缓存逻辑,让代码更专注于业务。
希望这份详细的教程能帮助你顺利地在 Java 项目中使用 Redis!
