springboot之缓存

JSR107

1.核心

JAVA Caching定义了5哥核心接口,分别是CachingProvider,CacheManager,Cache,Entry和Expiry。

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

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

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

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

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

2.如何使用?

1.导入依赖

<dependency>
<groupId>javax.cache</groupId>
<artifactId>cache-api</artifactId>
</dependency>

 

 

JSR107缓存,在实际开发中很少直接使用它。

 

 

2.Spring缓存抽象

 

spring从3.1开始定义了org.springframework.cache.Cahce和org.springframework.cache.CacheManager接口来同意不同的缓存技术;并且支持使用JCache(JSR-107)注解来简化开发

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

  • Cache接口为spring提供了各种xxxCache的实现;如RedisCache,EhCacheCache,ConcurrentMapCache等;

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

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

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

 

1.几个重要的概念&缓存注解

Cache缓存接口,定义缓存操作。实现由:RedisCache、EhCacheCache、ConcurrentMapCache等。
CacheManager 缓存管理器,管理各种缓存(Cache)组件
@Cacheable 主要针对方法配置,能够根据方法的请求参数对其结果进行缓存。
@CacheEvict 清空缓存
@CachePut 保证方法被调用,有希望结果被缓存。
@EnableCaching 开启基于注解的缓存
keyGenerator 缓存数据时key生成的策略
serialize 缓存数据时value序列化策略

 

缓存SpEL规则:

名字位置描述实例
methodName root object 当前被调用的方法名 #root.methodName
method root object 当前被调用的方法 #root.method.name
target root object 当前被调用的目标对象 #root.target
targetClass root object 当前被调用的目标对象类 #root.targetClass
args root object 当前被调用的方法的参数列表 #root.args[0]
caches root object 当前方法使用的缓存列表(如@Cacheable(value={"cache1","cache2"})),则有两个cache #root.caches[0].name
argumentname evaluation context 方法参数的名字,可以直接#参数名,也可以使用#p0或者#a0的形式,0代表参数索引。 #iban、#a0、#p0
result evaluation context 方法执行后的返回值(晋档方法执行后的判断有效,如 “unless”,'cache put'表达式 'cache evict'的表达式beforelnvocation=false) #result
       

工程代码

环境:

jdk:1.8

idea:2019.3.1

springboot 2.2.6

mysql:5.7.29

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>
    <groupId>com.seegot</groupId>
    <artifactId>spring-boot-01-cache</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>spring-boot-01-cache</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <spring-boot.version>2.2.6.RELEASE</spring-boot.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-cache</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.1.1</version>
        </dependency>
        <!--引入druid-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.1.10</version>
        </dependency>
        <!--引入log4j日志管理-->
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.17</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <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>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>${spring-boot.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                    <encoding>UTF-8</encoding>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <version>2.2.6.RELEASE</version>
            </plugin>
        </plugins>
    </build>

</project>

application.yml

spring:
  datasource:
    # 配置监控服务器
    type: com.alibaba.druid.pool.DruidDataSource
    druid:
      #   druid 数据源其他配置 当添加了如下配置,我们需要在程序中增加configuration配置来将这些配置映射到系统默认配置参数上去。
      driver-class-name: com.mysql.cj.jdbc.Driver
      username: root
      password: root
      url: jdbc:mysql://192.168.100.158:3306/spring_cache?characterEncoding=utf-8&autoReconnect=true&zeroDateTimeBehavior=convertToNull&useSSL=false
      initialSize: 5
      minIdle: 5
      maxActive: 20
      maxWait: 60000
      timeBetweenEvictionRunsMillis: 60000
      minEvictableIdleTimeMillis: 300000
      validationQuery: SELECT 1 FROM DUAL
      testWhileIdle: true
      testOnBorrow: false
      testOnReturn: false
      poolPreparedStatements: true
      #   配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙
      filter:
        config:
          enabled: true
        stat:
          enabled: true
          db-type: mysql
        wall:
          enabled: true
          db-type: mysql
      maxPoolPreparedStatementPerConnectionSize: 20
      useGlobalDataSourceStat: true
      connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500
      stat-view-servlet:
        login-username: admin
        login-password: admin
        reset-enable: false
        url-pattern: /druid/*
      web-stat-filter:
        url-pattern: /*
        exclusions: "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*"
mybatis:
  configuration:
    # 开启驼峰命名法
    map-underscore-to-camel-case: true
logging:
  level:
    # 设置包下日志输出级别
   com.seegot.mapper: debug

数据库spring_cache的代码:

/*
Navicat MySQL Data Transfer

Source Server         : 本地
Source Server Version : 50528
Source Host           : 127.0.0.1:3306
Source Database       : springboot_cache

Target Server Type    : MYSQL
Target Server Version : 50528
File Encoding         : 65001

Date: 2020-04-28 14:54:04
*/

SET FOREIGN_KEY_CHECKS=0;

-- ----------------------------
-- Table structure for 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 DEFAULT CHARSET=utf8;

-- ----------------------------
-- Table structure for employee
-- ----------------------------
DROP TABLE IF EXISTS `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 DEFAULT CHARSET=utf8;

部门bean代码

package com.seegot.bean;

import lombok.Data;

/**
 * @program: spring-boot-01-cache
 * @description:
 * @author: PP Zhang
 * @create: 2020-04-27 10:46
 */
@Data
public class Department {
    private Integer id;
    private String departmentName;
}

员工bean代码

package com.seegot.bean;

import lombok.Data;

/**
 * @program: spring-boot-01-cache
 * @description:
 * @author: PP Zhang
 * @create: 2020-04-27 10:47
 */
@Data
public class Employee {
    private Integer id;
    private String lastName;
    private String email;
    private Integer gender;
    private Integer dId;
}

EmployeeMapper的Mapper

package com.seegot.mapper;

import com.seegot.bean.Employee;
import org.apache.ibatis.annotations.*;

/**
 * @program: spring-boot-01-cache
 * @description:
 * @author: PP Zhang
 * @create: 2020-04-27 11:06
 */
@Mapper
public interface EmployeeMapper {
    @Select("select * from employee where id=#{id}")
    public Employee getEmpById(Integer id);
    @Update("update employee set lastName=#{lastName},email=#{email},gender=#{gender},d_id=#{dId} where id=#{id}")
    public void updateEmp(Employee emp);
    @Delete("delete from employee where id=#{id}")
    public void deleteEmpById(Integer id);
    @Insert("insert into employee(lastName,email,gender,d_id) values (#{lastName}),#{email},#{gender},#{dId}")
    public  void insertEmp(Employee emp);
}

Service层

package com.seegot.service;

import com.seegot.bean.Employee;
import com.seegot.mapper.EmployeeMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;

/**
 * @program: spring-boot-01-cache
 * @description:
 * @author: PP Zhang
 * @create: 2020-04-27 11:15
 */
@Service
public class EmployeeService {
    @Autowired
    EmployeeMapper employeeMapper;
    /**
    * @Description:
     *      将方法运行的结果进行缓存,以后再要相同的数据,直接从缓存中获取,不用调用方法去数据库查询。
     *  CacheManager管理多个Cache组件的,对缓存真正的CRUD操作在Cache组件中,每个缓存组件有自己唯一的名字;
     * @Cacheable几个属性:(key-value)
     *      cacheNames/values:指定缓存组件的名字。讲方法的反馈结果放在哪个缓存中,而且可以使数组的方式,可以指定多个缓存
     *      key:缓存数据时用的key;可以用它来指定,如果不指定Key默认是使用方法参数的值;
     *          编写SpEL:
     *              #id 是参数id的值。
     *      keyGenerator:key的生成器,可以自定义key的生成器组件id
     *      指定了Key 就不要再指定keyGenerator 二选一
     *      cacheManager:指定缓存管理器;或者cacheResolver指定获取解析器
     *      condition:指定符合条件的情况下才会缓存。@Cacheable(cacheNames = "emp",condition = "#id>0")
     *      unless:否定缓存,当unless指定的条件为true,方法返回值就不被缓存。可以获取到结果进行判断。unless = "#result==null
     *      sync:是否开启异步模式
     *
     *  原理:
     *      1.自动配置类:CacheAutoConfiguration
     *      2.缓存配置类
     *
     *      3.哪些配置类生效?
     *          SimpleCacheConfiguration
     *
     *   运行流程:
     * @Cacheable
     *      1.方法运行之前,先去查询Cache(缓存组件),按照cacheNames指定的名字获取;
     *      (CacheManager先获取相应的缓存),第一次获取缓存,如果没有Cache组件会自动创建,
     *      2.去Cache中查找缓存的内容,按使用一个Key,默认就是方法的参数;
     *        key是按照某种策略生成的。默认是使用keyGenerator生成的,默认使用SimpleKeyGenerator生成。
     *        SimpleKeyGenerator生成Key的默认策略:
     *          如果没有参数;key=new SimpleKey();
     *          如果有一个参数:key=参数的值()
     *          如果有多个参数:key=new SimpleKey(params);
     *      3.没有查到缓存就调用目标方法;
     *      4.将目标方法返回的结果,放进缓存中
     *
     *       @Cacheable标注的方法执行之前下来检查缓存中有没有这个数据,默认按照参数的值作为key去查询缓存,
     *       如果没有就运行方法并将结果放到缓存;以后在来调用,就可以直接使用缓存中的数据;
     *
     *
     *       核心:
     *          1)使用CacheManager【ConcurrentMapCacheManager】按照名字得到Cache【ConcurrentMapCache】组件
     *          2)key使用keyGenerator生成的,默认是SimpleKeyGenerator生成的。
    * @Param:
    * @return:
    * @Author: PP Zhang
    * @Date: 2020/4/27
    */
    // @Cacheable(value="emp",keyGenerator=myKeyGenerator)
    //  @Cacheable(cacheNames = "emp",key = "#root.methodName+'['+#id+']'",condition = "#a0>1 and #root.nethodName eq['aaa']")
    @Cacheable(cacheNames = "emp",key = "#id")
    public Employee getEmp(Integer id){
        System.out.println("查询"+id+"号的员工信息");
        Employee employee = employeeMapper.getEmpById(id);
        return  employee;
    }
    /**
     * @CachePut:即调用方法,又更新缓存数据;
     * 修改了数据库的某个数据,同时更新缓存;
     * 运行时机:
     *  1.先调用目标方法
     *  2.将目标方法结果缓存起来
     *
     * 测试步骤:
     *  1.查询1号员工;查到的结果会放到缓存中。
     *  key:1  value:lastName:Lilei
     *  2.以后查询还是之前的结果。
     *  3.更新1号员工信息。【lastName:zhangsan;gender=1】
     *      使用 @CachePut(value = "emp") 其实也将该条结果存入了缓存,
     *      由于没有指定缓存名称,所以默认是参数也就是 employee
     *      key:employee  value:返回的employee对象。
     *      @Cacheable是不可能用#result的。
     *  4.查询1号员工
     *     应该是更新的员工
     *     key = "#employee.id" 使用传入的参数员工id;
     *     key="#result.id" 是返回后的Id
     *
     *     为什么是没有更新前的?
     * */
    @CachePut(value = "emp",key = "#result.id")
    public  Employee updateEmp(Employee employee){
        System.out.println("updateEmp"+employee);
        employeeMapper.updateEmp(employee);
        return  employee;
    }
    /**
     * @CacheEvict:清除缓存
     * key 指定要清除的Key
     *
     * allEntries = true 删除指定缓存中的所有数据
     *
     * beforeInvocation = false 缓存的清除是否在方法之前执行?
     *  默认缓存清除是在方法执行之后执行的,如果方法出现异常,缓存就不会被清除。
     *
     *  beforeInvocation = true
     *   代表清除缓存操作是在方法运行之前执行的,无论方法是否出现异常,缓存都被清除。
     * */
    @CacheEvict(value = "emp",/*key = "#id",*//*allEntries = true*/beforeInvocation = true)
    public  void deleteEmp(Integer id){
        System.out.println("deleteEmp"+id);
        // employeeMapper.deleteEmpById(id);
        int i = 10/0;
    }
}

Controller代码

package com.seegot.controller;

import com.seegot.bean.Employee;
import com.seegot.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;

/**
 * @program: spring-boot-01-cache
 * @description:
 * @author: PP Zhang
 * @create: 2020-04-27 11:17
 */
@RestController
public class EmployeeController {
    @Autowired
    EmployeeService employeeService;

    @GetMapping("/emp/{id}")
    public Employee getEmpById(@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";
    }
}

http://192.168.100.106:8080/emp/1

完成上诉操作后,当第一次查询id=1用户时,会查一次数据库。当第二次查询id=1用户时,就不会在访问数据库了。

 
原文地址:https://www.cnblogs.com/pengpengzhang/p/12788104.html