1.核心
JAVA Caching定义了5哥核心接口,分别是CachingProvider,CacheManager,Cache,Entry和Expiry。
-
-
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用户时,就不会在访问数据库了。