1Spring缓存抽象
Spring从3.1开始定义了org.springframework.cache.Cache和org.springframework.cache.CacheManager接口来统一不同的缓存技术;
并支持使用JCache(JSR-107)注解简化我们开发;
Cache接口为缓存的组件规范定义,包含缓存的各种操作集合;
Cache接口下Spring提供了各种xxxCache的实现;如RedisCache,EhCacheCache , ConcurrentMapCache等;
每次调用需要缓存功能的方法时,Spring会检查检查指定参数的指定的目标方法是否已经被调用过;如果有就直接从缓存中获取方法调用后的结果,如果没有就调用方法并缓存结果后返回给用户。下次调用直接从缓存中获取。
使用Spring缓存抽象时我们需要关注以下两点;
1、确定方法需要被缓存以及他们的缓存策略
2、从缓存中读取之前缓存存储的数据
2几个重要概念&缓存注解
3@Cacheable/@CachePut/@CacheEvict 主要的参数
4Cache SpEL available metadata
5缓存使用
原理:
1、自动配置类;CacheAutoConfiguration
2、缓存的配置类
org.springframework.boot.autoconfigure.cache.GenericCacheConfiguration
org.springframework.boot.autoconfigure.cache.JCacheCacheConfiguration
org.springframework.boot.autoconfigure.cache.EhCacheCacheConfiguration
org.springframework.boot.autoconfigure.cache.HazelcastCacheConfiguration
org.springframework.boot.autoconfigure.cache.InfinispanCacheConfiguration
org.springframework.boot.autoconfigure.cache.CouchbaseCacheConfiguration
org.springframework.boot.autoconfigure.cache.RedisCacheConfiguration
org.springframework.boot.autoconfigure.cache.CaffeineCacheConfiguration
org.springframework.boot.autoconfigure.cache.GuavaCacheConfiguration
org.springframework.boot.autoconfigure.cache.SimpleCacheConfiguration【默认】
org.springframework.boot.autoconfigure.cache.NoOpCacheConfiguration
3、哪个配置类默认生效:SimpleCacheConfiguration;4、给容器中注册了一个CacheManager:ConcurrentMapCacheManager
5、可以获取和创建ConcurrentMapCache类型的缓存组件;他的作用将数据保存在ConcurrentMap中;运行流程:(跟踪这个类ConcurrentMapCacheManager)
@Cacheable:
1、方法运行之前,先去查询Cache(缓存组件),按照cacheNames指定的名字获取;
(CacheManager先获取相应的缓存),第一次获取缓存如果没有Cache组件会自动创建。
2、去Cache中查找缓存的内容,使用一个key,默认就是方法的参数;
key是按照某种策略生成的;默认是使用keyGenerator生成的,默认使用SimpleKeyGenerator生成key;
SimpleKeyGenerator生成key的默认策略;
如果没有参数;key=new SimpleKey();
如果有一个参数:key=参数的值
如果有多个参数:key=new SimpleKey(params);
3、没有查到缓存就调用目标方法;
4、将目标方法返回的结果,放进缓存中@Cacheable标注的方法执行之前先来检查缓存中有没有这个数据,默认按照参数的值作为key去查询缓存,
如果没有就运行方法并将结果放入缓存;以后再来调用就可以直接使用缓存中的数据;核心:
1)、使用CacheManager【ConcurrentMapCacheManager】按照名字得到Cache【ConcurrentMapCache】组件
2)、key使用keyGenerator生成的,默认是SimpleKeyGenerator
表
6 ConcurrentMapCache缓存
默认使用的是ConcurrentMapCacheManager==ConcurrentMapCache;将数据保存在 ConcurrentMap<Object, Object>
代码结构
基本代码
package com.feizhou.cache.bean;
import java.io.Serializable;
public class Employee implements Serializable {
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 + "]";
}
}
-----------------------
package com.feizhou.cache.controller;
import com.feizhou.cache.bean.Employee;
import com.feizhou.cache.service.EmployeeService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class EmployeeController {
@Autowired
EmployeeService employeeService;
@GetMapping("/emp/{id}")
public Employee getEmployee(@PathVariable("id") Integer id){
Employee employee = employeeService.getEmp(id);
return employee;
}
}
---------------------------------
package com.feizhou.cache.mapper;
import com.feizhou.cache.bean.Employee;
import org.apache.ibatis.annotations.*;
@Mapper
public interface EmployeeMapper {
@Select("SELECT * FROM employee WHERE id = #{id}")
public Employee getEmpById(Integer id);
@Update("UPDATE employee SET lastName=#{lastName},email=#{email},gender=#{gender},d_id=#{dId} WHERE id=#{id}")
public void updateEmp(Employee employee);
@Delete("DELETE FROM employee WHERE id=#{id}")
public void deleteEmpById(Integer id);
@Insert("INSERT INTO employee(lastName,email,gender,d_id) VALUES(#{lastName},#{email},#{gender},#{dId})")
public void insertEmployee(Employee employee);
@Select("SELECT * FROM employee WHERE lastName = #{lastName}")
Employee getEmpByLastName(String lastName);
}
---------------------------------
package com.feizhou.cache;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;
@MapperScan("com.feizhou.cache.mapper")
@SpringBootApplication
@EnableCaching
public class Springboot01CacheApplication {
public static void main(String[] args) {
SpringApplication.run(Springboot01CacheApplication.class, args);
}
}
-----------------application.properties----------------
spring.datasource.url=jdbc:mysql://localhost:3306/spring_cache
spring.datasource.username=root
spring.datasource.password=root
# 开启驼峰命名匹配规则
mybatis.configuration.map-underscore-to-camel-case=true
# 打开缓存日志
logging.level.com.feizhou.cache.mapper=debug
debug=true
---------------------------------
---------------------------------
测试代码Cacheable
package com.feizhou.cache.service;
import com.feizhou.cache.bean.Employee;
import com.feizhou.cache.mapper.EmployeeMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.*;
import org.springframework.stereotype.Service;
@Service
public class EmployeeService {
@Autowired
EmployeeMapper employeeMapper;
/**
* 将方法的运行结果进行缓存;以后再要相同的数据,直接从缓存中获取,不用调用方法;
* CacheManager管理多个Cache组件的,对缓存的真正CRUD操作在Cache组件中,每一个缓存组件有自己唯一一个名字;
*
*
* 原理:
*
* 几个属性:
* cacheNames/value:指定缓存组件的名字;将方法的返回结果放在哪个缓存中,是数组的方式,可以指定多个缓存;
* 案例: @Cacheable(cacheNames={"emp","emp2"},key ="#id")
*
*
* key:缓存数据使用的key;可以用它来指定。默认是使用方法参数的值 1-方法的返回值
* 编写SpEL; #id;参数id的值 #a0 #p0 #root.args[0]
* 案例: @Cacheable(cacheNames={"emp","emp2"},key ="#id")
*
*
*
* keyGenerator:key的生成器;可以自己指定key的生成器的组件id, key/keyGenerator:二选一使用;
* 案例: @Cacheable(value = {"emp"},keyGenerator = "myKeyGenerator")
*
*
* cacheManager:指定缓存管理器;或者cacheResolver指定获取解析器
*
*
*
* condition:指定符合条件的情况下才缓存;
* 案例: @Cacheable(cacheNames="emp",key ="#id" ,condition = "#id>0")
*
*
*
* unless:否定缓存;当unless指定的条件为true,方法的返回值就不会被缓存;可以获取到结果进行判断
* 案例: @Cacheable(cacheNames="emp",key ="#id" , unless ="#id == null")
* #id==null,就不缓存
*
*
* sync:是否使用异步模式
* @param id
* @return
*
*/
@Cacheable(cacheNames="emp")
public Employee getEmp(Integer id){
System.out.println("查询"+id+"号员工");
Employee emp = employeeMapper.getEmpById(id);
return emp;
}
}
1测试一般查询
第1次请求
第2次请求(没有查sql)
测试生成key
新增一个配置
测试
2@CachePut:既调用方法,又更新缓存数据;同步更新缓存
核心代码
测试
查询
还是去缓存查了,这是因为查询和更新的缓存key不一样
修改代码
测试
3 @CacheEvict:缓存清除
测试
清空缓存
查sql
4 @Caching 定义复杂的缓存规则
测试
查询其他
7redis缓存
1docker安装redis
https://hub.docker.com/_/redis/
镜像加速
docker pull registry.docker-cn.com/library/镜像名称
[root@bogon ~]# docker pull registry.docker-cn.com/library/redis
如果不使用镜像加速
[root@bogon ~]# docker pull redis
[root@bogon ~]# docker pull redis
Using default tag: latest
Trying to pull repository docker.io/library/redis ...
latest: Pulling from docker.io/library/redis
fc7181108d40: Pull complete
3e0ac67cad82: Pull complete
6ee495cb7235: Pull complete
9f7206d08b9d: Pull complete
a8354ef8cccb: Pull complete
53afb10d81c2: Pull complete
Digest: sha256:ca2d9251c2818df48c6598e01a5bdeab46040dc7ab236abe075d7c7343465177
Status: Downloaded newer image for docker.io/redis:latest
[root@bogon ~]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
[root@bogon ~]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
docker.io/tomcat latest 5377fd8533c3 2 weeks ago 506 MB
docker.io/redis latest 3c41ce05add9 2 weeks ago 95 MB
[root@bogon ~]# docker run -d -p 6379:6379 --name myredis docker.io/redis
f9233d3b3cf4077d024d531c00ed624c8c7341d544c7bc3d4dc8fd45db247e7b
[root@bogon ~]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
f9233d3b3cf4 docker.io/redis "docker-entrypoint..." 7 seconds ago Up 5 seconds 0.0.0.0:6379->6379/tcp myredis
测试
2项目中怎么引入redis(只要pom添加下面jar就行,什么的代码就变成redis了)
代码测试
package com.feizhou.cache;
import com.feizhou.cache.bean.Employee;
import com.feizhou.cache.mapper.EmployeeMapper;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.test.context.junit4.SpringRunner;
@RunWith(SpringRunner.class)
@SpringBootTest
public class Springboot01CacheApplicationTests {
@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("msg","hello");
String msg = stringRedisTemplate.opsForValue().get("msg");
System.out.println(msg);
}
@Test
public void contextLoads() {
// Employee empById = employeeMapper.getEmpById(1);
// System.out.println(empById);
}
}
测试保存序列化对象
测试保存josn对象
3CacheManagerCustomizers可以来定制缓存的一些规则
//CacheManagerCustomizers可以来定制缓存的一些规则 @Primary //将某个缓存管理器作为默认的 @Bean public RedisCacheManager employeeCacheManager(RedisTemplate<Object, Employee> empRedisTemplate){ RedisCacheManager cacheManager = new RedisCacheManager(empRedisTemplate); //key多了一个前缀 //使用前缀,默认会将CacheName作为key的前缀 cacheManager.setUsePrefix(true); return cacheManager; }
这里就不一一测试了,上面最初缓存的代码,在redis都可以用,截图结果如下
测试数据
代码位置
https://gitee.com/DanShenGuiZu/GongKaiZiYuan.git