zoukankan      html  css  js  c++  java
  • SpringBoot缓存——基本环境搭建

    SpringBoot缓存——基本环境搭建

    首先选择场景启动器:

    image-20201003104040164

    其依赖如下:

    <dependencies>
        <!--cache-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-cache</artifactId>
        </dependency>
        
        <!--web-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        
        <!--mybatis-->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.1.3</version>
        </dependency>
    
        <!--mysql-connector-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        
        <!--test-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>
    

    项目结构:

    image-20201003211622895

    一、使用到到的技术

    Web+Mybatis+Mysql+Spring Cache

    二、创建数据库和数据库表

    1、新建数据库spring_cache

    2、新建department表

    DROP TABLE IF EXISTS `department`;
    CREATE TABLE `department` (
      `id` int(11) NOT NULL AUTO_INCREMENT,
      `departmentName` varchar(255) DEFAULT NULL,
      PRIMARY KEY (`id`)
    ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
    

    3、新建employee表

    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 AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
    

    三、创建实体类对象

    1、Employee

    public class Employee {
        private Integer id;
        private String lastName;
        private Integer gender;
        private String email;
        private Integer dId;
        //getter和setter方法
    }
    

    2、Department

    public class Department {
        private Integer id;
        private String departmentName;
    	//getter和setter方法
    }
    

    四、整合mybatis

    1、application.yml配置文件

    #配置数据源
    spring:
      datasource:
        url: jdbc:mysql://localhost:3306/spring_cache?serverTimezone=UTC
        username: root
        password: root
        driver-class-name: com.mysql.cj.jdbc.Driver
    #开启驼峰命名匹配规则
    mybatis:
      configuration:
        map-underscore-to-camel-case: true
    
    #开启日志功能(包级别),需要注意格式
    logging:
      level:
        com.example.mapper : debug
    
    #开启debgu调试
    #debug: true
    

    2、在启动类上加上如下注解

    使用注解版的mybatis,在启动类上加上如下注解:

    • @MapperScan(value = "com.example.mapper"):使用@MapperScan扫描mapper接口所在的包。

      说明:也可以在每个mapper文件上使用@Mapper注解

    • @EnableCaching:开启基于注解的缓存功能。

    @MapperScan(value = "com.example.mapper")
    @SpringBootApplication
    @EnableCaching
    public class SpringBoot01CacheApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(SpringBoot01CacheApplication.class, args);
        }
    }
    

    3、EmployeeMapper

    这里使用注解版的mybatisc操作数据库。

    因为在启动类使用了@MapperScan扫描mapper接口所在的包,所有这里接口上不用写@mapper注解了。

    package com.example.mapper;
    
    import com.example.bean.Employee;
    import org.apache.ibatis.annotations.Delete;
    import org.apache.ibatis.annotations.Insert;
    import org.apache.ibatis.annotations.Select;
    import org.apache.ibatis.annotations.Update;
    
    public interface EmployeeMapper {
    
        //根据id查找emp
        @Select("select * from employee where id=#{id}")
        Employee getEmpById(Integer id);
    
        //更新emp
        @Update("update employee set lastName=#{lastName},email=#{email},gender=#{gender},d_id=#{dId} where id=#{id}")
        void updateEmp(Employee emp);
    
        //删除emp
        @Delete("delete from employee where id=#{id}")
        void deleteEmpById(Integer id);
    
        //添加emp
        @Insert("insert into employee values(default,#{lastName},#{email},#{gender},#{dId})")
        void insertEmp(Employee employee);
    
        //根据lastName查询emp
        @Select("select * from employee where lastName=#{lastName}")
        Employee getEmpByLastName(String lastName);
    }
    
    

    4、EmployeeService

    几个常用的注解:

    • @Cacheable:常用于查询方法上,将使用该注解的方法的运行结果缓存,以后再请求相同的数据时,直接从缓存中获取,不用调用方法。默认缓存的key值是该方法的参数值。

      如: 根据id查找:public Employee getEmpById(Integer id) {}

      该方法参数id的值就是在缓存中的key的值。(不是key对应的value)

    • @CachePut :常用于更新方法上,比如修改了数据库的某些数据,那么缓存中的数据也需要更新(如果缓存中没有该数据,那么则添加该缓存),该注解可实现修改数据并更新缓存。

      注意:可能会遇到以下情况:

      @Cacheable(cacheNames = "emp",key = "#id")

      public Employee getEmpById(Integer id) {}

      @CachePut(cacheNames = "emp",key = "#emp.id") //将方法返回结果的id值作为key

      public Employee updateEmp(Employee emp){return emp}

      执行步骤:

      1. 首先根据id查询到Employee ,并将其放入缓存,缓存的参数id即为在缓存中的key。

      2. 更新操作,如果更新的Employee 和上面执行查询的Employee是同一条数据,那么缓存中的数据该怎么更新?

      需要保证查询和更新操作之后放入缓存中数据的key是同一个。

      1. 以上查询方法的key默认是id和更新方法的key默认是emp,如何保证两者的key是一样的?

      手动指定更新操作的key与查询操作一致。

    • @CacheEvict: 缓存清除。

      参数:

      • cacheNames/value:指定要删除哪个缓存的数据

        • key:指定要清除的数据

        • allEntries:是否删除该cache中所有的数据,默认是false,当设置为true时,就不需要指定key了。

        • beforeInvocation:是否在该注解标注的方法执行之前清除缓存,默认是false。

        • 方法调用之前和调用之后删除有什么区别?

        如果方法没有出现异常,那么之前和之后清除缓存没有什么区别

        但是如果方法出现了异常,那么就可以在方法执行之前清除缓存,而方法调用之后清除缓存则不能实现。

    • @CacheConfig:可以抽取缓存的公共配置,通常使用在类上,抽取方法缓存的公共部分。

    以上注解的常用属性:

    • cacheNames/value:指定缓存组件的名称,将方法的返回值结果放到哪个缓存组件中,是数组的方式,可以指定多个缓存。
    • key:缓存数据所使用的key,默认是方法参数的值,可以使用SPEL表达式指定,如#id、root.args[0]表示的都是方法的参数。
    • keyGenerator:key的生成器,可以自己声明key生成器的组件id,与key二选一。
    • cacheManager:指定缓存管理器。
    • cacheResolver:指定获取解析器,cacheManager与cacheResolver二选一使用。
    • condition:指定符合条件的情况下使用缓存,如:condition="#id>0",参数id的值大于0的情况下加入缓存。
    • unless:除非,否定缓存,当unless条件成立,为true时,不会加入缓存。与condition相反。可以获取到结果进行判断,如unless="#result==null"的话就不缓存。
    package com.example.service;
    
    import com.example.bean.Employee;
    import com.example.mapper.EmployeeMapper;
    import org.springframework.cache.annotation.*;
    import org.springframework.stereotype.Service;
    
    import javax.annotation.Resource;
    
    @Service
    // @CacheConfig(cacheNames = "emp") //抽取缓存的公共配置
    public class EmployeeService {
        @Resource
        private EmployeeMapper employeeMapper;
    
        /**
         * @Cacheable 该注解将方法的运行结果缓存,以后再要相同的数据,从缓存中获取,不用调用方法。
         *
         * CacheManager管理多个Cache组件,对缓存的CRUD操作是在Cache组件中,每一个缓存组件都有一个唯一的名称。
         * 属性:
         *  cacheNames/value:指定缓存组件的名称,将方法的返回值结果放到哪个缓存组件中,是数组的方式,可以指定多个缓存。
         *  key:缓存数据使用的key,默认是方法参数的值,可以使用SPEL表达式指定,如#id、root.args[0]表示的都是方法的参数。
         *      例如:key="#root.MethodName+'['+#id+']'",得到的key就是getEmp[id]的形式。
         *  keyGenerator:key的生成器,可以自己声明key生成器的组件id,与key二选一。
         *      自定义步骤:在配置类中注册一个KeyGenerator类型的组件,然后自定义生成的内容返回。如:keyGenerator = "myKeyGenerator"
         *  cacheManager:指定缓存管理器,
         *  cacheResolver:指定获取解析器,cacheManager与cacheResolver二选一使用。
         *  condition:指定符合条件的情况下使用缓存,如:condition="#id>0",参数id的值大于0的情况下加入缓存。
         *             多个条件之间可以使用and连接,如:condition="#id>0 and #root.methodName eq 'aaa'"
         *  unless:除非,否定缓存,当unless条件成立,为true时,不会加入缓存。与condition相反。
         *      可以获取到结果进行判断,如unless="#result==null"的话就不缓存。
         *  sync:是否使用异步模式
         *
         * 原理:
         *  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.SimpleCacheConfiguration
         *      org.springframework.boot.autoconfigure.cache.NoOpCacheConfiguration
         *  3.哪个缓存配置默认生效,在主配置类中开启debug=true
         *    结果是:SimpleCacheConfiguration
         *    这个缓存配置类做了什么工作?有什么作用?
         *    给容器中注册了一个ConcurrentMapCacheManager缓存管理器。作用是可以获取和创建ConcurrentMapCache类型的
         *    缓存组件,从而将数据保存在ConcurrentMap中。
         *  4.发送查询请求:localhost:8080/getEmpById/2
         *    运行流程:
         *    @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.将目标方法返回的结果放入到缓存。
         *
         *    In short,@Cacheable标注的方法执行之前先检查缓存中有没有这个数据,默认是按照参数的值作为key去查询缓存,
         *    如果缓存中没有就执行目标方法并将返回结果放入到缓存,以后再来调用就可以使用缓存中的数据。
         *
         *    核心:
         *     1)使用CacheManager【ConcurrentMapCacheManager】按照cacheNames得到cache【ConcurrentMapCache】组件
         *     2)key是使用keyGenerator生成的,默认是SimpleKeyGenerator
         *
         * @param id
         * @return
         */
        @Cacheable(cacheNames = "emp",key = "#id")
        //根据id查找emp
        public Employee getEmpById(Integer id) {
            System.out.println("service查询了"+id+"号员工");
            return employeeMapper.getEmpById(id);
        }
    
        /**
         * @CachePut 既调用方法,又缓存数据。同步更新缓存。
         * 修改了数据库的某个数据,同时更新缓存。
         * 运行时机:
         *  1.先调用目标方法
         *  2.将目标方法的结果缓存起来
         * 测试步骤:
         *  1.查询1号员工,查询的结果会放入缓存中
         *      key:1   value:zhansgan
         *  2.再次查询还是之前的结果
         *  3.更新1号员工
         *      key:传入的employee对象   value:返回的employee对象
         *  4.查询1号员工?结果还是更新之前的结果。
         *     解决方法:
         *     key = "#emp.id"
         *     或key="#result.id"
         *     将key设置成与之前参数一样的值。
         *
         */
        @CachePut(cacheNames = "emp",key = "#emp.id")
        //更新emp
        public Employee updateEmp(Employee emp) {
            System.out.println("service更新了"+emp.getId()+"号员工");
            employeeMapper.updateEmp(emp);
            return emp;
        }
    
        /**
         * @CacheEvict 缓存清除
         *  参数:
         *      cacheNames/value:指定要删除哪个缓存的数据
         *      key:指定要清除的数据
         *      allEntries:是否删除该cache中所有的数据,默认是false,当设置为true时,就不需要指定key了。
         *      beforeInvocation:是否在该注解标注的方法执行之前清除缓存,默认是false。
         *          方法调用之前和调用之后删除有什么区别?
         *          如果方法没有出现异常,那么之前和之后清除缓存没有什么区别
         *          但是如果方法出现了异常,那么就可以在方法执行之前清除缓存,而方法调用之后清除缓存则不能实现。
         *
         *
         */
        // @CacheEvict(cacheNames = "emp",allEntries = true,beforeInvocation = true)
        // @CacheEvict(cacheNames = "emp",key = "#id")
        // @CacheEvict(cacheNames = "emp",allEntries = true)
        @CacheEvict(cacheNames = "emp",allEntries = true,beforeInvocation = true)
        //删除emp
        public void deleteEmpById(Integer id) {
            employeeMapper.deleteEmpById(id);
            //这里模拟了除数为0的异常
            // int i = 10 / 0;
        }
    
        //添加emp
        public void insertEmp(Employee employee) {
            employeeMapper.insertEmp(employee);
        }
    
        //定义复杂的缓存规则
        @Caching(
                cacheable = {
                        @Cacheable(cacheNames = "emp",key = "#lastName")
                },
                put = {
                        @CachePut(cacheNames = "emp",key = "#result.id"),
                        @CachePut(cacheNames = "emp",key = "#result.email")
                }
        )
        //根据lastName查询emp
        public Employee getEmpByLastName(String lastName) {
            System.out.println("service getEmpByLastName查询了lastName为:"+lastName+"的员工");
            return employeeMapper.getEmpByLastName(lastName);
        }
    }
    
    

    5、EmployeeController

    package com.example.controller;
    
    import com.example.bean.Employee;
    import com.example.service.EmployeeService;
    import org.springframework.cache.annotation.Cacheable;
    import org.springframework.web.bind.annotation.*;
    
    import javax.annotation.Resource;
    
    /**
     * @author: nie
     * @create: 2020-10-03 11:21
     * @description:
     **/
    @RestController
    public class EmployeeController {
        @Resource
        private EmployeeService employeeService;
    
    
        @GetMapping("/getEmpById/{id}")
        public Employee getEmpById(@PathVariable("id") Integer id) {
            return employeeService.getEmpById(id);
        }
    
        @PutMapping("/updateEmp")
        public String updateEmp(Employee employee) {
            employeeService.updateEmp(employee);
            return "success";
        }
    
        @DeleteMapping("/deleteEmpById/{id}")
        public String deleteEmpById(@PathVariable("id") Integer id) {
            employeeService.deleteEmpById(id);
            return "success";
        }
    
        @PostMapping("/insertEmp")
        public String insertEmp(Employee employee) {
            employeeService.insertEmp(employee);
            return "success";
        }
    
        //根据lastName查询emp
        @GetMapping("/getEmpByLastName/{lastName}")
        public Employee getEmpByLastName(@PathVariable("lastName") String lastName) {
            return employeeService.getEmpByLastName(lastName);
        }
    }
    
    

    6、自定义cache中key的生成策略

    package com.example.config;
    
    import org.springframework.cache.interceptor.KeyGenerator;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    
    import java.lang.reflect.Method;
    import java.util.Arrays;
    
    /**
     * @author: nie
     * @create: 2020-10-03 17:08
     * @description: 自定义cache中key的生成策略
     **/
    @Configuration  //标记这是一个配置类
    public class MyKeyGeneratorConfig {
    
        @Bean("myKeyGenerator")  //将该组件加入到容器中
        //注意导的包是:org.springframework.cache.interceptor.KeyGenerator
        public KeyGenerator keyGenerator() {
            //使用匿名内部类的方式
            return new KeyGenerator() {
                @Override
                public Object generate(Object o, Method method, Object... objects) {
                    System.out.println(o);
                    return method.getName() + "[" + Arrays.asList(objects).toString() + "]";
                }
            };
        }
    }
    
    
  • 相关阅读:
    进程与线程
    the art of seo(chapter seven)
    the art of seo(chapter six)
    the art of seo(chapter five)
    the art of seo(chapter four)
    the art of seo(chapter three)
    the art of seo(chapter two)
    the art of seo(chapter one)
    Sentinel Cluster流程分析
    Sentinel Core流程分析
  • 原文地址:https://www.cnblogs.com/nieaojie625/p/13765691.html
Copyright © 2011-2022 走看看