注意:这是接着SpringBoot缓存(点击进入)继续的,之前用到的代码和数据都在里面
点击进入源代码
文章目录
SpringBoot整合Redis
SpringBoot缓存默认使用的是ConcurrentMapCacheManager创建的ConcurrentMapCache组件,将数据保存在ConcurrentMap<Object, Object>中,而开发中经常使用的是缓存中间件。开发中经常使用缓存中间件:redis、memcached、ehcache;下面就对Redis整合。
一、安装Redis
1.用docker安装redis
-
用远程工具连接上linux服务器
-
用
docker images
检查是否已经安装redis
我是没有安装docker的 -
输入
docker pull redis
-
安装完成之后再次输入
docker images
-
输入
docker run -d -p 6379:6379 --name myredis redis仓库名字
-d 后台运行
-p 暴露端口
6379:6379 将服务器的6379映射到容器的6379
–name 起一个名字 -
查看redis镜像是否运行,
docker ps
2.用连接工具连接redis
能连接行redis就说明redis环境没有问题了
二、整合Redis
1.引入starter
- 在pom.xml文件中添加依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
springboot 2.x使用的是lettuce,是对jedis的进一步封装,功能更强
- 打开RedisAutoConfiguration类,这是redis的配置类
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(RedisOperations.class)
@EnableConfigurationProperties(RedisProperties.class)
@Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class })
public class RedisAutoConfiguration {
@Bean
@ConditionalOnMissingBean(name = "redisTemplate")
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory)
throws UnknownHostException {
RedisTemplate<Object, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
@Bean
@ConditionalOnMissingBean
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory)
throws UnknownHostException {
StringRedisTemplate template = new StringRedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
}
其中:
StringRedisTemplate //操作k-v都是字符串的
RedisTemplate //k-v都是对象的
2.配置redis
- 在application.properties中配置redis
spring.redis.host=47.94.229.156
3.测试
序列化javabean
在测试类里操作
@SpringBootTest
class DemoApplicationTests {
@Autowired
EmployeeMapper employeeMapper;
@Autowired
StringRedisTemplate stringRedisTemplate; //操作k-v都是字符串的
@Autowired
RedisTemplate redisTemplate; //k-v都是对象
/**
* Redis常见的五大数据类型:
* String(字符串)、List(列表)、Set(集合)、Hash(散列)、Zset(有序集)
* stringRedisTemplate.opsForValue()[String(字符串)]
* stringRedisTemplate.opsForList()[List(列表)]
* stringRedisTemplate.opsForSet()[Set(集合)]
* stringRedisTemplate.opsForHash()[Hash(散列)]
* stringRedisTemplate.opsForZset()[Zset(有序集)]
*/
@Test
public void test01(){
//给redis中保存数据
stringRedisTemplate.opsForValue().append("xx","cool");
//读取数据
stringRedisTemplate.opsForValue().get("xx");
stringRedisTemplate.opsForList().leftPush("mylist1","1");
stringRedisTemplate.opsForList().leftPush("mylist1","2");
}
@Test
public void test02(){
Employee empById = employeeMapper.getEmpById(1);
//把查出的数据放到缓存,Employee需要序列化
//默认如果保存对象,使用jdk序列化机制,序列化后的数据保存到redis中
redisTemplate.opsForValue().set("emp-01",empById);
}
@Test
void contextLoads() {
Employee empById = employeeMapper.getEmpById(1);
System.out.println(empById);
}
}
在test02测试中可以发现k-v都是序列化的值,如下图所示:
这是因为 StringRedisTemplate只能处理key和value都是字符串的信息
将数据以json的方式保存
(1)自己将对象转为json
(2)redisTemplate默认的序列化规则(使用这一种)
- 在config包下建立一个MyRedisConfig类
@Configuration
public class MyRedisConfig {
@Bean
public RedisTemplate<Object, Employee> empRedisTemplate(RedisConnectionFactory redisConnectionFactory)
throws UnknownHostException {
RedisTemplate<Object, Employee> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
Jackson2JsonRedisSerializer<Employee> employeeJackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<Employee>(Employee.class);
template.setDefaultSerializer(employeeJackson2JsonRedisSerializer);
return template;
}
}
上面的方法还要用到泛型,不能照顾多数,还可以写一个通用的方法,如下:
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
RedisSerializer<String> redisSerializer = new StringRedisSerializer();
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
/*ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
如果不注释掉value的值为:["com.example.demo.bean.Employee",{"id":1,"lastName":"张三","email":"111","gender":1,"dId":1}]
注释后value的值为:{"id":1,"lastName":"张三","email":"111","gender":1,"dId":1}
*/
template.setConnectionFactory(factory);
//key序列化方式
template.setKeySerializer(redisSerializer);
//value序列化
template.setValueSerializer(jackson2JsonRedisSerializer);
//value hashmap序列化
template.setHashValueSerializer(jackson2JsonRedisSerializer);
return template;
}
- 在测试类中添加如下代码
@AutowiredRedisTemplate<Object,Employee> empRedisTemplate;
@Test
public void test03(){
Employee empById = employeeMapper.getEmpById(1);
empRedisTemplate.opsForValue().set("emp-01",empById);
}
可以看到,使用了jso的格式缓存
三、测试缓存
原理: CacheManager 生成 缓存组件 缓存组件来给缓存中存取数据
1)、引入redis的starter,容器中保存的是RedisCacheManager
2)、RedisCacheManager 帮我们创建RedisCache来作为缓存组件,RedisCache通过操作redis操作数据
1.启动项目测试
-
启动项目,在浏览器中输入
http://localhost:8080/emp/1
可以看到浏览器中有返回结果,控制台打印信息 -
再次在浏览器中输入
http://localhost:8080/emp/1
可以看到浏览器有返回数据,控制台没有返回,说明数据来源于缓存(redis) -
打开redis连接工具查看
可以看到value是序列化之后的值,这是因为默认保存数据 k-v 都是Object;利用序列化保存;如何保存为json?
2.缓存数据如何保存为json
引入redis的starter,CacheManager变为RedisCacheManager
- 在MyRedisConfig类里添加如下代码:
@Bean
public CacheManager empCacheManager(RedisConnectionFactory factory) {
RedisSerializer<String> redisSerializer = new StringRedisSerializer();
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
//解决查询缓存转换异常的问题
/*ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
注掉前的value值:["com.example.demo.bean.Employee",{"id":1,"lastName":"张三","email":"111","gender":1,"dId":1}]
注掉后的value值:{"id":1,"lastName":"张三","email":"111","gender":1,"dId":1}
*/
// 配置序列化(解决乱码的问题),过期时间30秒
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofSeconds(30))
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer))
.disableCachingNullValues();
RedisCacheManager cacheManager = RedisCacheManager.builder(factory)
.cacheDefaults(config)
.build();
return cacheManager;
}
- 清除原来的缓存,在浏览器输入
http://localhost:8080/emp/1
可以看到缓存数据正常了 - 但是当我们从缓存中拿取数据的时候就会报错
eg:当有这条数据缓存的时候,再次输入http://localhost:8080/emp/1
,可以看到:
原因是:
缓存是以json的形式传入的缓存,取数据的时候我的是CacheManager默认使用RedisTemplate<Object,Object>操作的redis缓存
改正:
将图中的Object改成需要转换的类,我的就是Employee- 由此可见如果有好多javabean的话,对每个需要缓存的javabean的类型,创建一个CacheManager
(1)新增DepartmentService类
@Service
public class DepartmentService {
@Autowired
private DepartmentMapper departmentMapper;
@Cacheable(cacheNames = "dept")
public Department getDeptById(Integer id){
System.out.println("查询部门"+id);
Department deptById = departmentMapper.getDeptById(id);
return deptById;
}
}
(2)在DepartmentMapper中新增方法
@Select("SELECT * FROM department WHERE id = #{id}")
public Department getDeptById(Integer id);
(3)新增DepartmentController类
@RestController
public class DepartmentController {
@Autowired
private DepartmentService departmentService;
@GetMapping("/dept/{id}")
public Department getDeptById(@PathVariable("id") Integer id){
Department deptById = departmentService.getDeptById(id);
return deptById;
}
}
(4)在数据库department表中新增数据
(5)测试一下会出现的错误
-
启动项目,在浏览器中输入
http://localhost:8080/dept/1
可以看到第一次可以正常返回数据, -
再次在浏览器输入
http://localhost:8080/dept/1
(6)解决方法
- 在MyRedisConfig类中添加代码如下:
@Primary//标注默认使用的CacheManager,不标注会报错
@Bean
public CacheManager deptCacheManager(RedisConnectionFactory factory) {
RedisSerializer<String> redisSerializer = new StringRedisSerializer();
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Department.class);
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer))
.disableCachingNullValues();
RedisCacheManager cacheManager = RedisCacheManager.builder(factory)
.cacheDefaults(config)
.build();
return cacheManager;
}
指定缓存需要哪一个CacheManager
-
EmployeeService类
-
DepartmentService类
这样就可以在缓存中查询employee和department中的数据了
四、编码方式的缓存
-
以上都是通过注解的方式来缓存,下面用编码的方式缓存
-
使用缓存管理器得到缓存,进行api调用
1.修改DepartmentService类
-
注释掉下面的方法
-
添加下面的代码
@Qualifier("deptCacheManager")//明确指定
@Autowired
private CacheManager deptCacheManager;//注入部门的缓存管理器
public Department getDeptById(Integer id){
System.out.println("查询部门"+id);
Department deptById = departmentMapper.getDeptById(id);
//拿到缓存,对编码增删改查
Cache dept = deptCacheManager.getCache("dept");
dept.put("dept:1",deptById);
return deptById;
}
- 重启项目,在浏览器中输入
http://localhost:8080/dept/1
可以看到缓存中已经传入了数据