springboot2.x 使用redis缓存【转】【补】
在使用之前先简单介绍一下,redis和mongoDB这两个nosql的区别以及使用场景。
1. redis
redis是一个分布式缓存、高性能的key-value数据库。支持存储的value类型包括string(字符串)、list(链表)、set(集合)、zset(sorted set --有序集合)和hash(哈希类型)。这些数据类型都支持push/pop、add/remove及取交集并集和差集及更丰富的操作,而且这些操作都是原子性的。数据操作在内存中,因此效率非常高。可以持久化到磁盘,所以在对不同数据集进行高速读写时需要权衡内存,因为数据量不能大于硬件内存。
2. mongoDB
mongoDB 是一种文档性的数据库。先解释一下文档的数据库,即可以存放xml、json、bson类型系那个的数据。这些数据具备自述性(self-describing),呈现分层的树状数据结构。支持丰富的查询语句。
使用场景总结
-
mongodb 更偏向保存数据,而 redis 更偏向保持状态。
-
业务数据量小且对效率有要求用redis.
-
mongoDB用于对海量数据的操作性能提升。
最简单的就这几点,最最重要的是你要知道一个叫缓存,一个是数据库。想清楚了就理解了。
这些都是个人理解,关于事务方面的,虽然redis支持,但很弱,真正涉及到事务的场景基本不会用它的。
接下来,进入正题,首先你的机器上要先安装redis,建议用docker.
docker使用redis
-
获取
redis
镜像
执行命令docker pull redis
-
运行
redis
并进行端口映射和持久化存储
docker run -d --name myredis -p6379:6379 -v /d/dockerdata/redis/data:/data redis --appendonly yes
参数说明:
-
-d ——后台运行
-
-name —— 运行后容器的名字
-
-p 6379:6379 —— 端口映射,默认端口
-
-v /d/dockerdata/redis/data:/data —— 保存数据的位置。
-
redis –appendonly yes —— 在容器执行redis启动命令,并打开redis持久化配置。
有可能会提示输入Windows密码,或者提示找不到文件存储目录,正常输入密码,指定位置创建文件夹就好。
- 测试运行是否成功
PS C:\Users\Gyyyang> docker exec -it myredis redis-cli
127.0.0.1:6379> info
# Server
redis_version:5.0.6
redis_git_sha1:00000000
......
spring boot项目中使用redis
1.添加 Redis 依赖
Spring Boot 官方已经为我们提供好了集成 Redis 的 Starter,我们只需要简单地在 pom.xml 文件中添加如下代码即可。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
2.配置 Redis
spring:
redis:
host: 192.168.2.28
port: 6379
database: 0 # 数据库索引,默认是0
password: # Redis 服务器连接密码(默认为空)
# 在2.0后 redis 底层已经默认修改为使用的是 Lettuce 而不是 Jedis 作为 Redis 驱动程序,但是仍然支持 Jedis,配置用法一致
lettuce:
pool:
max-active: 8 #连接池最大连接数(使用负值表示没有限制)
max-wait: -1 # 连接池最大阻塞等待时间(使用负值表示没有限制)
max-idle: 8 # 连接池中的最大空闲连接
min-idle: 0 # 连接池中的最小空闲连接
timeout: 0 # 连接超时时间(毫秒)
Spring Boot
的 spring-boot-starter-data-redis
为 Redis
的相关操作提供了一个高度封装的 RedisTemplate
类,而且对每种类型的数据结构都进行了归类,将同一类型操作封装为 operation
接口。RedisTemplate
对五种数据结构分别定义了操作,如下所示:
-
操作字符串:
redisTemplate.opsForValue()
-
操作
Hash:redisTemplate.opsForHash()
-
操作
List:redisTemplate.opsForList()
-
操作
Set:redisTemplate.opsForSet()
-
操作
ZSet:redisTemplate.opsForZSet()
但是对于 string
类型的数据,Spring Boot
还专门提供了 StringRedisTemplate
类,而且官方也建议使用该类来操作 String
类型的数据。那么它和 RedisTemplate
又有啥区别呢?
-
RedisTemplate
是一个泛型类,而StringRedisTemplate
不是,后者只能对键和值都为String
类型的数据进行操作,而前者则可以操作任何类型。 -
两者的数据是不共通的,StringRedisTemplate 只能管理 StringRedisTemplate 里面的数据,RedisTemplate 只能管理 RedisTemplate 中 的数据。
RedisTemplate
的配置
一个 Spring Boot 项目中,我们只需要维护一个 RedisTemplate 对象和一个 StringRedisTemplate 对象就可以了。所以我们需要通过一个 Configuration 类来初始化这两个对象并且交由的 BeanFactory 管理。在 config 包下面新建了一个 RedisConfig 类,其内容如下所示:
@Configuration
@AutoConfigureAfter(RedisAutoConfiguration.class)
public class RedisConfig {
@Bean
@ConditionalOnMissingBean(name = "redisTemplate")
public RedisTemplate<String, Object> redisTemplate(
RedisConnectionFactory redisConnectionFactory) {
Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<Object>(Object.class);
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
RedisTemplate<String, Object> template = new RedisTemplate<String, Object>();
template.setConnectionFactory(redisConnectionFactory);
template.setKeySerializer(jackson2JsonRedisSerializer);
template.setValueSerializer(jackson2JsonRedisSerializer);
template.setHashKeySerializer(jackson2JsonRedisSerializer);
template.setHashValueSerializer(jackson2JsonRedisSerializer);
template.afterPropertiesSet();
return template;
}
@Bean
@ConditionalOnMissingBean(StringRedisTemplate.class)
public StringRedisTemplate stringRedisTemplate(
RedisConnectionFactory redisConnectionFactory) {
StringRedisTemplate template = new StringRedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
}
其中@Configuration
代表这个类是一个配置类,然后@AutoConfigureAfter(RedisAutoConfiguration.class)
是让我们这个配置类在内置的配置类之后在配置,这样就保证我们的配置类生效,并且不会被覆盖配置。其中需要注意的就是方法名一定要叫redisTemplate
因为@Bean
注解是根据方法名配置这个bean
的name
的。
测试
新建一个实体类:
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User implements Serializable {
private static final Long serialVersionUID = 111111111L;
private String username;
private String password;
private Integer age;
@Override
public String toString() {
return "User{" +
"username='" + username + '\'' +
", password='" + password + '\'' +
", age=" + age +
'}';
}
}
测试代码:
@SpringBootTest
class BootApplicationTests {
@Autowired
private RedisTemplate redisTemplate;
@Autowired
private StringRedisTemplate stringRedisTemplate;
@Test
void contextLoads() {
String key = "name";
stringRedisTemplate.opsForValue().set(key, "hai");
String value = stringRedisTemplate.opsForValue().get(key);
System.out.println(key + "----" + value);
User user =new User();
user.setUsername("yang");
user.setPassword("ssss");
user.setAge(12);
String userKey = "yang";
redisTemplate.opsForValue().set(userKey,user);
User newUser = (User) redisTemplate.opsForValue().get(userKey);
System.out.println("获取缓存中key为" + userKey + "的值为:" + newUser);
}
}
测试结果:
name----hai
获取缓存中key为yang的值为:User{username='yang', password='ssss', age=12}
其他数据类型操作类似,就不一一列举了,也可以下载一个可视化工具查看一下缓存。
文章内容纯属个人理解,可能存在错误,望及时指出,希望对你有用。
springboot2中使用fastjson序列化
本小节部分参考 SpringBoot+Redis缓存新老版本FastJson序列化使用方法(redis配置无效问题)==>https://blog.csdn.net/HumorChen99/article/details/109559566 并完善使用说明
上方的样例,使用的是jason序列化, 如何使用fasetjson?
先引入依赖包
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.80</version>
</dependency>
注入fastJsonRedisSerializer Bean
package com.bobo.code.config;
import com.alibaba.fastjson.support.config.FastJsonConfig;
import com.alibaba.fastjson.support.spring.GenericFastJsonRedisSerializer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
@Configuration
public class FastJsonRedisConfig {
@Bean
public RedisSerializer<Object> fastJsonRedisSerializer() {
FastJsonConfig config = new FastJsonConfig();
// 在这里进行 FastJson 的配置
//return new FastJsonRedisSerializer<>(Object.class);//存入的key没有@type , 反序列化对象时会报异常 java.lang.ClassCastException: com.alibaba.fastjson.JSONObject cannot be cast to com.bobo.code.model.Person
return new GenericFastJsonRedisSerializer();//存入的key多了一个@type , 用于指定反序列化对象类型, 比如"@type": "com.bobo.code.model.Person"
}
@Bean
public RedisSerializer<String> stringRedisSerializer() {
return new StringRedisSerializer();
}
}
在使用springboot2的缓存是如果没配置完善,会遇见类似转换异常,
这是因为序列化和反序列化没有配套导致反序列化之前序列化到redis中的value时出现类型转换异常
2023-04-24 21:19:41.504 ERROR 15528 --- [nio-8080-exec-4] o.a.c.c.C.[.[.[.[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [/jpa] threw exception [Request processing failed; nested exception is java.lang.ClassCastException: com.alibaba.fastjson.JSONObject cannot be cast to com.bobo.code.model.Person] with root cause
java.lang.ClassCastException: com.alibaba.fastjson.JSONObject cannot be cast to com.bobo.code.model.Person
at com.bobo.code.web.controller.PersonController$$EnhancerBySpringCGLIB$$6078ab8f.findById(<generated>) ~[classes/:na]
此时应该换用GenericFastJsonRedisSerializer
此时存入的key多了一个@type , 用于指定反序列化对象类型
{
"@type": "com.bobo.code.model.Person",
"email": "bobo@qq.com",
"id": 29,
"name": "bobo",
"passWord": "123456789"
}
定义redisTemplate使用fastJsonRedisSerializer
老版本
该版本可以让redis中存的value由于序列化对象的原因呈不可读状态
package com.bobo.code.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
/**
* 老版本序列化,存入redis中的value是不可读的
*/
@EnableCaching
@Configuration
@AutoConfigureAfter(RedisAutoConfiguration.class)
public class RedisOldVersionConfig {
@Autowired
private RedisSerializer fastJsonRedisSerializer;
@Autowired
private StringRedisSerializer stringRedisSerializer;
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
template.setDefaultSerializer(fastJsonRedisSerializer);
template.setKeySerializer(stringRedisSerializer);
template.setValueSerializer(fastJsonRedisSerializer);
template.setHashKeySerializer(stringRedisSerializer);
template.setHashValueSerializer(fastJsonRedisSerializer);
template.afterPropertiesSet();
return template;
}
//该bean非必须,可以不定义
@Bean
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
StringRedisTemplate template = new StringRedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
template.setDefaultSerializer(stringRedisSerializer);
return template;
}
}
新版本(推荐)
该版本可以让redis中存value之前先转成纯字符串 , 呈可读显示
package com.bobo.code.config;
import com.alibaba.fastjson.support.spring.FastJsonRedisSerializer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.Caching;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
/**
* 新版本,存入redis的value是可读的,推荐
*/
@EnableCaching
@Configuration
public class RedisNewVersionConfiguration {
@Autowired
private RedisSerializer fastJsonRedisSerializer;
//主要配置,本类的其它bean可以不配
@Bean
public CacheManager cacheManager(RedisConnectionFactory factory) {
RedisSerializer<String> redisSerializer = new StringRedisSerializer();
// 序列化配置
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(fastJsonRedisSerializer));
// 使用序列化的配置
RedisCacheManager cacheManager = RedisCacheManager.builder(factory)
.cacheDefaults(config)
.build();
return cacheManager;
}
@Bean
public KeyGenerator customKeyGenerator() {
return (target, method, params) -> {
StringBuilder sb = new StringBuilder();
sb.append(target.getClass().getSimpleName());
sb.append(":");
sb.append(method.getName());
sb.append(":");
for (Object obj : params) {
sb.append(obj.toString());
sb.append(":");
}
return sb.toString();
};
}
//样例
@Cacheable(value = "myCache", keyGenerator = "customKeyGenerator", cacheManager = "cacheManager", unless = "#result == null")
public Object getObjectFromCache(String key) {
// 查询数据库或其他逻辑
return new Object();
}
@Caching(evict = {
@CacheEvict(value = "myCache", keyGenerator = "customKeyGenerator")
})
public void evictObjectFromCache(String key) {
// 从数据库或其他逻辑中删除对象
}
}
controller中使用
自动包装模式
@Resource
RedisTemplate redisTemplate;
@ResponseBody
@RequestMapping("/findById")
public Person findById(@RequestParam Long id) {
logger.info("执行 findById");
final Optional<Person> opt = personRepository.findById(id);
if(opt.isPresent()){
logger.info(JSONUtil.toJsonPrettyStr(opt));
redisTemplate.opsForValue().set("person:"+id,opt.get());
return opt.get();
}else{
return null;
}
}
jpa缓存模式
@ResponseBody
@Cacheable(value="namespace:person", key = "methodName +#p0")
@RequestMapping("/findById")
public Person findById(@RequestParam Long id) {
logger.info("执行 findById");
final Optional<Person> opt = personRepository.findById(id);
if(opt.isPresent()){
logger.info(JSONUtil.toJsonPrettyStr(opt));
return opt.get();
}else{
return null;
}
}
redis中value结果
如果在FastJsonRedisConfig类中使用FastJsonRedisSerializer<>(Object.class),则存入的value如下
{
"email": "bobo@qq.com",
"id": 29,
"name": "bobo",
"passWord": "123456789"
}
如果在类中使用FastJsonRedisSerializer,则存入的value如下, 多了 "@type": "com.bobo.code.model.Person" ,它可以让fastjson在反序列化时知道应该反转成什么具体类型
{
"@type": "com.bobo.code.model.Person",
"email": "bobo@qq.com",
"id": 29,
"name": "bobo",
"passWord": "123456789"
}
我的代码位于
git@gitee.com:KingBoBo/springboot2-09-jpa.git 的 jpa缓存Cache 分支中
参考自
springboot2.x 使用redis (入门)==>https://www.cnblogs.com/gyyyblog/p/12019554.html