• SpringBoot08-缓存


    JSR107缓存规范

    1. Java Caching定义了5个核心接口, 分别是CachingProvider, CacheManager, Cache, Entry, Expiry.
    2. CachingProvider定义了创建, 配置, 获取, 管理和控制多个CacheManager. 一个应用可以在运行期访问多个CachingProvider.
    3. CacheManager定义了创建, 配置, 获取, 管理和控制多个唯一命名的Cache, 这些Cache存在于CacheManager的上下文中. 一个CacheManager仅被一个CachingProvider所拥有.
    4. Cache是一个类似Map的数据结构并临时存储以Key为索引的值, 一个Cache仅被一个CacheManager所拥有.
    5. Entry是一个存储在Cache中的key-value对.
    6. Expiry每一个存储在Cache中的条目有一个定义的有效期. 一旦超过这个时间, 条目为过期的状态. 一旦过期, 条目将不可访问, 更新和删除. 缓存有效期可以通过ExpiryPolicy设置.
    7. 如果要使用JSR107, 需要导入如下依赖
              <dependency>
                  <groupId>javax.cache</groupId>
                  <artifactId>cache-api</artifactId>
                  <version>1.1.1</version>
              </dependency>

    Spring缓存抽象

    • 简介
    1. JSR107规范的技术复杂性较高, Spring3.1定义了Cache和CacheManager接口来统一不同的缓存技术.
      • 当然, 支持使用JSR107注解来简化我们的开发.
    2. Cache接口为缓存的组件规范定义, 包含缓存的各种操作集合.
      • Cache接口下Spring提供了各种xxxCache的实现, 如RedisCache.
    3. 每次调用需要缓存功能的方法时, Spring会检查检查指定参数的指定的目标方法是否已经被调用过. 如果有就直接从缓存中获取方法调用后的结果, 如果没有就调用方法并缓存结果后返回给用户. 下次调用直接从缓存中获取.
    4. 使用Spring缓存抽象时需要关注两点
      • 确定方法需要被缓存以及他们的缓存策略.
      • 从缓存中读取之前缓存存储的数据.
    • 几个重要概念
    1. Cache: 缓存接口, 定义缓存操作, 实现有RedisCache, EhCacheCache等.
    2. CacheManager: 缓存管理器, 管理各种Cache.
    3. @Cacheable: 主要针对方法配置, 能根据方法的请求参数对其结果进行缓存.
    4. @CacheEvict: 清空缓存, 如给删除方法上加该注解.
    5. @CachePut: 保证方法被调用, 又希望结果被缓存, 经常用于更新缓存.
      • 和@Cacheable的区别
        • @Cacheable是注解中有就不调用方法了.
        • 而@CachePut无论如何都会调用方法, 然后把新结果存入缓存.
    6. @EnableCaching: 开启基于注解的缓存.
    7. keyGenerator: 缓存数据时key生成策略.
    8. serialize: 缓存数据时value序列化策略.
    • 搭建缓存的基本环境
    1. 引入spring-boot-starter-cache模块
              <dependency>
                  <groupId>org.springframework.boot</groupId>
                  <artifactId>spring-boot-starter-cache</artifactId>
              </dependency>
    2. 主启动类
      /**
       * 搭建基本环境
       * 1. 创建数据库spring_cache, 并创建department和employee表
       * 2. 创建javaBean封装数据
       * 3. 整合Mybatis操作数据库
       *      1. 配置数据源信息
       *      2. 使用注解版Mybatis
       *          1) @MapperScan指定需要扫描的mapper接口所在的包
       *          2) 对应的mapper接口要带@Mapper主机
       *
       * 快速体验缓存
       * 1. 开启基于注解的缓存.
       * 2. 标注缓存注解
       */
      
      @EnableCaching
      @MapperScan("top.binwenhome.cache.mapper")
      @SpringBootApplication
      public class SpringBootCache2Application {
      
          public static void main(String[] args) {
              SpringApplication.run(SpringBootCache2Application.class, args);
          }
      
      }
    3. 如EmployeeMapper
      @Mapper
      public interface EmployeeMapper {
      
          @Select("SELECT * FROM employee WHERE id = #{id}")
          public Employee getEmpById(Integer id);
      }
    4. 搭建service, controller层
      @Service
      public class EmployeeService {
      
          @Autowired
          private EmployeeMapper employeeMapper;
      
          public Employee getEmp(Integer id) {
              System.out.println("查询" + id + "号员工");
      
              Employee emp = employeeMapper.getEmpById(id);
      
              return emp;
          }
      }
      
      
      @RestController
      public class EmployeeController {
      
          @Autowired
          private EmployeeService employeeService;
      
          @GetMapping("/emp/{id}")
          public Employee getEmployee(@PathVariable("id")Integer id) {
              Employee emp = employeeService.getEmp(id);
              return emp;
          }
      }
    • 缓存中的SpEL用法
    • @Cacheable注解
    1. 将方法的结果进行缓存, 以后再要相同的数据, 就不用从数据库查了.
    2. CacheManager: 管理多个Cache组件, 对缓存的真正CRUD操作在Cache组件中, 每个缓存组件都有一个唯一的名字.
    3. 几个属性
      • cacheNames/value: 指定缓存的名字, 将方法的返回结果放在哪个缓存中, 是数组的方式.
      • key/keyGenerator
        • key: 缓存数据使用的key. 可以用它来指定. 默认是使用方法参数的值.
        • keyGenerator: key的生成器: 可以自己指定key的生成器的组件id.
        • 两者二选一.
      • condition: 指定符合条件的情况下, 才缓存.
      • cacheManager: 指定缓存管理器, 或cacheResolver指定缓存解析器.
      • unless: 否定缓存, 当unless指定的条件为true, 方法的返回值就不会被缓存. 可以获取到结果进行排查.
      • sync: 是否使用异步模式, 默认为false, 若使用, 则unless属性不支持.
    4. 举例(service方法中)
      • 使用key
        //condition = "#a0<2": 第一个参数的值<2时才进行缓存.
            //unless = "#a0==2" 如果第一个参数的值为2, 其结果不缓存
            @Cacheable(cacheNames = {"emp"}, key = "#root.methodName + '[' + #id + ']'",
                    condition = "#a0<2 and #root.methodName eq 'getEmp'", unless = "#a0==2")
            public Employee getEmp(Integer id) {
                System.out.println("查询" + id + "号员工");
                Employee emp = employeeMapper.getEmpById(id);
                return emp;
            }
      • 使用keyGenerator
            @Cacheable(cacheNames = {"emp"}, keyGenerator = "myKeyGenerator")
            public Employee getEmp(Integer id) {
                System.out.println("查询" + id + "号员工");
                Employee emp = employeeMapper.getEmpById(id);
                return emp;
            }
        • 需要自己手写KeyGenerator的实现加入容器中
          @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
          /**
           * @CachePut: 既调用方法, 又更新缓存数据
           * 1. 修改了数据库的某个数据, 同时更新缓存
           * 2. 运行时机
           *      1. 先调用目标方法
           *      2. 再把目标方法的结果缓存起来
           * 3.测试步骤
           *      1. 查询1号员工, 查到的结果会放到缓存中
           *          key: 1; value: 张三/1
           *      2. 以后查询还是之前的结果
           *      3. 更新1号员工 [lastName=zhangsan; gender=0]
           *          将方法的返回值也放进缓存了, 但它的key是传入的employee对象, 而值是返回的employee对象
           *      4. 查询1号员工
           *          是更新前的员工, 为什么是没更新前的? [1号员工没在缓存中更新]
           *      5. 所以应手动设置key
           *          key="#employee.id"; 或 key="#result.id"
           */
          @CachePut(cacheNames = {"emp"})
          public Employee updateEmp(Employee employee) {
              System.out.println("员工更新: " + employee);
              employeeMapper.updateEmp(employee);
              return employee;
          }
      
      只需要在@CachePut中添加属性key, 设置相应的值即可. (查询的key和更新的key必须一致)
    • @CacheEvict
          /**
           * @CacheEvict: 清除缓存
           *  1. 属性
           *      key: 指定要清楚的数据
           *      allEntries: 是否删除所有缓存. 默认为false. (只清空value/cacheNames中设置的缓存.)
           *      beforeInvocation: 缓存的清除是否在方法之前执行, 默认是false.
           *          举个例子: 如果当前方法出错, 若设false, 则不会清空缓存, 若true, 则会.
           */
          @CacheEvict(value = "emp", allEntries = true, beforeInvocation = true)
          public void deleteEmp(Integer id) {
              System.out.println("删除员工" + id);
              employeeMapper.deleteEmpById(id);
          }
    • @Caching&CacheConfig
      • @Caching
            /**
             * @Caching指定多种缓存规则
             */
            @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) {
                System.out.println("根据名字查询" + lastName);
                Employee emp = employeeMapper.getEmpByLastName(lastName);
                return emp;
            }
      • @CacheConfig
        /**
         * 在类上加@CachConfig, 指定cacheNames, 则该类所有的缓存名均为此值.
         */
        @CacheConfig(cacheNames = "emp")
        @Service
        public class EmployeeService {
          
            //...
        }

     整合Redis

    1.  默认给容器中注册的CacheManager是: ConcurrentMapCacheManager
      • 可以获取和创建ConcurrentMapCache类型的缓存组件, 其作用是将数据缓存在ConcurrentMap中.
    2. 而在开发中, 常用缓存中间件, 如Redis, EhCache等.
    • 整合Redis作为缓存
    1. 先安装Redis, 使用Docker方法.
      拉取Redis
      docker pull redis:latest

      运行Redis docker run
      -d -p 6379:6379 --name myredis docker.io/redis
    2. 引入Redis的starter
              <dependency>
                  <groupId>org.springframework.boot</groupId>
                  <artifactId>spring-boot-starter-data-redis</artifactId>
              </dependency>
    3. 配置redis
      • application.properties中配置
        spring.redis.host=192.168.13.129
      • 测试字符串
            //操作k-v都是String的
            @Autowired
            private StringRedisTemplate stringRedisTemplate;
        
            /**
             * Redis五大数据类型
             *  String: stringRedisTemplate.opsForValue() 操作字符串
             *  List:  stringRedisTemplate.opsForList() 操作列表
             *  Set: stringRedisTemplate.opsForSet() 操作集合
             *  Hash: stringRedisTemplate.opsForHash() 操作哈希
             *  ZSet: stringRedisTemplate.opsForZSet() 操作有序集合
             *
             *  而redisTemplate只是泛型和stringRedisTemplate不同
             */
            @Test
            public void test01() {
        
                ValueOperations<String, String> stringOps = stringRedisTemplate.opsForValue();
                //给Redis中保存数据
                //stringOps.append("msg", "hello");
        
                //读取数据
                //String msg = stringOps.get("msg");
                //System.out.println(msg);
        
                //列表中添加数据
                ListOperations<String, String> listOps = stringRedisTemplate.opsForList();
                listOps.leftPush("mylist", "1");
                listOps.leftPush("mylist", "2");
            }
      • 测试对象
            //k-v都是Object的
            @Autowired
            private RedisTemplate redisTemplate;
        
            //测试保存对象
            @Test
            public void test02() {
                //这里Employee必须序列化
                //默认如果保存对象, 使用jdk序列化机制, 序列化后的数据保存到redis中
                Employee emp = employeeMapper.getEmpById(1);
                redisTemplate.opsForValue().set("emp-01", emp);
        }
        • 但此时保存到Redis中的是序列化数据, 很难看.
      • 将对象以json形式保存
        • 配置自定义序列化规则
          @Configuration
          public class MyRedisConfig {
          
              @Bean
              public RedisTemplate<Object, Employee> redisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
                  RedisTemplate<Object, Employee> template = new RedisTemplate<>();
                  template.setConnectionFactory(redisConnectionFactory);
                  Jackson2JsonRedisSerializer<Employee> serializer = new Jackson2JsonRedisSerializer<Employee>(Employee.class);
                  template.setDefaultSerializer(serializer);
                  return template;
              }
          }
        • 使用
              @Autowired
              private RedisTemplate<Object, Employee> empTemplate;
          
              //测试保存对象
              @Test
              public void test02() {
                  Employee emp = employeeMapper.getEmpById(1);
          
                  //1.将数据以json的方式保存
                  //redisTemplate有默认的序列化规则
                  //改变默认的序列化规则
                  empTemplate.opsForValue().set("emp-02", emp);
              }
    4. 测试缓存
      • 原理: CacheManager -> Cache缓存组件 -> 缓存组件给缓存中CRUD数据.
        • 引入redis的starter后, 容器中保存的是RedisCacheManager.
        • RedisCacheManager帮我们创建RedisCache来作为缓存组件. RedisCache通过Redis缓存数据.
        • 默认保存数据k-v, 都是Object, 利用序列化保存
      • 如何保存为json?
        • 默认创建的RedisCacheManager操作Redis时使用的RedisTemplate<Object, Object>
        • 而默认使用jdk序列化机制.
      • 自定义CacheManager
            @Bean
            public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
                //初始化一个RedisCacheWriter
                RedisCacheWriter redisCacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory);
                //设置CacheManager的值序列化方式为json序列化
                GenericJackson2JsonRedisSerializer jsonRedisSerializer = new GenericJackson2JsonRedisSerializer();
                RedisSerializationContext.SerializationPair<Object> pair = RedisSerializationContext.SerializationPair.fromSerializer(jsonRedisSerializer);
                RedisCacheConfiguration redisCacheConfig = RedisCacheConfiguration.defaultCacheConfig().serializeValuesWith(pair);
        
                //设置默认超时过期时间30秒
                redisCacheConfig.entryTtl(Duration.ofSeconds(30));
        
                //初始化RedisCacheManager
                return new RedisCacheManager(redisCacheWriter, redisCacheConfig);
            }
    5. 之后, 上述测试缓存的例子如localhost:8080/emp/1 会将结果以json的形式保存在Redis中了.
  • 相关阅读:
    boost::asio::error的用法浅析
    boost::asio::buffer
    sqlserver2008安装图解
    WCF 聊天室程序代码详细讲解教程
    C#中使用Property Grid(属性面板)控件
    TXT>Access 使用DAO数据源!(VB Code)
    读取INI文件 VbCode
    Pet Shop 4
    模式行为型
    C#编程规范
  • 原文地址:https://www.cnblogs.com/binwenhome/p/13126362.html
Copyright © 2020-2023  润新知