1、JSP107

java Caching定义了5个核心接口,分别是 CachingProvider, CacheManager,Cache,Entry,Expiry

  • CachingProvider:定义了创建、配置、获取、管理和控制多个ChacheManager。一个应用可以在运行期间访问多个CachingProvider
  • CacheManager:定义了创建、配置、获取和管理、控制多个唯一命名的Cahce。这些Cache存在于CacheManager的上下文中,一个CacheManager仅被一个CachingProvider所拥有。
  • Cache:是一个类似Map的数据结构并临时村吃以key为索引的值。一个Cache仅被一个CacheManager所拥有。
  • Entry:是一个存储在Cache中的key-value对。
  • Expiry:每一个存储在Cache中的条目有一个定义的有效期。一旦超过这个时间,条目为过期的状态。一旦过期,条目将不可访问、更新和删除。缓存有效期可以通过ExpiryPolicy设置。
  • image.png

    2、Spring 缓存抽象

    Spring3.1 开始定义了
    org.springframework.cache.Cache
    org.springframework.cacche.CacheManager 接口来统一不同的缓存技术;
    并支持使用JCache(JSR-107)注解简化我们开发;

Cache接口为缓存的组件规范定义,包含缓存的各种操作集合;

  • Cache接口下Spring提供了各种xxxCache的实现;如RedisCache,EhCacheCache , ConcurrentMapCache等;
  • 每次调用需要缓存功能的方法时,Spring会检查检查指定参数的指定的目标方法是否已经被调用过;如果有就直接从缓存中获取方法调用后的结果,如果没有就调用方法并缓存结果后返回给用户。下次调用直接从缓存中获取。
  • 使用Spring缓存抽象时我们需要关注以下两点;

1、确定方法需要被缓存以及他们的缓存策略
2、从缓存中读取之前缓存存储的数据

3、几个重要概念&缓存注解

Cache 缓存接口,定义缓存操作。实现有:RedisCache、EhCacheCaChe、ConcurrentMapCache等
CacheManager 缓存管理器,管理各种缓存(Cache)组件
@Cacheable 主要针对方法配置,能够根据方法的请求参数对其结果进行缓存
@CacheEvict 清空缓存
@EnableCaching 开启基于注解的缓存
@CachePut 保证方法被调用,又希望结果被缓存。
keyGenerator 缓存数据时key生成策略
serialize 缓存数据时value序列化策略
  1. /**
  2. * 搭建基本环境
  3. * 1、导入数据库文件 创建出 department 和 employee表
  4. * 2、创建 javaBean 封装数据
  5. * 3、整合 Mybatis 操作数据库
  6. * 1.配置数据源信息
  7. * 2.使用注解版的 MyBatis
  8. * 1)、@MapperScan 指定需要扫描的 mapper 接口所在的包
  9. */
  10. @MapperScan("com.atguigu.springboot.mapper")
  11. @SpringBootApplication
  12. public class Springboot01CacheApplication {
  13. public static void main(String[] args) {
  14. SpringApplication.run(Springboot01CacheApplication.class, args);
  15. }
  16. }

4.环境配置

配置文件 application.properties

  1. spring.datasource.url=jdbc:mysql://localhost:3306/spring_cache
  2. spring.datasource.username=root
  3. spring.datasource.password=000823
  4. #spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
  5. #开启驼峰命名匹配规则
  6. mybatis.configuration.map-underscore-to-camel-case=true

两个javaBean department employee

  1. package com.atguigu.springboot.bean;
  2. public class Department {
  3. private Integer id;
  4. private String departmentName;
  5. public Department() {
  6. super();
  7. // TODO Auto-generated constructor stub
  8. }
  9. public Department(Integer id, String departmentName) {
  10. super();
  11. this.id = id;
  12. this.departmentName = departmentName;
  13. }
  14. public Integer getId() {
  15. return id;
  16. }
  17. public void setId(Integer id) {
  18. this.id = id;
  19. }
  20. public String getDepartmentName() {
  21. return departmentName;
  22. }
  23. public void setDepartmentName(String departmentName) {
  24. this.departmentName = departmentName;
  25. }
  26. @Override
  27. public String toString() {
  28. return "Department [id=" + id + ", departmentName=" + departmentName + "]";
  29. }
  30. }
  1. package com.atguigu.springboot.bean;
  2. public class Employee {
  3. private Integer id;
  4. private String lastName;
  5. private String email;
  6. private Integer gender; //性别 1男 0女
  7. private Integer dId;
  8. public Employee() {
  9. super();
  10. }
  11. public Employee(Integer id, String lastName, String email, Integer gender, Integer dId) {
  12. super();
  13. this.id = id;
  14. this.lastName = lastName;
  15. this.email = email;
  16. this.gender = gender;
  17. this.dId = dId;
  18. }
  19. public Integer getId() {
  20. return id;
  21. }
  22. public void setId(Integer id) {
  23. this.id = id;
  24. }
  25. public String getLastName() {
  26. return lastName;
  27. }
  28. public void setLastName(String lastName) {
  29. this.lastName = lastName;
  30. }
  31. public String getEmail() {
  32. return email;
  33. }
  34. public void setEmail(String email) {
  35. this.email = email;
  36. }
  37. public Integer getGender() {
  38. return gender;
  39. }
  40. public void setGender(Integer gender) {
  41. this.gender = gender;
  42. }
  43. public Integer getdId() {
  44. return dId;
  45. }
  46. public void setdId(Integer dId) {
  47. this.dId = dId;
  48. }
  49. @Override
  50. public String toString() {
  51. return "Employee [id=" + id + ", lastName=" + lastName + ", email=" + email + ", gender=" + gender + ", dId="
  52. + dId + "]";
  53. }
  54. }

EmployeeMapper接口

  1. @Mapper
  2. public interface EmployeeMapper {
  3. @Select("select * from employee where id = #{id}")
  4. public Employee getEmpById(Integer id);
  5. @Update("update employee set lastName=#{lastName},email=#{email},gender=#{gender},d_id=#{dId} where id=#{id}")
  6. public void updateEmp(Employee employee);
  7. @Delete("delete from employee where id=#{id}")
  8. public void deleteEmpById(Integer id);
  9. @Insert("insert into employee(lastName,email,gender,d_id) values(#{lastName},#{email},#{gender},#{dId})")
  10. public void insertEmp(Employee employee);
  11. }

测试EmployeeMapper

  1. @SpringBootTest
  2. class Springboot01CacheApplicationTests {
  3. @Autowired
  4. EmployeeMapper employeeMapper;
  5. @Test
  6. void contextLoads() {
  7. Employee empById = employeeMapper.getEmpById(1);
  8. System.out.println(empById);
  9. }
  10. }

EmployeeService.java

  1. @Service
  2. public class EmployeeService {
  3. @Autowired
  4. EmployeeMapper employeeMapper;
  5. public Employee getEmp(Integer id){
  6. System.out.println("查询"+id+"号员工:");
  7. Employee emp = employeeMapper.getEmpById(id);
  8. return emp;
  9. }
  10. }

EmoloyeeController.java

  1. @RestController
  2. public class EmployeeController {
  3. @Autowired
  4. EmployeeService employeeService;
  5. @GetMapping("/emp/{id}")
  6. public Employee getEmployee(@PathVariable("id") Integer id){
  7. Employee emp = employeeService.getEmp(id);
  8. return emp;
  9. }
  10. }

环境搭建测试:
image.png

5.@cacheable注解

  1. * 二、快速体验缓存
  2. * 步骤:
  3. * 1.开启基于注解的缓存
  4. * 2.标注缓存注解即可
  5. * @Cacheables
  6. * @CacheEvict
  7. * @CachePut
  8. */
  9. @MapperScan("com.atguigu.springboot.mapper")
  10. @SpringBootApplication
  11. @EnableCaching
  1. /**
  2. * 将方法的运行结果进行缓存:以后再要相同的数据直接从缓存中获取不用调用方法
  3. *
  4. * CacheManager管理多个Cache组件的,对缓存真正的crud操作在缓存组件中,每一个缓存组件有自己唯一一个名字;
  5. * 几个属性:
  6. * cacheName/value:指定缓存的名字;
  7. * key:缓存数据时用的 key ;可以用它指定。默认是使用方法参数的值 1-方法的返回值
  8. * 编写 Spel表达式:#id;参数id的值 == #a0,#p0,#root.args[0]
  9. * keyGenerator:key的生成器,可以自己指定key的生成器组件id
  10. * key 和 keyGenerator 二选一
  11. * CacheManager:指定缓存管理器;或者cacheResolver 缓存解析器 二选一
  12. * condition:指定符合条件的情况下才缓存;
  13. * unless:否定缓存;当unless指定的条件为true,方法的返回值不会被缓存;可以获取到结果进行判断
  14. * sync:是否使用异步模式
  15. * @param id
  16. * @return
  17. */
  18. @Cacheable(cacheNames = "emp")
  19. public Employee getEmp(Integer id){
  20. System.out.println("查询"+id+"号员工:");
  21. Employee emp = employeeMapper.getEmpById(id);
  22. return emp;
  23. }

当第二次查找id=1的用户时,不会从数据库进行查找,而是从缓存中进行查找

5.1.缓存工作原理 和 @Cacheable 运行流程

  1. * 原理:
  2. * 1.自动配置类;
  3. * 2.缓存的配置类:
  4. * 0 = "org.springframework.boot.autoconfigure.cache.GenericCacheConfiguration"
  5. * 1 = "org.springframework.boot.autoconfigure.cache.JCacheCacheConfiguration"
  6. * 2 = "org.springframework.boot.autoconfigure.cache.EhCacheCacheConfiguration"
  7. * 3 = "org.springframework.boot.autoconfigure.cache.HazelcastCacheConfiguration"
  8. * 4 = "org.springframework.boot.autoconfigure.cache.InfinispanCacheConfiguration"
  9. * 5 = "org.springframework.boot.autoconfigure.cache.CouchbaseCacheConfiguration"
  10. * 6 = "org.springframework.boot.autoconfigure.cache.RedisCacheConfiguration"
  11. * 7 = "org.springframework.boot.autoconfigure.cache.CaffeineCacheConfiguration"
  12. * 8 = "org.springframework.boot.autoconfigure.cache.SimpleCacheConfiguration"
  13. * 9 = "org.springframework.boot.autoconfigure.cache.NoOpCacheConfiguration"
  14. * 3.哪个配置类默认生效:SimpleCacheConfiguration
  15. *
  16. * 4.给容器中注册了一个CacheManager CacheManager ==> ConcurrentMapCacheManager
  17. * 5.可以获取和创建 ConcurrentMapCacheManager 类型的 缓存组件;他的作用是将数据保存在 ConcurrentMap 里面;
  18. *
  19. * 运行流程:
  20. * 1.方法运行之前,先去查询Cache(缓存组件),按照 cacheName指定的名字获取;
  21. * CacheManager先获取相应的缓存),第一次获取缓存如果没有 cache 组件会先创建出来
  22. * 2. Cache 中查找缓存的内容,使用一个 key ,默认方法的参数;
  23. * key 是按照某种策略生成的;默认是使用 keyGenerator 生成的,默认使用 SimpleKeyGenerator 生成 key
  24. * SimpleKeyGenerator 生成 key 的默认策略:
  25. * 如果没有参数,key=new SimpleKey();
  26. * 如果有一个参数:key=参数的值
  27. * 如果有多个参数:key=new SimpleKey(params);
  28. * 3.没有查到缓存调用目标方法;
  29. * 4.将目标方法缓存的结果放到缓存里面
  30. *
  31. * @Cacheable标注的方法执行之前,先来检查缓存中有没有数据,按照参数的值作为 key 去查询缓存
  32. * 如果没有就运行,并将结果放入缓存;以后再来调用就可以直接使用缓存中的数据
  33. *
  34. * 核心:
  35. * 1.使用 CacheManagerConcurrentMapCacheMapper】按照名字得到 CacheConcurrentMapCache】组件
  36. * 2.key 使用 KeyGenerator生成的,默认是 SimpleKeyGenerator

5.2.@Cacheable的其他属性

5.2.1 sepl 表达式

image.png

5.2.3 属性详解

cacheName/value:指定缓存的名字;将方法的缓存结果放在哪个缓存中,数组的方式可以指定多个缓存

key:缓存数据时用的 key ;可以用它指定。默认是使用方法参数的值 1-方法的返回值
编写 Spel表达式:#id;参数id的值 == #a0,#p0,#root.args[0]
* getEmp[2] =====> key = “#root.methodName+’[‘+#id+’]’”

keyGenerator:key的生成器,可以自己指定key的生成器组件id
key 和 keyGenerator 二选一

  1. @Configuration
  2. public class MyCacheConfig {
  3. @Bean("myKeyGenerator")
  4. public KeyGenerator keyGenerator(){
  5. return new KeyGenerator(){
  6. @Override
  7. public Object generate(Object o, Method method, Object... objects) {
  8. return method.getName()+"["+ Arrays.asList(objects) +"]";
  9. }
  10. };
  11. }
  12. }

CacheManager:指定缓存管理器;或者cacheResolver 缓存解析器 二选一
condition:指定符合条件的情况下才缓存;
condition = “#id > 0”:具体参数的值条件
condition = “#a0 > 0”:第一个参数的值大于 1 进行缓存

unless:否定缓存;当unless指定的条件为true,方法的返回值不会被缓存;可以获取到结果进行判断
unless = “#a0 == 2”:如果第一个参数的值等于 2 ,结果不缓存

* sync:是否使用异步模式

6.其他属性

6.1 @CachePut注解

  1. /**
  2. * @CachePut: 既调用缓存,又更新缓存数据;同步更新缓存
  3. * 修改了数据库的某个是数据,同时更新缓存;
  4. * 运行时机:
  5. * 1.先调用目标方法,
  6. * 2.将目标方法的结果缓存起来
  7. *
  8. * 测试步骤:
  9. * 1.查询 1 号员工,查到的结果会放在缓存中
  10. * 2.以后查询还是之前的结果
  11. * 3.更新 1 号员工
  12. * 将方法的返回值,放入缓存
  13. * key:传入的employee对象 value:返回的employee对象
  14. * 4.查询 1 号员工?
  15. * 应该是更新后的员工【1号员工没有在缓存中更新】,
  16. * key = "#employee" 使用传入的参数员工id
  17. * key = "#result.id"
  18. * @Cacheable的 key 不能用result取结果
  19. *
  20. * 为什么是更新之前的员工信息?
  21. *
  22. */
  23. @CachePut(value = "emp",key = "#employee.id")
  24. public Employee updateEmp(Employee employee){
  25. System.out.println("update emp"+employee);
  26. employeeMapper.updateEmp(employee);
  27. return employee;
  28. }

6.2 @CacheEvict

  1. /**
  2. * @CacheEvict:缓存清除
  3. * key:指定要删除的数据
  4. * allEntries = true 删除所有缓存数据
  5. * beforeInvocation = false :缓存的清除是否在方法之前执行
  6. * 默认是在方法执行之后执行,如果方法出现异常,不会清除缓存
  7. * beforeInvocation = true:
  8. * 代表清除缓存实在方法执行之前进行的,无论方法是否出现异常缓存都会清除
  9. */
  10. @CacheEvict(value = "emp" ,key = "#id",allEntries = true)
  11. public void deleteEmp(Integer id){
  12. System.out.println("删除员工");
  13. //employeeMapper.deleteEmpById(id);
  14. }

6.3 @Caching&@CacheConfig

  1. /**
  2. * 定义复杂的缓存规则
  3. * @param lastName
  4. * @return
  5. */
  6. @Caching(
  7. cacheable = {
  8. @Cacheable(value = "emp",key = "#lastName")
  9. },
  10. put = {
  11. @CachePut(value = "emp",key = "#result.id"),
  12. @CachePut(value = "emp",key = "#result.email")
  13. }
  14. )
  15. public Employee getEmpByLastName(String lastName){
  16. Employee empByLastName = employeeMapper.getEmpByLastName(lastName);
  17. return empByLastName;
  18. }
  1. @CacheConfig(cacheNames = {"emp"})//抽取缓存的公共配置

7.整合redis

Redis 是一个开源(BSD许可)的,内存中的数据结构存储系统,它可以用作数据库、缓存和消息中间件。
1.安装 redis ,使用 docker;
2.引入 redis 的 starter
3.配置 redis

7.1 docker命令

  1. [root@192 ~]# sudo systemctl start docker
  2. [root@192 ~]# docker images
  3. REPOSITORY TAG IMAGE ID CREATED SIZE
  4. redis latest ddcca4b8a6f0 11 days ago 105MB
  5. hello-world latest d1165f221234 5 months ago 13.3kB
  6. [root@192 ~]# docker run -d -p 6379:6379 --name myredis redis
  7. [root@192 ~]# docker restart b8f9871d4cbb

7.2 整合测试

引入starter

  1. <dependency>
  2. <groupId>org.springframework.boot</groupId>
  3. <artifactId>spring-boot-starter-data-redis</artifactId>
  4. </dependency>

配置文件 application.roperties

  1. spring.redis.host=192.168.75.137
  1. @Autowired
  2. StringRedisTemplate stringRedisTemplate;//操作 kv 字符串
  3. @Autowired
  4. RedisTemplate redisTemplate;// kv 都是对象的
  5. @Autowired
  6. RedisTemplate<Object,Employee> empRedisTemplate;// kv 都是对象的
  7. /**
  8. * Redis 常见的五大数据类型
  9. * String(字符串)、List(列表)、set(集合)、Hash(散列)、zSet(有序集合)
  10. * stringRedisTemplate.opsForValue();简化操作string
  11. * stringRedisTemplate.opsForList();[List(列表)]
  12. * stringRedisTemplate.opsForSet();[set(集合)]
  13. * stringRedisTemplate.opsForHash();[Hash(散列)]
  14. * stringRedisTemplate.opsForZSet();[zSet(有序集合)]
  15. * */
  16. @Test
  17. public void test01(){
  18. //给 redis 保存一个数据
  19. //stringRedisTemplate.opsForValue().append("msg","hello");
  20. //读取 key 的值
  21. String msg = stringRedisTemplate.opsForValue().get("msg");
  22. System.out.println(msg);
  23. stringRedisTemplate.opsForList().leftPushAll("mylist","1","2","3");
  24. }
  25. /**
  26. * 测试保存对象
  27. */
  28. @Test
  29. public void t2(){
  30. Employee empById = employeeMapper.getEmpById(1);
  31. //默认保存对象,使用 jdk 序列化机制,序列化后的数据保存到数据库中
  32. //redisTemplate.opsForValue().set("emp-01",empById);

解决redis显示数据问题

  1. @Configuration
  2. public class MyRedisConfig {
  3. @Bean
  4. public RedisTemplate<Object, Employee> empRedisTemplate(
  5. RedisConnectionFactory redisConnectionFactory
  6. ){
  7. RedisTemplate<Object,Employee> template = new RedisTemplate<Object,Employee>();
  8. template.setConnectionFactory(redisConnectionFactory);
  9. Jackson2JsonRedisSerializer<Employee> serializer = new Jackson2JsonRedisSerializer<Employee>(Employee.class);
  10. template.setDefaultSerializer(serializer);
  11. return template;
  12. }
  13. }
  1. @Test
  2. public void t2(){
  3. Employee empById = employeeMapper.getEmpById(1);
  4. //默认保存对象,使用 jdk 序列化机制,序列化后的数据保存到数据库中
  5. //redisTemplate.opsForValue().set("emp-01",empById);
  6. //1.将数据以json的方式保存
  7. //自己将对象转为 json
  8. //默认的序列化规则;改变默认的序列化规则
  9. empRedisTemplate.opsForValue().set("emp-01",empById);
  10. }

7.3 测试缓存

解决存取对象序列化问题

  1. /**
  2. * 基于SpringBoot2 对 RedisCacheManager 的自定义配置
  3. * @param redisConnectionFactory
  4. * @return
  5. */
  6. @Primary//将某个缓存管理器作为默认使用的
  7. @Bean
  8. public RedisCacheManager employeeCacheManager(RedisConnectionFactory redisConnectionFactory) {
  9. //初始化一个RedisCacheWriter
  10. RedisCacheWriter redisCacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory);
  11. //设置CacheManager的值序列化方式为json序列化
  12. RedisSerializer<Object> jsonSerializer = new GenericJackson2JsonRedisSerializer();
  13. RedisSerializationContext.SerializationPair<Object> pair = RedisSerializationContext.SerializationPair.fromSerializer(jsonSerializer);
  14. RedisCacheConfiguration defaultCacheConfig = RedisCacheConfiguration.defaultCacheConfig().serializeValuesWith(pair);
  15. //设置默认超过时期是1天
  16. defaultCacheConfig.entryTtl(Duration.ofDays(1));
  17. //初始化RedisCacheManager
  18. return new RedisCacheManager(redisCacheWriter, defaultCacheConfig);
  19. }
  20. }

编码方式进行缓存操作

  1. @Autowired
  2. DepartmentMapper departmentMapper;
  3. @Qualifier("employeeCacheManager")
  4. @Autowired
  5. RedisCacheManager deptCacheManager;
  6. /**
  7. * 使用缓存管理器得到缓存,进行api调用
  8. * @param id
  9. * @return
  10. */
  11. public Department getDeptById(Integer id){
  12. System.out.println("查询部门");
  13. Department department = departmentMapper.getDeptById(id);
  14. //获取某个缓存
  15. Cache dept = deptCacheManager.getCache("dept");
  16. dept.put("dept:1",department);
  17. return department;
  18. }