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

posted @ 2020-10-03 22:11  笑到不能自已  阅读(286)  评论(0编辑  收藏  举报