Appearance
Spring Data Redis
1. Redis简介
Redis (Remote Dictionary Server) 是一个开源的内存数据结构存储系统,可用作数据库、缓存和消息代理。它支持多种数据类型,如字符串、哈希、列表、集合、有序集合、位图、HyperLogLog等。
2. Spring Data Redis概述
Spring Data Redis是Spring Data家族的一部分,提供了与Redis交互的高级抽象,简化了Redis操作的代码编写,并将Redis集成到Spring应用程序中。它提供了一致的Spring编程模型,支持原生Redis数据类型操作,同时保留了Redis的特性。
3. 主要特性
- 连接管理:支持多种Redis客户端(Lettuce, Jedis)
- 操作抽象:提供RedisTemplate,简化Redis数据操作
- 自动序列化/反序列化:对象与Redis数据类型的自动转换
- Redis Repositories:类似JPA Repository的抽象
- 分布式锁:基于Redis的分布式锁实现
- 缓存集成:与Spring的缓存抽象集成
- 发布/订阅支持:对Redis pub/sub的原生支持
- 事务支持:Redis命令事务处理
4. 依赖配置
Maven
xml
<!-- Spring Boot方式 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- 使用lettuce作为客户端 -->
<dependency>
<groupId>io.lettuce</groupId>
<artifactId>lettuce-core</artifactId>
</dependency>
<!-- 非Spring Boot方式 -->
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-redis</artifactId>
<version>3.1.0</version>
</dependency>
5. Redis连接配置
Spring Boot配置
properties
# application.properties
spring.redis.host=localhost
spring.redis.port=6379
spring.redis.password=
spring.redis.database=0
spring.redis.lettuce.pool.max-active=8
spring.redis.lettuce.pool.max-idle=8
spring.redis.lettuce.pool.min-idle=0
spring.redis.lettuce.pool.max-wait=-1ms
手动配置RedisConnectionFactory
java
@Configuration
public class RedisConfig {
@Bean
public LettuceConnectionFactory redisConnectionFactory() {
return new LettuceConnectionFactory(new RedisStandaloneConfiguration("localhost", 6379));
}
// 或使用集群配置
@Bean
public LettuceConnectionFactory redisClusterConnectionFactory() {
RedisClusterConfiguration clusterConfig = new RedisClusterConfiguration();
clusterConfig.addClusterNode(new RedisNode("127.0.0.1", 6379));
clusterConfig.addClusterNode(new RedisNode("127.0.0.1", 6380));
// 添加更多节点...
return new LettuceConnectionFactory(clusterConfig);
}
// 配置连接池
@Bean
public LettuceConnectionFactory redisConnectionFactoryWithPool() {
LettucePoolingClientConfiguration clientConfig = LettucePoolingClientConfiguration.builder()
.commandTimeout(Duration.ofSeconds(2))
.poolConfig(poolConfig())
.build();
return new LettuceConnectionFactory(
new RedisStandaloneConfiguration("localhost", 6379),
clientConfig
);
}
private GenericObjectPoolConfig poolConfig() {
GenericObjectPoolConfig config = new GenericObjectPoolConfig();
config.setMaxTotal(8);
config.setMaxIdle(8);
config.setMinIdle(0);
return config;
}
}
6. RedisTemplate配置
java
@Configuration
public class RedisTemplateConfig {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(connectionFactory);
// 使用Jackson2JsonRedisSerializer序列化对象
Jackson2JsonRedisSerializer<Object> serializer = new Jackson2JsonRedisSerializer<>(Object.class);
ObjectMapper mapper = new ObjectMapper();
mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
mapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance,
ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
serializer.setObjectMapper(mapper);
// 设置String键的序列化方式
template.setKeySerializer(new StringRedisSerializer());
// 设置String值的序列化方式
template.setValueSerializer(serializer);
// 设置Hash键的序列化方式
template.setHashKeySerializer(new StringRedisSerializer());
// 设置Hash值的序列化方式
template.setHashValueSerializer(serializer);
template.afterPropertiesSet();
return template;
}
// 如果只需要处理String类型
@Bean
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory connectionFactory) {
return new StringRedisTemplate(connectionFactory);
}
}
7. 基本操作
字符串操作
java
@Service
public class StringOperationsExample {
private final StringRedisTemplate redisTemplate;
public StringOperationsExample(StringRedisTemplate redisTemplate) {
this.redisTemplate = redisTemplate;
}
public void stringOperations() {
ValueOperations<String, String> ops = redisTemplate.opsForValue();
// 设置值
ops.set("key", "value");
// 设置值并设置过期时间
ops.set("key-with-ttl", "value", Duration.ofMinutes(10));
// 获取值
String value = ops.get("key");
// 设置过期时间
redisTemplate.expire("key", Duration.ofHours(1));
// 检查键是否存在
Boolean exists = redisTemplate.hasKey("key");
// 删除键
redisTemplate.delete("key");
// 批量操作
Map<String, String> map = new HashMap<>();
map.put("key1", "value1");
map.put("key2", "value2");
ops.multiSet(map);
List<String> keys = Arrays.asList("key1", "key2");
List<String> values = ops.multiGet(keys);
}
}
哈希操作
java
@Service
public class HashOperationsExample {
private final RedisTemplate<String, Object> redisTemplate;
public HashOperationsExample(RedisTemplate<String, Object> redisTemplate) {
this.redisTemplate = redisTemplate;
}
public void hashOperations() {
HashOperations<String, String, Object> ops = redisTemplate.opsForHash();
// 单个字段设置
ops.put("user:1", "name", "John");
ops.put("user:1", "age", 30);
// 获取单个字段
Object name = ops.get("user:1", "name");
// 批量设置字段
Map<String, Object> fields = new HashMap<>();
fields.put("email", "john@example.com");
fields.put("active", true);
ops.putAll("user:1", fields);
// 获取所有字段和值
Map<String, Object> entries = ops.entries("user:1");
// 获取所有字段名
Set<String> fieldNames = ops.keys("user:1");
// 检查字段是否存在
Boolean hasField = ops.hasKey("user:1", "email");
// 删除字段
ops.delete("user:1", "active");
// 增加数字值
ops.increment("user:1", "age", 1);
}
}
列表操作
java
@Service
public class ListOperationsExample {
private final RedisTemplate<String, Object> redisTemplate;
public ListOperationsExample(RedisTemplate<String, Object> redisTemplate) {
this.redisTemplate = redisTemplate;
}
public void listOperations() {
ListOperations<String, Object> ops = redisTemplate.opsForList();
// 右侧插入
ops.rightPush("list", "item1");
ops.rightPushAll("list", "item2", "item3");
// 左侧插入
ops.leftPush("list", "item0");
// 获取列表范围
List<Object> items = ops.range("list", 0, -1); // 获取所有元素
// 修改指定位置的值
ops.set("list", 1, "modified-item");
// 获取指定位置的元素
Object item = ops.index("list", 0);
// 获取列表长度
Long size = ops.size("list");
// 弹出元素
Object leftItem = ops.leftPop("list");
Object rightItem = ops.rightPop("list");
// 阻塞弹出(适用于消息队列场景)
Object value = ops.leftPop("list", Duration.ofSeconds(10));
// 列表修剪
ops.trim("list", 0, 2); // 只保留前3个元素
}
}
集合操作
java
@Service
public class SetOperationsExample {
private final RedisTemplate<String, Object> redisTemplate;
public SetOperationsExample(RedisTemplate<String, Object> redisTemplate) {
this.redisTemplate = redisTemplate;
}
public void setOperations() {
SetOperations<String, Object> ops = redisTemplate.opsForSet();
// 添加元素
ops.add("set1", "member1", "member2", "member3");
ops.add("set2", "member3", "member4", "member5");
// 获取集合所有成员
Set<Object> members = ops.members("set1");
// 判断成员是否存在
Boolean isMember = ops.isMember("set1", "member1");
// 获取集合大小
Long size = ops.size("set1");
// 删除成员
ops.remove("set1", "member1");
// 随机获取成员
Object randomMember = ops.randomMember("set1");
List<Object> randomMembers = ops.randomMembers("set1", 2);
// 集合操作
Set<Object> diff = ops.difference("set1", "set2"); // 差集
Set<Object> union = ops.union("set1", "set2"); // 并集
Set<Object> intersect = ops.intersect("set1", "set2"); // 交集
// 将集合操作结果存储到新集合
ops.differenceAndStore("set1", "set2", "diff-result");
ops.unionAndStore("set1", "set2", "union-result");
ops.intersectAndStore("set1", "set2", "intersect-result");
}
}
有序集合操作
java
@Service
public class ZSetOperationsExample {
private final RedisTemplate<String, Object> redisTemplate;
public ZSetOperationsExample(RedisTemplate<String, Object> redisTemplate) {
this.redisTemplate = redisTemplate;
}
public void zsetOperations() {
ZSetOperations<String, Object> ops = redisTemplate.opsForZSet();
// 添加元素及分数
ops.add("zset", "member1", 1.0);
ops.add("zset", "member2", 2.0);
ops.add("zset", "member3", 3.0);
// 批量添加
Set<ZSetOperations.TypedTuple<Object>> tuples = new HashSet<>();
tuples.add(ZSetOperations.TypedTuple.of("member4", 4.0));
tuples.add(ZSetOperations.TypedTuple.of("member5", 5.0));
ops.add("zset", tuples);
// 按分数获取元素
Set<Object> range = ops.range("zset", 0, -1); // 所有元素
Set<Object> reverseRange = ops.reverseRange("zset", 0, -1); // 反序所有元素
// 按分数范围获取元素
Set<Object> scoreRange = ops.rangeByScore("zset", 2.0, 4.0);
// 获取元素的分数
Double score = ops.score("zset", "member1");
// 获取排名
Long rank = ops.rank("zset", "member2"); // 从低到高排名
Long revRank = ops.reverseRank("zset", "member2"); // 从高到低排名
// 增加分数
ops.incrementScore("zset", "member1", 2.5);
// 统计分数范围内的元素数量
Long count = ops.count("zset", 2.0, 4.0);
// 删除元素
ops.remove("zset", "member1");
// 删除排名范围内的元素
ops.removeRange("zset", 0, 1);
// 删除分数范围内的元素
ops.removeRangeByScore("zset", 4.0, 5.0);
}
}
8. Redis Repositories
Spring Data Redis也支持Repository风格的抽象,类似于Spring Data JPA。
启用Redis Repositories
java
@Configuration
@EnableRedisRepositories(basePackages = "com.example.repository")
public class RedisRepositoryConfig {
// Redis连接配置
}
定义实体类
java
@RedisHash("user")
public class User implements Serializable {
@Id
private String id;
private String name;
private String email;
private int age;
@TimeToLive
private Long expiration; // 过期时间(秒)
// 构造函数、getter和setter
}
创建Repository接口
java
public interface UserRepository extends CrudRepository<User, String> {
List<User> findByName(String name);
List<User> findByAgeGreaterThan(int age);
@Query("{ ?0 : ?1 }")
List<User> findByAttribute(String attribute, String value);
}
使用Repository
java
@Service
public class UserService {
private final UserRepository userRepository;
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
public User save(User user) {
return userRepository.save(user);
}
public Optional<User> findById(String id) {
return userRepository.findById(id);
}
public List<User> findByName(String name) {
return userRepository.findByName(name);
}
public Iterable<User> findAll() {
return userRepository.findAll();
}
public void delete(String id) {
userRepository.deleteById(id);
}
}
9. 缓存抽象
Spring Data Redis与Spring Cache集成,提供了Redis作为缓存后端的实现。
配置Redis缓存
java
@Configuration
@EnableCaching
public class RedisCacheConfig {
@Bean
public RedisCacheManager cacheManager(RedisConnectionFactory connectionFactory) {
RedisCacheConfiguration cacheConfig = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofMinutes(10)) // 默认缓存过期时间
.serializeKeysWith(RedisSerializationContext.SerializationPair
.fromSerializer(new StringRedisSerializer()))
.serializeValuesWith(RedisSerializationContext.SerializationPair
.fromSerializer(new GenericJackson2JsonRedisSerializer()))
.disableCachingNullValues(); // 不缓存null值
// 配置不同的缓存对应不同的过期时间
Map<String, RedisCacheConfiguration> cacheConfigurations = new HashMap<>();
cacheConfigurations.put("userCache", cacheConfig.entryTtl(Duration.ofMinutes(5)));
cacheConfigurations.put("productCache", cacheConfig.entryTtl(Duration.ofHours(1)));
return RedisCacheManager.builder(connectionFactory)
.cacheDefaults(cacheConfig)
.withInitialCacheConfigurations(cacheConfigurations)
.transactionAware()
.build();
}
}
使用缓存注解
java
@Service
public class CachedUserService {
private final UserRepository userRepository;
public CachedUserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
@Cacheable(value = "userCache", key = "#id")
public User findById(String id) {
return userRepository.findById(id)
.orElseThrow(() -> new RuntimeException("User not found"));
}
@CachePut(value = "userCache", key = "#user.id")
public User save(User user) {
return userRepository.save(user);
}
@CacheEvict(value = "userCache", key = "#id")
public void delete(String id) {
userRepository.deleteById(id);
}
@CacheEvict(value = "userCache", allEntries = true)
public void clearCache() {
// 清空整个缓存
}
}
10. 分布式锁
Spring Data Redis提供了RedisLockRegistry实现基于Redis的分布式锁。
java
@Configuration
public class RedisLockConfig {
@Bean
public RedisLockRegistry redisLockRegistry(RedisConnectionFactory connectionFactory) {
return new RedisLockRegistry(connectionFactory, "locks", 30000);
}
}
使用分布式锁:
java
@Service
public class DistributedLockService {
private final RedisLockRegistry lockRegistry;
public DistributedLockService(RedisLockRegistry lockRegistry) {
this.lockRegistry = lockRegistry;
}
public void executeWithLock(String lockKey, Runnable task) {
Lock lock = lockRegistry.obtain(lockKey);
try {
boolean acquired = lock.tryLock(10, TimeUnit.SECONDS);
if (acquired) {
try {
task.run();
} finally {
lock.unlock();
}
} else {
throw new RuntimeException("Failed to acquire lock: " + lockKey);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RuntimeException("Interrupted while acquiring lock", e);
}
}
}
11. 发布/订阅
Spring Data Redis支持Redis的发布/订阅模式。
配置消息监听器
java
@Configuration
public class RedisMessageConfig {
@Bean
public RedisMessageListenerContainer redisMessageListenerContainer(
RedisConnectionFactory connectionFactory,
MessageListener messageListener) {
RedisMessageListenerContainer container = new RedisMessageListenerContainer();
container.setConnectionFactory(connectionFactory);
// 添加监听器和监听的频道
container.addMessageListener(messageListener, new ChannelTopic("chat"));
return container;
}
@Bean
public MessageListener messageListener() {
return (message, pattern) -> {
System.out.println("Received message: " + new String(message.getBody()));
System.out.println("From channel: " + new String(message.getChannel()));
};
}
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
// 配置RedisTemplate
// ...
}
}
消息发布
java
@Service
public class MessagePublisher {
private final RedisTemplate<String, Object> redisTemplate;
public MessagePublisher(RedisTemplate<String, Object> redisTemplate) {
this.redisTemplate = redisTemplate;
}
public void publish(String channel, Object message) {
redisTemplate.convertAndSend(channel, message);
}
}
12. 会话管理
Spring Session可以与Spring Data Redis集成,实现基于Redis的HTTP会话管理。
配置
java
@Configuration
@EnableRedisHttpSession(maxInactiveIntervalInSeconds = 1800)
public class RedisSessionConfig {
// Redis连接配置
}
13. Redis事务
Spring Data Redis支持Redis的事务操作。
java
@Service
public class TransactionalRedisService {
private final RedisTemplate<String, Object> redisTemplate;
public TransactionalRedisService(RedisTemplate<String, Object> redisTemplate) {
this.redisTemplate = redisTemplate;
}
public void executeInTransaction() {
redisTemplate.execute(new SessionCallback<List<Object>>() {
@Override
public List<Object> execute(RedisOperations operations) {
operations.multi(); // 开始事务
operations.opsForValue().set("key1", "value1");
operations.opsForValue().set("key2", "value2");
operations.opsForList().leftPush("list", "item");
return operations.exec(); // 提交事务
}
});
}
public void executeWithWatch() {
redisTemplate.execute(new SessionCallback<Object>() {
@Override
public Object execute(RedisOperations operations) {
operations.watch("watched-key"); // 监视key
Object value = operations.opsForValue().get("watched-key");
operations.multi();
operations.opsForValue().set("watched-key", "new-value");
try {
return operations.exec(); // 如果watched-key被修改,事务将失败
} catch (Exception e) {
return null; // 事务失败
}
}
});
}
}
14. 性能优化
1. 使用管道操作
java
List<Object> results = redisTemplate.executePipelined(new RedisCallback<Object>() {
@Override
public Object doInRedis(RedisConnection connection) throws DataAccessException {
StringRedisConnection stringRedisConn = (StringRedisConnection) connection;
for (int i = 0; i < 1000; i++) {
stringRedisConn.set("key" + i, "value" + i);
}
return null;
}
});
2. 使用Lua脚本
java
@Service
public class RedisScriptService {
private final RedisTemplate<String, Object> redisTemplate;
public RedisScriptService(RedisTemplate<String, Object> redisTemplate) {
this.redisTemplate = redisTemplate;
}
public void executeScript() {
DefaultRedisScript<Boolean> script = new DefaultRedisScript<>();
script.setScriptSource(new ResourceScriptSource(new ClassPathResource("script.lua")));
script.setResultType(Boolean.class);
List<String> keys = Collections.singletonList("key");
Boolean result = redisTemplate.execute(script, keys, "arg1", "arg2");
}
}
15. 总结
Spring Data Redis提供了全面的Redis操作抽象,从低级连接管理到高级Repository模式,使得在Spring应用中集成Redis变得简单而高效。无论是用于缓存、会话管理、消息发布/订阅还是分布式锁,Spring Data Redis都提供了一致的编程模型和丰富的功能。
通过合理配置和使用Spring Data Redis,可以充分利用Redis的高性能特性,同时享受Spring框架的便利性。