• 5、Spring Boot缓存


    1.JSR107

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

      CachingProvider:定义了创建、配置、获取、管理和控制多个CacheManager。一个应用可以在运行期访问多个CachingProvider。

      CacheManager:定义了创建、配置、获取、管理和控制多个唯一命名的Cache,这些Cache存在于CacheManager的上下文中。一个CacheManager仅被一个CachingProvider所拥有。

      Cache:是一个类似Map的数据结构并临时存储以Key为索引的值。一个Cache仅被一个CacheManager所拥有。

      Entry:是一个存储在Cache中的key-value对。

      Expiry:每一个存储在Cache中的条目有一个定义的有效期。一旦超过这个时间,条目为过期的状态。一旦过期,条目将不可访问、更新和删除。缓存有效期可以通过ExpiryPolicy设置。

     

    2.Spring缓存抽象

      Spring从3.1开始定义了org.springframework.cache.Cache

    org.springframework.cache.CacheManager接口来统一不同的缓存技术;

    并支持使用JCache(JSR-107)注解简化我们开发;

     

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

      Cache接口下Spring提供了各种xxxCache的实现;如RedisCache、EhCacheCache、ConcurrentMapCache等;

      每次调用需要缓存功能的方法时,Spring会检查检查指定参数的指定的目标方法是否已经被调用过;如果有就直接从缓存中获取方法调用后的结果,如果没有就调用方法并缓存结果后返回给用户。下次调用直接从缓存中获取。

      使用Spring缓存抽象时我们需要关注以下两点

    确定方法需要被缓存以及他们的缓存策略

    从缓存中读取之前缓存存储的数据

     

    (1).搭建环境

    1).导入数据库文件

    com.hosystem.cache.bean.Department

    package com.hosystem.cache.bean;

     

    import java.io.Serializable;

     

    public class Department implements Serializable {

       

       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 + "]";

       }

    }

    com.hosystem.cache.bean.Employee

    package com.hosystem.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 + "]";

       }

    }

    (2).注解使用

     

    com.hosystem.cache.service.EmployeeService

    package com.hosystem.cache.service;

     

    import com.hosystem.cache.bean.Employee;

    import com.hosystem.cache.mapper.EmployeeMapper;

    import org.springframework.beans.factory.annotation.Autowired;

    import org.springframework.cache.annotation.*;

    import org.springframework.stereotype.Service;

     

    @Service

    @CacheConfig(cacheNames = "emp")

    public class EmployeeService {

     

        @Autowired

        EmployeeMapper employeeMapper;

     

        /**

         *  将方法的运行结果进行缓存;下次在调用相同的数据,直接从缓存中获取,不再调用方法;

         *

         *  CacheManager管理多个cache组件,对缓存的真正CRUD操作在Cache组件中,每一个缓存组件有自己唯一一个名字

         *  工作原理:

         *      1.自动配置类:CacheAutoConfiguration

         *      2.缓存配置类:GenericCacheConfigurationJCacheCacheConfigurationEhCacheCacheConfigurationHazelcastCacheConfigurationInfinispanCacheConfigurationCouchbaseCacheConfigurationRedisCacheConfigurationCaffeineCacheConfigurationSimpleCacheConfigurationNoOpCacheConfiguration

         *          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.SimpleCacheConfiguration

         *          org.springframework.boot.autoconfigure.cache.NoOpCacheConfiguration

         *      3.配置类默认生效:SimpleCacheConfiguration

         *      4.给容器中注册了一个cacheManager:ConcurrentMapCacheManager

         *      5.可以获取、创建ConcurrentMapCache类型的缓存组件;它的作用是将数据保存在ConcurrentMap

         *

         *  @Cacheable 运行流程:

         *      1.方法运行之前,先去查找Cache(缓存组件),按照cacheNames指定的名字获取;(CacheManager先获取相应的缓存)第一次获取缓存如果没有该缓存则会自动创建

         *      2.Cache中查找缓存的内容,使用一个key,默认为方法的参数;

         *        (1).key是按照某种策略生成的;默认是使用keyGenerator生成的,默认使用SimpleKeyGenerator生成key

         *        (2).默认使用SimpleKeyGenerator生成key默认策略:若无参数,key = new SimpleKey();|如果有单个参数,key=参数值;|如果有多个参数,key = new SimpleKey(params);

         *      3.若为查找到缓存就调用方法

         *      4.将方法返回的结果,放入缓存中

         *      @Cacheable 标注的方法执行之前先来检查缓存中有没有这个数据,默认按照参数的值作为key查找缓存,如果缓存不存在,则运行方法并将结果放入缓存

         *  核心:

         *      1.使用CacheManager[ConcurrentMapCacheManager]按照名字获取cache[ConcurrentHashMapCache]组件

         *      2.key使用keyGenerator生成,默认是SimpleKeyGenerator

         *

         *  属性:valuecacheNameskeykeyGeneratorcacheManagercacheResolverconditionunlesssync

         *      value/cacheNames:指定缓存组件的名字

         *      key:缓存数据使用的key,可以用它指定参数。默认是使用方法参数的值

         *          SpEL: #id:参数id的值  #a0 #p0 #root.args[0]

         *      keyGenerator:key生成器;可以指定key生成器组件id;

         *          注:keyGeneratorkey只能二选一

         *      cacheManager:指定缓存管理器

         *      cacheResolver:指定获取解析器

         *      condition:指定符合条件情况下缓存

         *      unless:否定缓存;unless指定条件为true,方法返回值不会被缓存;可以获取结果进行判断

         *      sync:是否使用异步模式

         */

        //cacheNames = "emp":

        //condition = "#id>0":只有当id>0的时候再进行缓存

        //condition = "#a0>1":只有当第一个参数>1时候才进行缓存

        //unless = "#result==null":当返回结果为空时不进行缓存

        //unless = "#a0==2":如果第一个参数的结果为2,则结果不缓存

        //key = "#root.methodName+'['+#id+']'"

        //keyGenerator = "myKeyGenerator":自定义key

        @Cacheable(cacheNames = "emp"/*,condition = "#a0>1",unless = "#a0==2"*/)

        public Employee getEmp(Integer id){

            System.out.println("查询"+id+"号员工");

            Employee emp = employeeMapper.getEmpById(id);

            return emp;

        }

     

        /**

         *  @CachePut:调用方法同时更新缓存数据

         *  修改数据库某个数据 同时更新缓存

         *

         *  运行时间:

         *    1.先调用方法

         *    2.将方法的结果缓存起来

         *

         *  测试步骤:

         *    1.查询1号员工;查询到的结果会放在缓存中 key:1 value:lastName:张三

         *    2.查询结果照旧

         *    3.更新1号员工信息[emp?id=1&lastName=zhangs&gender=0];将方法的返回值也放进缓存中 key:传入的employee对象 值:返回的employee对象

         *    4.查询1号员工;查询结果为未更新前的数据[1号员工的信息没有在缓存中更新]

         *      key = "#employee.id":使用传入参数的员工id进行更新

         *      key = "#result.id":使用返回后的id

         *      :@Cacheablekey是不能够使用#result

         */

        @CachePut(value = "emp",key = "#result.id")

        public Employee updateEmp(Employee employee){

            System.out.println("update" + employee);

            employeeMapper.updateEmp(employee);

            return employee;

        }

     

        /**

         *  @CacheEvict:缓存清除

         */

        //key = "#id":指定key删除缓存

        //allEntries = true:删除缓存中所有数据 默认参数为false

        //beforeInvocation=false:缓存的清除是否在方法之前执行 默认是false,即清除缓存操作在方法执行之后执行 如果方法出现异常缓存就不会清除

        //beforeInvocation = true:清除缓存操作在方法执行之前执行 如果方法出现异常缓存也会清除

        @CacheEvict(value = "emp"/*,key = "#id"*//*,allEntries = true*/,beforeInvocation = true)

        public void deleteEmp(Integer id){

            System.out.println("delete"+id);

    //        employeeMapper.deleteEmpById(id);

            int i = 10/0;

        }

     

        @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);

        }

    }

    com.hosystem.cache.service.DeptService

    package com.hosystem.cache.service;

     

    import com.hosystem.cache.bean.Department;

    import com.hosystem.cache.mapper.DepartmentMapper;

    import org.springframework.beans.factory.annotation.Autowired;

    import org.springframework.cache.annotation.Cacheable;

    import org.springframework.stereotype.Service;

     

    @Service

    public class DeptService {

     

        @Autowired

        DepartmentMapper departmentMapper;

     

        /**

         *  缓存的数据能存入redis

         *  第二次从缓存中查询就不能恢复反序列化

         *  存的是deptjson;cachemanager默认使用RedisTemplate<Object, Employee>操作Redis

         *

         * @param id

         * @return

         */

        @Cacheable(cacheNames = "dept")

        public Department getDeptById(Integer id){

            System.out.println("查询部门"+id);

            Department mapper = departmentMapper.getDeptById(id);

            return mapper;

        }

    }

     

     

    com.hosystem.cache.controller.EmployeeController

    package com.hosystem.cache.controller;

     

     

    import com.hosystem.cache.bean.Employee;

    import com.hosystem.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;

        }

     

        @GetMapping("/emp")

        public Employee update(Employee employee){

            Employee emp = employeeService.updateEmp(employee);

            return emp;

        }

     

        @GetMapping("/delemp")

        public String deleteEmp(Integer id){

            employeeService.deleteEmp(id);

            return "success";

        }

     

        @GetMapping("/emp/lastname/{lastName}")

        public Employee getEmpByLastName(@PathVariable("lastName") String lastName){

            return employeeService.getEmpByLastName(lastName);

        }

    }

    com.hosystem.cache.Springboot01CacheApplication

    package com.hosystem.cache;

     

    import org.mybatis.spring.annotation.MapperScan;

    import org.springframework.boot.SpringApplication;

    import org.springframework.boot.autoconfigure.SpringBootApplication;

    import org.springframework.cache.annotation.EnableCaching;

     

    /**

     *  搭建环境

     *  1. 导入数据库文件  创建departmentemployee

     *  2. 创建javaBean封装数据

     *  3. 整合mybatis操作数据库

     *     1).配置数据源

     *     2).使用注解版mybatis

     *        (1).@MapperScan指定需要扫描的mapper接口所在的包

     *

     *  使用缓存

     *  1. 开启注解缓存 @EnableCaching

     *  2. 标注缓存注解

     *     @Cacheable:针对方法配置,能够根据方法的请求参数对其结果进行缓存

     *     @CacheEvict:清空缓存

     *     @CachePut:保证方法被调用,又希望结果被缓存

     *

     * 默认使用的ConcurrentMapCacheManager--->ConcurrentMapCache 将数据保存在ConcurrentMap<Object,Object>

     * 开发中常使用其它缓存中间件:Redismemcahced

     *

     * 整合Redis作为缓存

     * Redis 是一个开源(BSD许可)的,内存中的数据结构存储系统,它可以用作数据库、缓存和消息中间件。

     * 1.安装docker:https://www.cnblogs.com/HOsystem/p/13789551.html

     * 2.安装Redis(通过docker):https://www.cnblogs.com/HOsystem/p/13850049.html

     * 3.配置Redis

     * 4.测试缓存

     *    原理:CacheManager===Cache 缓存组件来实际给缓存中存储数据

     *    (1).引入redisstarter,容器中保存的是org.springframework.data.redis.cache.RedisCacheManager

     *    (2).org.springframework.data.redis.cache.RedisCacheManager帮忙创建org.springframework.data.redis.cache.RedisCache作为缓存组件;

     *       org.springframework.data.redis.cache.RedisCache通过操作redis缓存数据的

     *    (3).默认保存数据k-v都是object 利用序列化保存;如何保存为json;

     *       1).引入了redisstartercachemanager变为RedisCacheManage

     *       2).默认创建RedisCacheManage操作redis的时候使用的是RedisTemplate<Object,Object>

     *       3).RedisTemplate<Object,Object>默认使用jdk的序列化机制

     *    (4).自定义CacheManager

     *

     */

    @MapperScan("com.hosystem.cache.mapper")

    @EnableCaching

    @SpringBootApplication

    public class Springboot01CacheApplication {

     

       public static void main(String[] args) {

          SpringApplication.run(Springboot01CacheApplication.class, args);

       }

     

    }

     

    (3).docker

    1).安装docker

    https://www.cnblogs.com/HOsystem/p/13789551.html

    2).安装redis

    https://www.cnblogs.com/HOsystem/p/13850049.html

    3).测试Redis

    com.hosystem.cache.Springboot01CacheApplicationTests

    package com.hosystem.cache;

     

    import com.hosystem.cache.bean.Employee;

    import com.hosystem.cache.mapper.EmployeeMapper;

    import com.sun.xml.internal.ws.api.ha.StickyFeature;

    import org.junit.jupiter.api.Test;

    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.data.redis.serializer.RedisSerializer;

    import org.springframework.lang.Nullable;

     

    @SpringBootTest

    class Springboot01CacheApplicationTests {

     

       @Autowired

       EmployeeMapper employeeMapper;

     

       @Autowired

       StringRedisTemplate stringRedisTemplate;  //操作k-v是字符串形式

     

       @Autowired

       RedisTemplate redisTemplate;   //k-v都是对象

     

       @Autowired

       RedisTemplate<Object,Employee> empRedisTemplate;

     

       /**

        *     Redis常见五大数据类型

        *        String(字符串)List(列表)Hash(散列)Set(集合)ZSet(有序集合)

        *        stringRedisTemplate.opsForValue():String(字符串)

        *        stringRedisTemplate.opsForList():List(列表)

        *        stringRedisTemplate.opsForHash():Hash(散列)

        *        stringRedisTemplate.opsForSet():Set(集合)

        *        stringRedisTemplate.opsForZSet():ZSet(有序集合)

        */

       @Test

       public void test01(){

          //redis保存数据

    //    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");

       }

     

       //测试保存对象

       @Test

       public void test02(){

          Employee empById = employeeMapper.getEmpById(1);

          //默认保存对象,使用jdk序列化机制,序列化后的数据保存到redis

    //    redisTemplate.opsForValue().set("emp-01",empById);

          //1.将数据以json的方式保存

             //(1).将对象转为json

             //(2).redisTemplate默认序列化规则;自定义默认序列化规则

                //    private RedisSerializer keySerializer = null;

                //    private RedisSerializer valueSerializer = null;

                //    private RedisSerializer hashKeySerializer = null;

                //    private RedisSerializer hashValueSerializer = null;

                //    private RedisSerializer<String> stringSerializer = RedisSerializer.string();

          empRedisTemplate.opsForValue().set("emp-01",empById);

       }

     

     

     

       @Test

       public void contextLoads() {

          Employee empById = employeeMapper.getEmpById(1);

          System.out.println(empById);

       }

     

    }

    docker启动redis失败

    Error response from daemon: Cannot start container 53fe1fcb2e05214c6f853ef2fe9f65539e69fdc7d6a454bfb073c10c2fba82dd: iptables failed: iptables -t nat -A DOCKER -p tcp -d 0/0 --dport 6379 -j DNAT --to-destination 172.17.0.3:6379 ! -i docker0: iptables: No chain/target/match by that name.

    我们首先对iptables进行防火墙规则配置 允许6379端口可以访问

    docker启动redis失败

    [root@pluto sysconfig]# docker run -d -p 6379:6379 --name myredis redis

    Error response from daemon: Conflict. The name "myredis" is already in use by container 53fe1fcb2e05. You have to delete (or rename) that container to be able to reuse that name.

    [root@pluto sysconfig]# docker ps -a

    CONTAINER ID        IMAGE                       COMMAND                CREATED             STATUS                      PORTS               NAMES

    53fe1fcb2e05        redis                       "docker-entrypoint.s   2 minutes ago                                                       myredis       

    [root@pluto sysconfig]# docker rm 53fe1fcb2e05

    (4).自定义CacheManager

    *   整合Redis作为缓存

    *  Redis 是一个开源(BSD许可)的,内存中的数据结构存储系统,它可以用作数据库、缓存和消息中间件。

    *  1.安装docker:https://www.cnblogs.com/HOsystem/p/13789551.html

    *  2.安装Redis(通过docker):https://www.cnblogs.com/HOsystem/p/13850049.html

    *  3.配置Redis

    *  4.测试缓存

    *     原理:CacheManager===Cache 缓存组件来实际给缓存中存储数据

    *     (1).引入redisstarter,容器中保存的是org.springframework.data.redis.cache.RedisCacheManager

    *     (2).org.springframework.data.redis.cache.RedisCacheManager帮忙创建org.springframework.data.redis.cache.RedisCache作为缓存组件;

    *        org.springframework.data.redis.cache.RedisCache通过操作redis缓存数据的

    *     (3).默认保存数据k-v都是object 利用序列化保存;如何保存为json;

    *        1).引入了redisstartercachemanager变为RedisCacheManage

    *        2).默认创建RedisCacheManage操作redis的时候使用的是RedisTemplate<Object,Object>

    *        3).RedisTemplate<Object,Object>默认使用jdk的序列化机制

    *     (4).自定义CacheManager

    @Autowired

    StringRedisTemplate stringRedisTemplate;

     

     

    @Autowired

    RedisTemplate redisTemplate;

     

    pom.xml

    <?xml version="1.0" encoding="UTF-8"?>

    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

       xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">

       <modelVersion>4.0.0</modelVersion>

       <parent>

          <groupId>org.springframework.boot</groupId>

          <artifactId>spring-boot-starter-parent</artifactId>

          <version>2.3.4.RELEASE</version>

          <relativePath <!-- lookup parent from repository -->

     

  • 相关阅读:
    Number Sequence
    不容易系列之(3)—— LELE的RPG难题
    又见回文
    统计元音
    数列
    regular expression
    野兽男孩
    GameStd
    boost and qt compile.
    kde4 + compiz只有两个桌面的问题
  • 原文地址:https://www.cnblogs.com/HOsystem/p/14017938.html
Copyright © 2020-2023  润新知