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() + "]";
                }
            };
        }
    }
    
    
  • 相关阅读:
    docker如何将运行中的容器保存为docker镜像?
    java8流的地址
    maven命令package、install、deploy
    windows下设置redis开机自启动
    mysql的安装参考
    service mysql启动失败unit not found
    JAVA中Wait()与Notity()、同步队列与等待队列
    Java8函数式编程
    Groovy ConfigSlurper()读取配置文件简易示例
    SoapUI官方文档
  • 原文地址:https://www.cnblogs.com/nieaojie625/p/13765691.html
Copyright © 2011-2022 走看看