SpringBoot2.x整合Redis两种使用方式

avatar 2020年01月31日12:09:30 6 4988 views
博主分享免费Java教学视频,B站账号:Java刘哥 ,长期提供技术问题解决、项目定制:本站商品点此
本文介绍 SpringBoot 整合 Redis,常见的两种使用方式:redisTemplate 和 @Cacheable 注解。
redisTemplate 是常见 spring-data-redis 提供的用法,一般我们会封装一个 RedisUtil 工具类来方便我们调用。
@Cacheable 和 @CacheEvict 注解也是 Spring 提供的注解,我们知道注解一般是基于 AOP 实现,通过在注解方法前后进行一些操作,简化开发者的代码。
下面会通过一个增删改查介绍两种用法。
代码地址:https://github.com/saysky/redis-demo

一、SpringBoot 整合 Redis

1、Maven 依赖
pom.xml
<!--    Redis    -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<version>2.2.4.RELEASE</version>
</dependency>
<!-- json -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.60</version>
</dependency>

这里 springboot 版本为 2.2.4.RELEASE,spring-boot-starter-data-redis 和 boot 保持同一版本
使用 fastjson 作为json工具
2、配置文件
application.yml
spring:
redis:
database: 0
# 单机版配置
host: 127.0.0.1
port: 6379
# 集群配置
# cluster:
# max-redirects: 1
# nodes:
# - 127.0.0.1:7010
# - 127.0.0.1:7011
# - 127.0.0.1:7012
# password:
lettuce:
pool:
#最大连接数据库连接数,设 0 为没有限制
max-active: 8
#最大等待连接中的数量,设 0 为没有限制
max-idle: 8
#最大建立连接等待时间。如果超过此时间将接到异常。设为-1表示无限制。
max-wait: -1
#最小等待连接中的数量,设 0 为没有限制
min-idle: 0
timeout: 1000

二、用法一:RedisUtil

1、新建 RedisUtil.java
package com.liuyanzhao.redis.utils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
/**
* @author 言曌
* @date 2020-01-29 14:11
*/
@Component
public class RedisUtil {
@Autowired
private StringRedisTemplate redisTemplate;
// Key(键),简单的key-value操作
/**
* 实现命令:TTL key,以秒为单位,返回给定 key的剩余生存时间(TTL, time to live)。
*
* @param key
* @return
*/
public long ttl(String key) {
return redisTemplate.getExpire(key);
}
/**
* 实现命令:expire 设置过期时间,单位秒
*
* @param key
* @return
*/
public void expire(String key, long timeout) {
redisTemplate.expire(key, timeout, TimeUnit.SECONDS);
}
/**
* 实现命令:INCR key,增加key一次
*
* @param key
* @return
*/
public long incr(String key, long delta) {
return redisTemplate.opsForValue().increment(key, delta);
}
/**
* 实现命令: key,减少key一次
*
* @param key
* @return
*/
public long decr(String key, long delta) {
if (delta < 0) {
// throw new RuntimeException("递减因子必须大于0");
del(key);
return 0;
}
return redisTemplate.opsForValue().increment(key, -delta);
}
/**
* 实现命令:KEYS pattern,查找所有符合给定模式 pattern的 key
*/
public Set<String> keys(String pattern) {
return redisTemplate.keys(pattern);
}
/**
* 实现命令:DEL key,删除一个key
*
* @param key
*/
public void del(String key) {
redisTemplate.delete(key);
}
/**
* 根据通配符批量删除
*
* @param key
*/
public void delByKeys(String key) {
Set<String> keys = redisTemplate.keys(key+"*");
redisTemplate.delete(keys);
}
// String(字符串)
/**
* 实现命令:SET key value,设置一个key-value(将字符串值 value关联到 key)
*
* @param key
* @param value
*/
public void set(String key, String value) {
redisTemplate.opsForValue().set(key, value);
}
/**
* 实现命令:SET key value EX seconds,设置key-value和超时时间(秒)
*
* @param key
* @param value
* @param timeout (以秒为单位)
*/
public void set(String key, String value, long timeout) {
redisTemplate.opsForValue().set(key, value, timeout, TimeUnit.SECONDS);
}
/**
* 实现命令:GET key,返回 key所关联的字符串值。
*
* @param key
* @return value
*/
public String get(String key) {
return (String) redisTemplate.opsForValue().get(key);
}
// Hash(哈希表)
/**
* 实现命令:HSET key field value,将哈希表 key中的域 field的值设为 value
*
* @param key
* @param field
* @param value
*/
public void hset(String key, String field, Object value) {
redisTemplate.opsForHash().put(key, field, value);
}
/**
* 实现命令:HGET key field,返回哈希表 key中给定域 field的值
*
* @param key
* @param field
* @return
*/
public String hget(String key, String field) {
return (String) redisTemplate.opsForHash().get(key, field);
}
/**
* 实现命令:HDEL key field [field ...],删除哈希表 key 中的一个或多个指定域,不存在的域将被忽略。
*
* @param key
* @param fields
*/
public void hdel(String key, Object... fields) {
redisTemplate.opsForHash().delete(key, fields);
}
/**
* 实现命令:HGETALL key,返回哈希表 key中,所有的域和值。
*
* @param key
* @return
*/
public Map<Object, Object> hgetall(String key) {
return redisTemplate.opsForHash().entries(key);
}
// List(列表)
/**
* 实现命令:LPUSH key value,将一个值 value插入到列表 key的表头
*
* @param key
* @param value
* @return 执行 LPUSH命令后,列表的长度。
*/
public long lpush(String key, String value) {
return redisTemplate.opsForList().leftPush(key, value);
}
/**
* 实现命令:LPOP key,移除并返回列表 key的头元素。
*
* @param key
* @return 列表key的头元素。
*/
public String lpop(String key) {
return (String) redisTemplate.opsForList().leftPop(key);
}
/**
* 实现命令:RPUSH key value,将一个值 value插入到列表 key的表尾(最右边)。
*
* @param key
* @param value
* @return 执行 LPUSH命令后,列表的长度。
*/
public long rpush(String key, String value) {
return redisTemplate.opsForList().rightPush(key, value);
}
}

后面我们都会在需要使用 Redis 的地方注入 RedisUtil 就行
2、基本操作
package com.liuyanzhao.redis.service.impl;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.liuyanzhao.redis.entity.User;
import com.liuyanzhao.redis.repository.UserRepository;
import com.liuyanzhao.redis.service.UserService;
import com.liuyanzhao.redis.utils.RedisUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import java.util.Optional;
/**
* Redis缓存方法一:通过 redisTemplate 操作
*
* @author 言曌
* @date 2020-01-29 14:23
*/
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserRepository userRepository;
@Autowired
private RedisUtil redisUtil;
public static final String REDIS_USER_ID_KEY_PREFIX = "user::id-";
@Override
public User findById(Long id) {
// 从 redis 查询
String value = redisUtil.get(REDIS_USER_ID_KEY_PREFIX + id);
if (!StringUtils.isEmpty(value)) {
User user = JSON.parseObject(value, User.class);
if (user != null) {
return user;
}
}
// redis 没有,从数据库查询
Optional<User> optional = userRepository.findById(id);
User user = optional.isPresent() ? optional.get() : null;
// 将对象存储到 redis
redisUtil.set(REDIS_USER_ID_KEY_PREFIX + id, JSON.toJSONString(user, SerializerFeature.WriteClassName));
return user;
}
@Override
public void insert(User user) {
// 新增
userRepository.save(user);
}
@Override
public void update(User user) {
// 更新
userRepository.save(user);
// 删除 redis 的
redisUtil.del(REDIS_USER_ID_KEY_PREFIX + user.getId());
}
@Override
public void deleteById(Long id) {
//注意:必须先删除数据库的,再删除 redis 的
// 删除数据库的
userRepository.deleteById(id);
//删除 redis 的
redisUtil.del(REDIS_USER_ID_KEY_PREFIX + id);
}
}

简单说下:
这里我们都以Redis 的 String 数据类型为例,将对象通过 fastjson 序列化为 JSON 字符串,然后存储到 Redis 中,然后查询出来的时候,再通过 fastjson 将 JSON 字符串转成 java 对象。
(1) 查询操作:先查询 Redis,如果Redis没有则再查询数据库,并添加到 Redis
(2) 删除操作:需要先删除数据库数据,再删除Redis中的
(3) 更新操作:  更新数据库数据,删除 Redis 数据,当然也可以再添加 Redis 记录
使用 Redis Desktop Manager 来查看,可以看到数据格式为 String 类型的 JSON 格式,里面加了 @type,
如果不想要 @type,可以在序列化的时候去掉 SerializerFeature.WriteClassName,即替换 JSON.toJSONString(user, SerializerFeature.WriteClassName)); 为 JSON.toJSONString(user));

三、用法二:使用注解 @Cacheabele

1、新建 FastJsonRedisSerializer.java
因为我们方法二使用的 fastjson 来序列化和反序列化,为了兼容方法二的序列化结果,使两种Redis操作方式能完全兼容,这里我们会设置序列化方式为 fastjson
重写 RedisSerializer 类,重写序列化和反序列化方法
package com.liuyanzhao.redis.config;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.ParserConfig;
import com.alibaba.fastjson.serializer.SerializerFeature;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.SerializationException;
import java.nio.charset.Charset;
/**
* @author 言曌
* @date 2020-01-29 16:23
*/
public class FastJsonRedisSerializer<T> implements RedisSerializer<T> {
public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");
private Class<T> clazz;
// 解决 autoType is not support 问题
static {
ParserConfig.getGlobalInstance().addAccept("com.liuyanzhao.redis");
// ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
}
public FastJsonRedisSerializer(Class<T> clazz) {
super();
this.clazz = clazz;
}
@Override
public byte[] serialize(T t) throws SerializationException {
if (t == null) {
return new byte[0];
}
return JSON.toJSONString(t, SerializerFeature.WriteClassName).getBytes(DEFAULT_CHARSET);
}
@Override
public T deserialize(byte[] bytes) throws SerializationException {
if (bytes == null || bytes.length <= 0) {
return null;
}
String str = new String(bytes, DEFAULT_CHARSET);
return JSON.parseObject(str, clazz);
}
}

注意为了解决 autoType is not support 问题,需要添加,需要修改下面包名,改成你的项目包名就行,只要使用缓存注解的类所在包属于该包就行
 static {
ParserConfig.getGlobalInstance().addAccept("com.liuyanzhao.redis");
}

2、新建 RedisConfig.java
需要开启Spring的缓存功能,添加 @EnableCaching 注解,一般在启动类上添加,这里我们直接在配置类上添加就行
然后需要设置序列化和反序列化的类
package com.liuyanzhao.redis.config;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import java.time.Duration;
/**
* 缓存配置类
* 用于注解的缓存配置,如 @Cacheable 的序列化配置
*
* 说明:
* 我们 redisUtils 序列化方式采用 json序列化
* @Cacheable 默认序列化方式为 二进制的
* 两个不能混用,为了解决这个问题,这里设置 @Cacheable 默认序列化方式为 json
* @author 言曌
* @date 2020-01-29 16:13
*/
@Configuration
@EnableCaching
public class RedisConfig extends CachingConfigurerSupport {
/**
* 设置 redis 数据默认过期时间
* 设置@cacheable 序列化方式
*
* @return
*/
@Bean
public RedisCacheConfiguration redisCacheConfiguration() {
FastJsonRedisSerializer<Object> fastJsonRedisSerializer = new FastJsonRedisSerializer<>(Object.class);
RedisCacheConfiguration configuration = RedisCacheConfiguration.defaultCacheConfig();
configuration = configuration.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(fastJsonRedisSerializer)).entryTtl(Duration.ofDays(30));
return configuration;
}
}

3、基本操作
也是增删改查
package com.liuyanzhao.redis.service.impl;
import com.liuyanzhao.redis.entity.User;
import com.liuyanzhao.redis.repository.UserRepository;
import com.liuyanzhao.redis.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import java.util.Optional;
/**
* Redis缓存方法二:通过 @Cacheable、@CacheEvict 注解实现 操作,需要开启缓存 @EnableCaching
*
* @author 言曌
* @date 2020-01-29 14:23
*/
@Service
public class UserServiceImpl2 implements UserService {
@Autowired
private UserRepository userRepository;
@Override
@Cacheable(value = "user", key = "'id-'+#id")
public User findById(Long id) {
Optional<User> optional = userRepository.findById(id);
User user = optional.isPresent() ? optional.get() : null;
return user;
}
@Override
public void insert(User user) {
// 新增
userRepository.save(user);
}
@Override
@CacheEvict(value = "user", key = "'id-'+#user.id")
public void update(User user) {
// 更新
userRepository.save(user);
}
@Override
@CacheEvict(value = "user", key = "'id-'+#id")
public void deleteById(Long id) {
// 删除数据库的
userRepository.deleteById(id);
}
}

这里说明下:
@Cacheable 注解是先根据 key 去查询 Redis 中是否有这个 key,如果有则直接返回。
如果没有则执行方法体,最后将方法返回内容添加到 Redis 中,key 为上面那个key
@CacheEvict 注解是先执行方法体,然后根据 key 去 Redis 中删除
其实还有一个 @CachePut 注解,这里觉得没必要用,用这两个注解足矣

四、代码地址

完整代码:https://github.com/saysky/redis-demo
该 demo 数据库用的是 H2,相对 MySQL 而言不需要创建数据库,适合做测试使用
ORM 框架用的是 SpringDataJPA 也是方便测试
完整代码结构如下,大家可以自行去下载代码

  • 微信
  • 交流学习,有偿服务
  • weinxin
  • 编程爱好者交流群
  • 资源分享,问题解决,技术交流。QQ群:424285828(新群)
  • weinxin
avatar

发表评论

avatar 登录者:匿名
匿名评论,评论回复后会有邮件通知

  

已通过评论:0   待审核评论数:0