Spring Boot 与缓存 创建项目结构
集成开发工具 IDEA 2020.2 , 使用 spring 项目搭建向导创建
一、搭建基本环境 导入数据库文件,创建出department 和 employee 表 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 create database springboot_cache;CREATE TABLE `department` ( `id` int (11 ) NOT NULL AUTO_INCREMENT, `departmentName` varchar (255 ) DEFAULT NULL , PRIMARY KEY (`id`) ) ENGINE= InnoDB DEFAULT CHARSET= utf8; CREATE TABLE `employee` ( `id` int (11 ) NOT NULL AUTO_INCREMENT, `lastName` varchar (255 ) DEFAULT NULL , `email` varchar (255 ) DEFAULT NULL , `gender` int (2 ) DEFAULT NULL , `d_id` int (11 ) DEFAULT NULL , PRIMARY KEY (`id`) ) ENGINE= InnoDB DEFAULT CHARSET= utf8;
创建 javaBean 封装数据 【Department.java】
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 public class Department { private Integer id; private String departmentName; public Department () { super (); } 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 + "]" ; } }
【Employee.java】
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 public class Employee { private Integer id; private String lastName; private String email; private Integer gender; 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 + "]" ; } }
整合 MyBatis 操作数据库
1)、配置数据源信息
1 2 3 4 5 6 7 8 9 10 spring.datasource.url =jdbc:mysql://localhost:3306/springboot_cache?serverTimezone=GMT%2B8 spring.datasource.username =root spring.datasource.password =1234 spring.datasource.driver-class-name =com.mysql.cj.jdbc.Driver mybatis.configuration.map-underscore-to-camel-case =true logging.level.com.oy.springboot.mapper =debug
2)、使用注解版的 Mybatis:
@MapperScan 指定需要扫描的 mapper 接口所在的包 1 2 3 4 5 6 7 8 @MapperScan("com.oy.springboot.mapper") @SpringBootApplication public class SpringBoot01CacheApplication { public static void main (String[] args) { SpringApplication.run(SpringBoot01CacheApplication.class, args); } }
二、快速体验缓存 ==步骤:==
① 开启基于注解的缓存 @EnableCaching 1 2 3 4 5 6 7 8 9 10 @MapperScan("com.oy.springboot.mapper") @SpringBootApplication @EnableCaching public class SpringBoot01CacheApplication { public static void main (String[] args) { SpringApplication.run(SpringBoot01CacheApplication.class, args); } }
② 标注缓存注解即可 @Cacheable @CacheEvict @CachePut 【EmployeeMapper】
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 @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 insertEmpById (Integer id) ; @Insert("insert into employee(lastName, email, gender, d_id) values(#{lastName}, #{email}, #{gender},#{dId})") public void insertEmployee (Employee employee) ; }
【EmployeeService】
1 2 3 4 5 6 7 8 9 10 11 12 13 @Service public class EmployeeService { @Autowired EmployeeMapper employeeMapper; @Cacheable(value = {"emp"}) public Employee getEmp (Integer id) { System.out.println("查询" +id + "号员工" ); Employee emp = employeeMapper.getEmpById(id); return emp; } }
【EmployeeController】
1 2 3 4 5 6 7 8 9 10 11 12 @RestController public class EmployeeController { @Autowired EmployeeService employeeService; @GetMapping("emp/{id}") public Employee getEmployee (@PathVariable("id") Integer id) { Employee emp = employeeService.getEmp(id); return emp; } }
测试 :
发送第二次请求,查看控制台没有发生改变(说明缓存生效 )
三、缓存原理 ① 重要的概念&缓存注解 注解 描述 Cache 缓存接口,定义缓存的操作。实现有:RedisCache、EhCacheCache、ConcurrentMapCache 等 CacheManager 缓存管理器,管理各种缓存(Cache )组件 @Cacheable 主要针对方法配置,能根据方法的请求参数对其结果进行缓存 @CacheEvict 清空缓存 @CachePut 保证方法被调用,又希望结构别缓存 @EnableCaching 开启基于注解的缓存 keyGenerator 缓存数据时 key 生成策略 serialize 缓存数据时 value 序列化策略
② CacheManager 将方法的运行结果进行缓存:以后再要相同的数据,直接从缓存中获取,不在调用方法:
CacheManager 管理多个 Cache 组件的,对缓存的真正 CRUD(增删查改)操作在 Cache 组件中,每一个缓存组件有自己的唯一一个名字。
原理 :
自动配置类:CacheAutoConfiguration 缓存的配置类: 1 2 3 4 5 6 7 8 9 10 11 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
SimpleCacheConfiguration 配置类默认生效 在【application.properties】配置
控制台输出日志:
给容器中注册一个 CacheManager: ConcurrentMaCacheManager: 可以获取和创建 ConcurrentMapCache 类型的缓存组件;它的作用将数据保存砸 ConcurrentMap 中。 ③ @Cacheble 运行流程 :
方法运行之前,先去查看 Cache(缓存组件),按照 cacheName 指定的名字获取;(CacheManager 先获取相对应的缓存),第一次获取缓存如果没有 Cache 组件会自动创建。
去 Cache 中查找缓存的内容,使用一个 key,默认一个 key,默认就是方法的参数;
key 是按照某种策略生成的,默认是使用 keyGenerator 生成的 key:
SimpleKeyGenerator 生成 key 的默认策略:
如果没有参数; key=new SimpleKey()
如果一个参数: key=参数的值
如果有多个参数: key=new SimpleKey(params);
没有查到缓存就调用目标方法:
将目标方法返回的结果,放进缓存中
@Cacheable 标注的方法执行之前先来检查缓存中有没有这个数据,默认按照参数的值作为 key 去查询缓存,如果没有就运行方法并将结果放入缓存;以后再来调用就可以直接使用缓存中的数据。
核心 :
使用 CacheManager【ConcurrentMapCacheManager】按照名字得到 Cache【ConcurrentMapCache】组件 key 使用 keyGenerator 生成的,默认是 SimpleKeyGenerator 属性 :
CacheNames/value : 指定缓存组件的名字;将方法的返回结果放在哪个缓冲区,是数组的方式,可以指定多个缓存:key : 缓存数据使用 key: 可以用它来指定,默认是使用方法参数 1-方法的返回值编写 SqEL: #id; 参数 id 的值 #a0 #p0 #root.args[0] getEmp[2] keyGenerator : key 的生成器:可以自己指定 key 的生成器的组件 idkey / keyGenerator: 二选一使用 cacheManager : 指定的缓存管理器: 或者 cacheResolver 指定获取解析器condition : 指定符号条件的情况下才缓存:eg: condition = “#a0 >1” : 第一个参数的值 > 1 的时候才会被缓存; 可以获取到结果进行判断 unless : 否定缓存: 当 unless 指定的条件为 true, 方法的返回值就不会被缓存;可以获取到结果进行判断unless = “#result == null” unless = “#a0 ==2”: 如果第一个参数的值为 2,结果不缓存; sync : 是否使用异步模式示例 :
1 2 3 4 5 6 @Cacheable(value = {"emp"},keyGenerator = "myKeyGenerator",condition = "#a0>1",unless = "#a0==2") public Employee getEmp (Integer id) { System.out.println("查询" +id+"号员工" ); Employee emp = employeeMapper.getEmpById(id); return emp; }
注意使用keyGenerator 需要自行配置(参考):
【keyGenerator】以 getEmp[2] 为例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 @Configuration public class MyCacheConfig { @Bean("myKeyGenerator") public KeyGenerator keyGenerator () { return new KeyGenerator (){ @Override public Object generate (Object o, Method method, Object... objects) { return method.getName() + "[" + Arrays.asList(objects).toString()+"]" ; } }; } }
④ @CachePut 即调用目标的方法,有更新缓存数据;同步更新缓存
修改了数据库的某个数据,同时更新缓存;
运行机制 :
先调用目标方法 将目标方法的结果缓存起来 测试步骤 :
查询 1 号员工: 查到的结果会放在缓存中:
更新 1 号员工:【lastName: AAA; gender:0】
在次查询 1 号员工(缓存没有更新)
解决方式 :
1 2 3 4 5 6 @CachePut(value = "emp", key = "#result.id") public Employee updateEmp (Employee employee) { System.out.println("updateEmp:" + employee); employeeMapper.updateEmp(employee); return employee; }
可设置参数约束条件:
⑤ @CacheEvict 缓存清除
key : 指定要清除的数据
beforeInvocaion = false : 缓存的清除是否在方法之前执行
默认代表缓存清除的操作是在方法执行之后;如果出现异常缓存就不会清除
beforeInvocation = true : 代表清除缓存操作在方法运行之前执行,无论方法是否出现异常,缓存都要清除。
1 2 3 4 5 6 7 8 @CacheEvict(value = "emp", key = "#id") public void deleteEmp (Integer id) { System.out.println("deleteEmp:" + id); }
⑥ @Caching 定义复杂的缓存规则
1 2 3 4 5 6 7 8 9 10 11 12 @Caching( cacheable = { @Cacheable(/*value="emp",*/key = "#lastName") }, put = { @CachePut(/*value="emp",*/key = "#result.id"), @CachePut(/*value="emp",*/key = "#result.email") } ) public Employee getEmpByLastName (String lastName) { return employeeMapper.getEmpByLastName(lastName); }
⑦ CacheConfig 抽取缓存的公共配置
1 2 3 @CacheConfig(cacheNames="emp"/*,cacheManager = "employeeCacheManager"*/) @Service public class EmployeeService {
四、整合 Redis 作为缓存 Redis 是一个开源(BSD 许可)的,内存中的数据结构存储系统,它可以作数据库、缓存和消息中间件。
安装 redis: 使用 Docker 引入 redis 的 starter 1 docker run -d -p 6379:6379 --name myredis redis
配置 redis (下载软件 RedisDesktopManager)
4、Redis 常见 的五大数据类型
String(字符串)、List(列表)、Set(集合)、Hash(散列)、Zset(有序集合) stringRedisTemplate.opsForList()[List(列表)] stringRedisTemplate.opsForSet()[Set(集合)] stringRedisTemplate.opsForHash()[Hash(散列)] stringRedisTemplate.opsForZSet()[ZSet(有序集合)] 5、测试缓存
原理:CacheManager === Cache 缓存组件来实际缓存中存取数据
① 引入 redis 的 starter,容器中保存的是 RedisCacheManager;
1 2 3 4 <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-data-redis</artifactId > </dependency >
在配置文件中配置
1 spring.redis.host =192.168.64.129
② RedisCacheManager 帮我们创建 RedisCache 来作为缓存组件;RedisCache 通过操作 redis 缓存数据的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 @Autowired StringRedisTemplate stringRedisTemplate; @Autowired RedisTemplate redisTemplate; @Test public void test1 () { stringRedisTemplate.opsForValue().append("msg" ,"hello" ); String msg = stringRedisTemplate.opsForValue().get("msg" ); System.out.println(msg); stringRedisTemplate.opsForList().leftPush("mylist" ,"1" ); stringRedisTemplate.opsForList().leftPush("mylist" ,"2" ); }
③ 默认保存数据 k-v 都是 Object; 利用序列化保存;
1)引入了 redis 的 starter, cacheManager 变为 RedisChacheManager;
1 2 3 4 <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-data-redis</artifactId > </dependency >
2) 默认创建的 RedisCacheManager 操作 redis 的时候使用的是 RedisTemplate<Object, Object>
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 @Autowired EmployeeMapper employeeMapper; @Autowired StringRedisTemplate stringRedisTemplate; @Autowired RedisTemplate redisTemplate; @Test public void test02 () { Employee empById = employeeMapper.getEmpById(1 ); redisTemplate.opsForValue().set("emp-01" ,empById); }
3) RedisTemplate<Object, Object> 是默认使用 jdk 的序列化机制
【MyRedisTemplateConfig】
1 2 3 4 5 6 7 8 9 10 11 12 @Configuration public class MyRedisTemplateConfig { @Bean public RedisTemplate<Object, Employee> employeeRedisTemplate (RedisConnectionFactory redisConnectionFactory) { RedisTemplate<Object, Employee> template = new RedisTemplate <>(); template.setConnectionFactory(redisConnectionFactory); Jackson2JsonRedisSerializer<Employee> ser = new Jackson2JsonRedisSerializer <>(Employee.class); template.setDefaultSerializer(ser); return template; } }
测试 :
1 2 3 4 5 6 7 8 9 @Resource RedisTemplate<Object, Employee> employeeRedisTemplate; @Test public void test02 () { Employee empById = employeeMapper.getEmpById(1 ); employeeRedisTemplate.opsForValue().set("emp-01" ,empById); }
① 自定义 CacheManager 1、 SpringBoot 1.x 版本的 RedisCacheManager 配置 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 @Bean public RedisCacheManager empCacheManager (RedisTemplate<Object, Employee> empRedisTemplate) { RedisCacheManager cacheManager = new RedisCacheManager (empRedisTemplate); cacheManager.setUsePrefix(true ); return cacheManager; } }
但是如果我们仅仅自定义这一个 CacheManager 则只能操作 Employee 这一种类型的数据,因为这个 CacheMananger 只实现了 Employee 的泛型,操作其他类型就会报错(可以正常缓存其他类型的数据,但是从缓存中查询出的数据在反序列化时会报错)。这时我们就需要自定义多个 CacheManager,比如增加一个可以缓存 Department 类型的 CacheMananger:
1 2 3 4 5 6 7 8 9 10 @Bean public RedisCacheManager deptCacheManager (RedisTemplate<Object, Department> deptRedisTemplate) { RedisCacheManager cacheManager = new RedisCacheManager (deptRedisTemplate); cacheManager.setUsePrefix(true ); return cacheManager; }
当容器中有多个 RedisCacheManager 的时候,需要使用@Primary 指定一个默认的
2、SpringBoot 2.x 版本的 RedisCacheManager 配置 1 2 3 4 5 6 7 8 9 10 11 12 @Bean public CacheManager cacheManager (RedisConnectionFactory redisConnectionFactory) { RedisCacheConfiguration cacheConfiguration = RedisCacheConfiguration.defaultCacheConfig() .entryTtl(Duration.ofDays(1 )) .disableCachingNullValues() .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer ())); return RedisCacheManager.builder(redisConnectionFactory).cacheDefaults(cacheConfiguration).build(); }
3、测试 1 2 3 4 5 6 @Cacheable(cacheNames = "dept",cacheManager ="cacheManager") public Department getDeptById (Integer id) { System.out.println("查询部门:" + id); Department department = deptMapper.getDeptById(id); return department; }