我们在日常开发中很多数据可以放在缓存中增加查询效率,提升性能,比如:商品、验证码
JSR-107 缓存规范:
交互流程:缓存管理器里面存在多个缓存,例如:存储员工等等
CacheManage是用来管理缓存的, Cache是用来操作缓存的(定义接口)
如果要使用JSR-107,需要导入cache包
Spring缓存抽象
几个缓存注解
SpringBoot基本环境搭建
1、搭建基本环境 选择需要组件
2、创建数据库表
3、创建javabean实体封装数据
package com.xianyu.cache.bean;
public class Department {
private Integer id;
private String departmentName;
public Department() {
super();
// TODO Auto-generated constructor stub
}
public Department(Integer id, String departmentName) {
super();
this.id = id;
this.departmentName = departmentName;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getDepartmentName() {
return departmentName;
}
public void setDepartmentName(String departmentName) {
this.departmentName = departmentName;
}
@Override
public String toString() {
return "Department [id=" + id + ", departmentName=" + departmentName + "]";
}
}
package com.xianyu.cache.bean;
public class Employee {
private Integer id;
private String lastName;
private String email;
private Integer gender; //性别 1男 0女
private Integer dId;
public Employee() {
super();
}
public Employee(Integer id, String lastName, String email, Integer gender, Integer dId) {
super();
this.id = id;
this.lastName = lastName;
this.email = email;
this.gender = gender;
this.dId = dId;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public Integer getGender() {
return gender;
}
public void setGender(Integer gender) {
this.gender = gender;
}
public Integer getdId() {
return dId;
}
public void setdId(Integer dId) {
this.dId = dId;
}
@Override
public String toString() {
return "Employee [id=" + id + ", lastName=" + lastName + ", email=" + email + ", gender=" + gender + ", dId="
+ dId + "]";
}
}
4、整合Mybatis操作数据库
1、配置数据源信息(8.0版本记得在url后添加时区,不然会报错)
2、写sql语句
5、创建Service接口,并创建ServiceImpl实现该接口,之后调用mapper
6、创建Controller,使用Rest风格
7、启动服务,输入 http://localhost:8080/emp/1 测试发现did为null,但是数据库里有数据,是因为javabean实体内是驼峰命名,数据库不是
可以在mybatis数据源里面添加 mybatis.configuration.map-underscore-to-camel-case=true
重启下服务 ok
体验缓存
CacheManager管理多个Cache组件的,对缓存的真正CRUD操作在Cache组件中每个缓存都有自己名字
1、开启基于注解的缓存
2、启动项标注缓存注解即可 @EnableCaching
Service层添加缓存注解:@Cacheable
为了方便测试,数据源添加SQL日志打印
logging.level.com.xianyu.cache.mapper=debug
重启服务,开始测试是否将数据添加进缓存:
1、新添加一条数据
2、查询2号员工没有问题
3、控制台SQL打印说明进来该方法
4、我们再次访问没有问题,但是控制台空空如也,而且运行速度明显变快,说明没有进来该方法已经存到缓存当中(以后要相同的数据,直接从缓存中获取,不用调用方法)
Cacheable注解内属性的使用; 点开源码发现里面有很多属性
各个属性功能
cacheNames/value:指定缓存组件的名字;将方法的返回结果放在哪个缓存中,是数组的方式,可以指定多个缓存;
key:缓存数据使用的key;可以用它来指定。默认是使用方法参数的值 1-方法的返回值
编写SpEL; #i d;参数id的值 #a0 #p0 #root.args[0]
getEmp[2]
keyGenerator:key的生成器;可以自己指定key的生成器的组件id
key/keyGenerator:二选一使用;
cacheManager:指定缓存管理器;或者cacheResolver指定获取解析器
condition:指定符合条件的情况下才缓存;
condition = "#id>0"
condition = "#a0>1":第一个参数的值>1的时候才进行缓存
unless:否定缓存;当unless指定的条件为true,方法的返回值就不会被缓存;可以获取到结果进行判断
unless = "#result == null"
unless = "#a0==2":如果第一个参数的值是2,结果不缓存;
sync:是否使用异步模式
其他注解
@CachePut:既调用方法,又更新缓存数据;同步更新缓存
它可以在修改了数据库的某个数据时,同时更新缓存;
测试一下:
1、编写一个修改员工的接口
@GetMapping("/emp")
public Employee update(Employee employee){
Employee emp = employeeService.updateEmp(employee);
return emp;
}
Employee updateEmp(Employee employee);
//定义key = "#result.id":使用返回后的id
@Override
@CachePut(value = {"emp"},key = "#result.id")
public Employee updateEmp(Employee employee){
System.out.println("updateEmp:" + employee );
employeeMapper.updateEmp(employee);
return employee;
}
2、查询1号员工;和之前一样查到的结果会放在缓存中;
3、更新1号员工;【lastName:zhangsan;gender:1;email=555555@qq.com】
4、清空控制台,再次调用接口查询,发现已经同步更新到了缓存
CacheEvict:清除缓存
测试:1、查询1号员工,没有问题
2、执行删除这名员工
/**
* key:指定要清除的数据
* allEntries = true : 清除缓存中所有数据
* beforeInvocation = false : 缓存的清除是否在方法之前执行
* 默认false代表是在方法执行之后执行
* 如果是false,则万一程序报错,不会执行清除缓存,
* 如果是true在方法执行之前执行清除,则会先清除缓存在执行方法
*/
@CacheEvict(value = "emp",/*key = "#id"*/ allEntries = true)
public void deleteEmp(Integer id){
System.out.println("deleteEmp:"+id);
employeeMapper.deleteEmpById(id);
}
再次查询发现缓存内已经没有了
@Caching 定义复杂的缓存规则
可以多个组合同时用
@Override
@Caching(
cacheable = {
@Cacheable(value = "emp", key = "#lastName")
},put = {
@CachePut(value = "emp", key = "#result.id"),
@CachePut(value = "emp", key = "#result.email")
}
)
@CacheConfig 公共配置注解,添加后 ,下面所有的方法不需要在写这个属性,例如下面方法value属性就可以不加
整合Redis
我们默认使用缓存的是ConcurrentMapCacheManager==ConcurrentMapCache;将数据保存在 ConcurrentMap<Object, Object>中
开发中使用缓存中间件;redis、memcached、ehcache;这次以整合redis举例
三、整合redis作为缓存
Redis 是一个开源(BSD许可)的,内存中的数据结构存储系统,它可以用作数据库、缓存和消息中间件。中文官方网站 : http://www.redis.cn/
1、使用Redis Desktop Manager创建redis连接
打开官网,点击上面命令,可以看到操作的命令
右键连接,点击console,可以在下面输入命令
例如输入 append msg hello 就会创建一个key为msg ,Value为hello的键值对,数字10是长度;输入get msg就可以获取到key为msg的value值
操作list同理,具体使用看官网命令,就不多举例
2、引入redis的starter 配置redis
导入POM:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
数据源内引入redis主机地址
3、使用代码操作redis,导完包后可以自动注入
测试stringRedisTemplate 操作字符串
@Test
public void test01(){
//操作字符串,使用append命令
stringRedisTemplate.opsForValue().append("test01", "hello");
}
成功添加
别的操作同理
/**
* Redis常见的五大数据类型
* String(字符串)、List(列表)、Set(集合)、Hash(散列)、ZSet(有序集合)
* stringRedisTemplate.opsForValue()[String(字符串)]
* stringRedisTemplate.opsForList()[List(列表)]
* stringRedisTemplate.opsForSet()[Set(集合)]
* stringRedisTemplate.opsForHash()[Hash(散列)]
* stringRedisTemplate.opsForZSet()[ZSet(有序集合)]
*/
读取使用get(key)
测试redisTemplate 操作对象
//测试保存对象
@Test
public void test02(){
Employee empById = employeeMapper.getEmpById(2);
redisTemplate.opsForValue().set("emp-01", empById);
}
成功保存对象 //默认如果保存对象,使用jdb序列化机制,序列化后的数据保存到redis中
如果想存储正常的Json数据,可以自己创建配置Redis序列化器
@Bean
public RedisTemplate<Object, Employee> empRedisTemplate(
RedisConnectionFactory redisConnectionFactory)
throws UnknownHostException {
RedisTemplate<Object, Employee> template = new RedisTemplate<Object, Employee>();
template.setConnectionFactory(redisConnectionFactory);
Jackson2JsonRedisSerializer<Employee> ser = new Jackson2JsonRedisSerializer<Employee>(Employee.class);
template.setDefaultSerializer(ser);
return template;
}
自动注入,使用这个
@Autowired
private RedisTemplate<Object, Employee> empRedisTemplate;
结果可以直观显示