1 JSR107

  • Java Caching定义了5个核心接口,分别是CachingProvider、CacheManager、Cache、Entry和Expiry。
  • SpringBoot和缓存 - 图1CachingProvider:定义了创建、配置、获取、管理和控制多个CacheManager。一个应用可以在运行期间访问多个CachingProvider。
  • SpringBoot和缓存 - 图2CacheManager:定义了创建、配置、获取、管理和控制多个唯一命名的Cache,这些Cache存在于CacheManager的上下文中。一个CacheManager仅被一个CachingProvider所拥有。
  • SpringBoot和缓存 - 图3Cache:是一个类似Map的数据结构并临时存储以key为索引的值。一个Cache仅被一个CacheManager所拥有。
  • SpringBoot和缓存 - 图4Entry:是一个存储在Cache中的key-value对。
  • SpringBoot和缓存 - 图5Expiry:每一个存储在Cache中的条目有一个定义的有效期。一旦超过这个时间,条目为过期的状态。一旦过期,条目将不可访问、更新和删除。缓存有效期可以通过ExpiryPolicy设置。

JSR107简介.png

2 Spring缓存抽象

  • Spring从3.1开始定义了org.springframework.cache.Cache和org.springframework.cache.CacheManager接口来统一不同的缓存技术,并支持使用JCache(JSR-107)注解来简化缓存开发。

Spring的缓存抽象.png

  • Cache接口为缓存的组件规范定义,包含缓存的各种操作集合。
  • Cache接口下Spring提供了各种xxxCache的实现,如RedisCache、EhCacheCache等。
  • 每次调用需要缓存功能的方法时,Spring会检查指定参数的目标方法是否已经被调用过,如果已经被调用过就直接从缓存中获取方法调用后的结果;如果没有调用过就调用方法并缓存结果后返回给用户,下次调用直接从缓存中获取。
  • 使用Spring缓存抽象的时候我们需要关注:
  • SpringBoot和缓存 - 图8确定方法需要被缓存以及它们的缓存策略。
  • SpringBoot和缓存 - 图9从缓存中读取数据之前需要缓存存储的数据。

3 几个重要概念&缓存注解

3.1 接口和缓存注解

接口和注解 描述
Cache 缓存接口,定义缓存操作。实现有:RedisCache、EhCacheCache、ConcurrenMapCache等。
CacheManager 缓存管理器,管理各种缓存(Cache)组件。
@Cacheable 主要针对方法配置,能够根据方法的请求参数对其结果进行缓存。
@CacheEvict 清空缓存
@CachePut 保证方法被调用,又希望结果被缓存。
@EnableCaching 开启基于注解的缓存
keyGenerator 缓存数据时key的生成策略。
serialize 缓存数据时value序列化策略
@Caching @Caching注解中包含@Cacheable、@CacheEvict、@CachePut,指定复杂的缓存规则
@CacheConfig 统一配置本类的缓存注解的属性

3.2 Cache中可以书写的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
argument name evaluation context 方法参数的名字. 可以直接 #参数名 ,也可以使用 #p0或#a0 的形式,0代表参数的索引; #iban 、 #a0 、 #p0
result evaluation context 方法执行后的返回值(仅当方法执行之后的判断有效,如‘unless’,’cache put’的表达式 ’cache evict’的表达式beforeInvocation=false) #result

4 缓存使用

4.1 基本环境搭建

  • 导入相关jar包的Maven坐标:
  1. <dependency>
  2. <groupId>org.springframework.boot</groupId>
  3. <artifactId>spring-boot-configuration-processor</artifactId>
  4. <optional>true</optional>
  5. </dependency>
  6. <dependency>
  7. <groupId>mysql</groupId>
  8. <artifactId>mysql-connector-java</artifactId>
  9. </dependency>
  10. <dependency>
  11. <groupId>org.springframework.boot</groupId>
  12. <artifactId>spring-boot-starter-jdbc</artifactId>
  13. </dependency>
  14. <dependency>
  15. <groupId>org.springframework.boot</groupId>
  16. <artifactId>spring-boot-starter-test</artifactId>
  17. <scope>test</scope>
  18. </dependency>
  19. <dependency>
  20. <groupId>org.springframework.boot</groupId>
  21. <artifactId>spring-boot-starter-cache</artifactId>
  22. </dependency>
  23. <dependency>
  24. <groupId>org.springframework.boot</groupId>
  25. <artifactId>spring-boot-starter-web</artifactId>
  26. </dependency>
  27. <dependency>
  28. <groupId>com.alibaba</groupId>
  29. <artifactId>druid-spring-boot-starter</artifactId>
  30. <version>1.1.22</version>
  31. </dependency>
  32. <dependency>
  33. <groupId>org.mybatis.spring.boot</groupId>
  34. <artifactId>mybatis-spring-boot-starter</artifactId>
  35. <version>2.1.3</version>
  36. </dependency>
  • sql脚本:
  1. DROP TABLE IF EXISTS `employee`;
  2. CREATE TABLE `employee` (
  3. `id` int(11) NOT NULL AUTO_INCREMENT,
  4. `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
  5. `gender` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
  6. PRIMARY KEY (`id`) USING BTREE
  7. ) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
  • 实体类Employee.java
  1. package com.sunxiaping.springboot.domain;
  2. import java.io.Serializable;
  3. /**
  4. * @author 许威威
  5. * @version 1.0
  6. */
  7. public class Employee implements Serializable {
  8. private Integer id;
  9. private String name;
  10. private String gender;
  11. public Integer getId() {
  12. return id;
  13. }
  14. public void setId(Integer id) {
  15. this.id = id;
  16. }
  17. public String getName() {
  18. return name;
  19. }
  20. public void setName(String name) {
  21. this.name = name;
  22. }
  23. public String getGender() {
  24. return gender;
  25. }
  26. public void setGender(String gender) {
  27. this.gender = gender;
  28. }
  29. @Override
  30. public String toString() {
  31. return "Employee{" +
  32. "id=" + id +
  33. ", name='" + name + '\'' +
  34. ", gender='" + gender + '\'' +
  35. '}';
  36. }
  37. }
  • application.yml
  1. spring:
  2. datasource:
  3. username: root
  4. password: 123456
  5. url: jdbc:mysql://192.168.1.57:3306/test?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&useSSL=false&serverTimezone=GMT%2B8&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true
  6. driver-class-name: com.mysql.cj.jdbc.Driver
  7. type: com.alibaba.druid.pool.DruidDataSource
  8. # 数据源的其他配置
  9. druid:
  10. # 初始化
  11. initial-size: 5
  12. # 最小
  13. min-idle: 5
  14. # 最大
  15. max-active: 20
  16. # 连接等待超时时间
  17. max-wait: 60000
  18. # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
  19. time-between-eviction-runs-millis: 60000
  20. # 配置一个连接在池中最小生存的时间,单位是毫秒
  21. min-evictable-idle-time-millis: 300000
  22. validation-query: SELECT 1 FROM DUAL
  23. test-on-borrow: false
  24. test-while-idle: true
  25. test-on-return: false
  26. # 打开PSCache,并且指定每个连接上PSCache的大小
  27. pool-prepared-statements: true
  28. max-pool-prepared-statement-per-connection-size: 20
  29. # 配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙
  30. filters: stat,wall
  31. #合并多个DruidDataSource的监控数据
  32. use-global-data-source-stat: true
  33. # 通过connectProperties属性来打开mergeSql功能;慢SQL记录
  34. connection-properties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
  35. # 配置DruidStatFilter
  36. web-stat-filter:
  37. enabled: true
  38. url-pattern: "/*"
  39. exclusions: "*.js,*.gif,*.jpg,*.bmp,*.png,*.css,*.ico,/druid/*"
  40. # 配置DruidStatViewServlet
  41. stat-view-servlet:
  42. enabled: true
  43. url-pattern: "/druid/*"
  44. # IP白名单(没有配置或者为空,则允许所有访问)
  45. # allow:
  46. # IP黑名单 (存在共同时,deny优先于allow)
  47. # deny:
  48. # 禁用HTML页面上的“Reset All”功能
  49. reset-enable: false
  50. # 登录名
  51. login-username: admin
  52. # 登录密码
  53. login-password: 123456
  54. # 开启mybatis的日志
  55. logging:
  56. level:
  57. com.sunxiaping.springboot.mapper: debug #打印sql
  • EmployeeMapper.java
  1. package com.sunxiaping.springboot.mapper;
  2. import com.sunxiaping.springboot.domain.Employee;
  3. import org.apache.ibatis.annotations.*;
  4. /**
  5. * @author 许威威
  6. * @version 1.0
  7. */
  8. @Mapper
  9. public interface EmployeeMapper {
  10. @Results(id = "employeeMap", value = {
  11. @Result(id = true, column = "id", property = "id"),
  12. @Result(property = "name", column = "name"),
  13. @Result(property = "gender", column = "gender")
  14. })
  15. @Select("SELECT id,`name`,gender FROM employee WHERE id = #{id}")
  16. Employee findEmpById(Integer id);
  17. @Insert(value = "INSERT INTO employee (`name`,gender) VALUES (#{name},#{gender})")
  18. void saveEmp(Employee employee);
  19. @Update(value = "UPDATE employee SET `name` = #{name},gender =#{gender} WHERE id = #{id}")
  20. void updateEmp(Employee employee);
  21. @Delete(value = "DELETE FROM employee WHERE id = #{id}")
  22. void deleteEmpById(Integer id);
  23. }
  • EmployeeService.java
  1. package com.sunxiaping.springboot.service;
  2. import com.sunxiaping.springboot.domain.Employee;
  3. /**
  4. * @author 许威威
  5. * @version 1.0
  6. */
  7. public interface EmployeeService {
  8. Employee findEmpById(Integer id);
  9. }
  • EmployeeServiceImpl.java
  1. package com.sunxiaping.springboot.service.impl;
  2. import com.sunxiaping.springboot.domain.Employee;
  3. import com.sunxiaping.springboot.mapper.EmployeeMapper;
  4. import com.sunxiaping.springboot.service.EmployeeService;
  5. import org.springframework.beans.factory.annotation.Autowired;
  6. import org.springframework.stereotype.Service;
  7. import javax.transaction.Transactional;
  8. import java.util.Optional;
  9. /**
  10. * @author 许威威
  11. * @version 1.0
  12. */
  13. @Service
  14. @Transactional
  15. public class EmployeeServiceImpl implements EmployeeService {
  16. @Autowired
  17. private EmployeeMapper employeeMapper;
  18. @Override
  19. public Employee findEmpById(Integer id) {
  20. return Optional.ofNullable(employeeMapper.findEmpById(id)).orElse(new Employee());
  21. }
  22. }
  • EmployeeController.java
  1. package com.sunxiaping.springboot.web;
  2. import com.sunxiaping.springboot.domain.Employee;
  3. import com.sunxiaping.springboot.service.EmployeeService;
  4. import org.springframework.beans.factory.annotation.Autowired;
  5. import org.springframework.web.bind.annotation.GetMapping;
  6. import org.springframework.web.bind.annotation.PathVariable;
  7. import org.springframework.web.bind.annotation.RequestMapping;
  8. import org.springframework.web.bind.annotation.RestController;
  9. /**
  10. * @author 许威威
  11. * @version 1.0
  12. */
  13. @RestController
  14. @RequestMapping(value = "emp")
  15. public class EmployeeController {
  16. @Autowired
  17. private EmployeeService employeeService;
  18. @GetMapping(value = "/view/{id}")
  19. public Employee view(@PathVariable(value = "id") Integer id) {
  20. return employeeService.findEmpById(id);
  21. }
  22. }
  • 启动类:
  1. package com.sunxiaping.springboot;
  2. import org.mybatis.spring.annotation.MapperScan;
  3. import org.springframework.boot.SpringApplication;
  4. import org.springframework.boot.autoconfigure.SpringBootApplication;
  5. @SpringBootApplication
  6. @MapperScan(basePackages = "com.sunxiaping.springboot.mapper")
  7. public class SpringbootApplication {
  8. public static void main(String[] args) {
  9. SpringApplication.run(SpringbootApplication.class, args);
  10. }
  11. }
  • 测试:
  1. package com.sunxiaping.springboot;
  2. import com.sunxiaping.springboot.domain.Employee;
  3. import com.sunxiaping.springboot.mapper.EmployeeMapper;
  4. import org.junit.Test;
  5. import org.junit.runner.RunWith;
  6. import org.springframework.beans.factory.annotation.Autowired;
  7. import org.springframework.boot.test.context.SpringBootTest;
  8. import org.springframework.test.context.junit4.SpringRunner;
  9. @SpringBootTest
  10. @RunWith(SpringRunner.class)
  11. public class SpringbootApplicationTests {
  12. @Autowired
  13. private EmployeeMapper employeeMapper;
  14. @Test
  15. public void test() {
  16. Employee employee = employeeMapper.findEmpById(1);
  17. System.out.println("employee = " + employee);
  18. }
  19. }

4.2 体验缓存

4.2.1 步骤

  • SpringBoot和缓存 - 图10开启注解的缓存(@EnableCaching)。
  • SpringBoot和缓存 - 图11标注缓存注解即可(@Cacheable、@CachePut、@CacheEvict)。

4.2.2 @Cacheable注解

  • @Cacheable注解:
    • 将方法运行的结果进行缓存,以后再要相同的数据,直接从缓存中获取,不需要再调用方法。
    • CacheManager管理多个Cache组件,对缓存真正的CRUD操作在Cache组件中,每一个缓存组件都有自己唯一的名称。
    • 属性:
      • value/cacheNames:指定缓存组件的名称
      • key:缓存数据使用的key,默认是方法参数的值(如果传入的参数是1,那么值就是1对应的返回的返回值。),支持SPEL表达式。
      • keyGenerator:key的生成器,可以自己指定key的生成器的组件id。key和keyGenerator只能二选一。
      • cacheManager:指定缓存管理器。
      • cacheResolver:指定缓存解析器。
      • condition:指定符合条件的情况下才缓存。支持SPEL表达式。
      • unless:否定缓存。当unless指定的条件为true,方法的返回值就不会返回;可以获取到结果进行判断。
      • sync:是否同步,默认值是false,即异步,在多线程环境中需要设置为true,避免缓存击穿的问题。
  • 示例:
  • 在启动类上使用@EnableCaching注解开启缓存:
  1. package com.sunxiaping.springboot;
  2. import org.mybatis.spring.annotation.MapperScan;
  3. import org.springframework.boot.SpringApplication;
  4. import org.springframework.boot.autoconfigure.SpringBootApplication;
  5. import org.springframework.cache.annotation.EnableCaching;
  6. @SpringBootApplication
  7. @MapperScan(basePackages = "com.sunxiaping.springboot.mapper")
  8. @EnableCaching
  9. public class SpringbootApplication {
  10. public static void main(String[] args) {
  11. SpringApplication.run(SpringbootApplication.class, args);
  12. }
  13. }
  • 使用@Cacheable注解方法返回的结果缓存:
  1. package com.sunxiaping.springboot.service.impl;
  2. import com.sunxiaping.springboot.domain.Employee;
  3. import com.sunxiaping.springboot.mapper.EmployeeMapper;
  4. import com.sunxiaping.springboot.service.EmployeeService;
  5. import org.springframework.beans.factory.annotation.Autowired;
  6. import org.springframework.cache.annotation.Cacheable;
  7. import org.springframework.stereotype.Service;
  8. import org.springframework.transaction.annotation.Transactional;
  9. import java.util.Optional;
  10. /**
  11. * @author 许威威
  12. * @version 1.0
  13. */
  14. @Service
  15. @Transactional
  16. public class EmployeeServiceImpl implements EmployeeService {
  17. @Autowired
  18. private EmployeeMapper employeeMapper;
  19. /**
  20. * @Cacheable注解: 将方法的运行结果进行缓存,以后再要相同的数据,直接从缓存中获取,不需要再调用方法
  21. * CacheManager管理多个Cache组件,对缓存的真正CRUD操作在Cache组件中,每一个缓存组件都有自己唯一的名称
  22. * @Cacheable注解的属性:
  23. * value/cacheNames:指定缓存组件的名称
  24. * key:缓存数据使用的key,默认是方法参数的值(如果传入的参数是1,那么值就是1对应的返回的返回值。),支持SPEL表达式。
  25. * keyGenerator:key的生成器,可以自己指定key的生成器的组件id。key和keyGenerator二选一。
  26. * cacheManager:指定缓存管理器。
  27. * cacheResolver:指定缓存解析器。
  28. * condition:指定符合条件的情况下才缓存。支持SPEL表达式。
  29. * unless:否定缓存。当unless指定的条件为true,方法的返回值就不会返回。
  30. * sync:是否同步,默认值是false,即异步,在多线程环境中需要设置为true,避免缓存击穿的问题。
  31. */
  32. @Cacheable(cacheNames = "emp", key = "#id")
  33. @Override
  34. public Employee findEmpById(Integer id) {
  35. return Optional.ofNullable(employeeMapper.findEmpById(id)).orElse(new Employee());
  36. }
  37. }

4.3 缓存的工作原理

  • SpringBoot和缓存 - 图12自动配置类:CacheAutoConfiguration,其源码片段如下:
  1. @Configuration(proxyBeanMethods = false)
  2. @ConditionalOnClass(CacheManager.class)
  3. @ConditionalOnBean(CacheAspectSupport.class)
  4. @ConditionalOnMissingBean(value = CacheManager.class, name = "cacheResolver")
  5. @EnableConfigurationProperties(CacheProperties.class)
  6. @AutoConfigureAfter({ CouchbaseDataAutoConfiguration.class, HazelcastAutoConfiguration.class,
  7. HibernateJpaAutoConfiguration.class, RedisAutoConfiguration.class })
  8. @Import({ CacheConfigurationImportSelector.class, CacheManagerEntityManagerFactoryDependsOnPostProcessor.class })
  9. public class CacheAutoConfiguration {
  10. //其他略
  11. }
  • SpringBoot和缓存 - 图13通过CacheAutoConfiguration上面的@Import注解,可以知道其向Spring容器中导入了一些组件,dubug后的缓存配置类如下所示:

导入到Spring容器中的缓存配置类.png

  • SpringBoot和缓存 - 图15默认生效的缓存配置类。可以通过在application.yml中增加dubug=true,来查看那个缓存配置类生成。可以得知SimpleCacheConfiguration的缓存配置类生效:
  • SpringBoot和缓存 - 图16SimpleCacheConfiguration给容器中添加了ConcurrentMapCacheManager组件。
  1. @Configuration(proxyBeanMethods = false)
  2. @ConditionalOnMissingBean(CacheManager.class)
  3. @Conditional(CacheCondition.class)
  4. class SimpleCacheConfiguration {
  5. //给容器中添加了ConcurrentMapCacheManager组件
  6. @Bean
  7. ConcurrentMapCacheManager cacheManager(CacheProperties cacheProperties,
  8. CacheManagerCustomizers cacheManagerCustomizers) {
  9. ConcurrentMapCacheManager cacheManager = new ConcurrentMapCacheManager();
  10. List<String> cacheNames = cacheProperties.getCacheNames();
  11. if (!cacheNames.isEmpty()) {
  12. cacheManager.setCacheNames(cacheNames);
  13. }
  14. return cacheManagerCustomizers.customize(cacheManager);
  15. }
  16. }
  • SpringBoot和缓存 - 图17ConcurrentMapCacheManager的部分源码如下:可以获取和创建ConcurrentMapCache类型的缓存组件,并把数据保存在ConcurrentMap中。
  1. public class ConcurrentMapCacheManager implements CacheManager, BeanClassLoaderAware {
  2. //重写CacheManager的getCache方法
  3. @Override
  4. @Nullable
  5. public Cache getCache(String name) {
  6. //根据传入的name从CacheManager中获取Cache对象
  7. Cache cache = this.cacheMap.get(name);
  8. //如果cache不存在
  9. if (cache == null && this.dynamic) {
  10. //加锁
  11. synchronized (this.cacheMap) {
  12. //再获取一次
  13. cache = this.cacheMap.get(name);
  14. //如果依然不遵从
  15. if (cache == null) {
  16. //创建一个新的ConcurrentMapCache并返回
  17. cache = createConcurrentMapCache(name);
  18. //将上面创建的ConcurrentMapCache作为value放入到CacheManager中,key就是传入的name
  19. this.cacheMap.put(name, cache);
  20. }
  21. }
  22. }
  23. return cache;
  24. }
  25. }

4.4 @Cacheable注解的工作流程

  • SpringBoot和缓存 - 图18方法运行之前,先去查询Cache(缓存组件),按照cacheNames(指定的缓存名称)从CacheManager中获取相应的缓存组件。
  • SpringBoot和缓存 - 图19第一次获取缓存,如果没有对应的Cache组件,会自动创建Cache组件(默认为ConcurrentMapCache),然后将其保存到ConcurrentMapCacheManager中的cacheMap中,key是cacheNames,value是ConcurrentMapCache。
  • SpringBoot和缓存 - 图20根据指定的key,即@Cacheable注解中属性key对应的值(默认是方法传入的实际参数值),去Cache中查找缓存的内容。
    • key是按照某种策略自自动生成的,默认是使用keyGenerator(SimpleKeyGenerator)生成的。
    • 如果没有参数,key=new SimpleKey()。
    • 如果有一个参数,key=参数值。
    • 如果有多个参数,key就是new SimipleKey(params)。
  • SpringBoot和缓存 - 图21没有查到缓存就调用目标方法。
  • SpringBoot和缓存 - 图22将目标方法返回的结果,放入到缓存。

4.5 @CachePut注解的使用

  • EmployeeService.java
  1. package com.sunxiaping.springboot.service;
  2. import com.sunxiaping.springboot.domain.Employee;
  3. /**
  4. * @author 许威威
  5. * @version 1.0
  6. */
  7. public interface EmployeeService {
  8. /**
  9. * 根据id查询员工信息
  10. *
  11. * @param id
  12. * @return
  13. */
  14. Employee findEmpById(Integer id);
  15. /**
  16. * 更新员工信息
  17. *
  18. * @param employee
  19. * @return
  20. */
  21. Employee updateEmp(Employee employee);
  22. }
  • EmployeeServiceImpl.java
  1. package com.sunxiaping.springboot.service.impl;
  2. import com.sunxiaping.springboot.domain.Employee;
  3. import com.sunxiaping.springboot.mapper.EmployeeMapper;
  4. import com.sunxiaping.springboot.service.EmployeeService;
  5. import org.springframework.beans.factory.annotation.Autowired;
  6. import org.springframework.cache.annotation.CachePut;
  7. import org.springframework.cache.annotation.Cacheable;
  8. import org.springframework.stereotype.Service;
  9. import org.springframework.transaction.annotation.Transactional;
  10. import java.util.Optional;
  11. /**
  12. * @author 许威威
  13. * @version 1.0
  14. */
  15. @Service
  16. @Transactional
  17. public class EmployeeServiceImpl implements EmployeeService {
  18. @Autowired
  19. private EmployeeMapper employeeMapper;
  20. /**
  21. * @Cacheable注解: 将方法的运行结果进行缓存,以后再要相同的数据,直接从缓存中获取,不需要再调用方法
  22. * CacheManager管理多个Cache组件,对缓存的真正CRUD操作在Cache组件中,每一个缓存组件都有自己唯一的名称
  23. * @Cacheable注解的属性: value/cacheNames:指定缓存组件的名称
  24. * key:缓存数据使用的key,默认是方法参数的值(如果传入的参数是1,那么值就是1对应的返回的返回值。),支持SPEL表达式。
  25. * keyGenerator:key的生成器,可以自己指定key的生成器的组件id。key和keyGenerator二选一。
  26. * cacheManager:指定缓存管理器。
  27. * cacheResolver:指定缓存解析器。
  28. * condition:指定符合条件的情况下才缓存。支持SPEL表达式。
  29. * unless:否定缓存。当unless指定的条件为true,方法的返回值就不会返回。
  30. * sync:是否同步,默认值是false,即异步,在多线程环境中需要设置为true,避免缓存击穿的问题。
  31. */
  32. @Cacheable(cacheNames = "emp", key = "#id")
  33. @Override
  34. public Employee findEmpById(Integer id) {
  35. return Optional.ofNullable(employeeMapper.findEmpById(id)).orElse(new Employee());
  36. }
  37. /**
  38. * @CachePut employee 既调用方法,又更新缓存数据
  39. * 修改了数据库的某个数据,同时更新缓存
  40. * 运行时机:①先调用目标方法②将目标方法的结果放入到缓存中
  41. */
  42. @CachePut(cacheNames = "emp",key = "#employee.id")
  43. @Override
  44. public Employee updateEmp(Employee employee) {
  45. employeeMapper.updateEmp(employee);
  46. return employee;
  47. }
  48. }

4.6 @CacheEvict注解的使用

  • EmployeeService.java
  1. package com.sunxiaping.springboot.service;
  2. import com.sunxiaping.springboot.domain.Employee;
  3. /**
  4. * @author 许威威
  5. * @version 1.0
  6. */
  7. public interface EmployeeService {
  8. /**
  9. * 根据id查询员工信息
  10. *
  11. * @param id
  12. * @return
  13. */
  14. Employee findEmpById(Integer id);
  15. /**
  16. * 更新员工信息
  17. *
  18. * @param employee
  19. * @return
  20. */
  21. Employee updateEmp(Employee employee);
  22. /**
  23. * 根据主键删除员工信息
  24. *
  25. * @param id
  26. */
  27. void deleteEmp(Integer id);
  28. }
  • EmployeeServiceImpl.java
  1. package com.sunxiaping.springboot.service.impl;
  2. import com.sunxiaping.springboot.domain.Employee;
  3. import com.sunxiaping.springboot.mapper.EmployeeMapper;
  4. import com.sunxiaping.springboot.service.EmployeeService;
  5. import org.springframework.beans.factory.annotation.Autowired;
  6. import org.springframework.cache.annotation.CacheEvict;
  7. import org.springframework.cache.annotation.CachePut;
  8. import org.springframework.cache.annotation.Cacheable;
  9. import org.springframework.stereotype.Service;
  10. import org.springframework.transaction.annotation.Transactional;
  11. import java.util.Optional;
  12. /**
  13. * @author 许威威
  14. * @version 1.0
  15. */
  16. @Service
  17. @Transactional
  18. public class EmployeeServiceImpl implements EmployeeService {
  19. @Autowired
  20. private EmployeeMapper employeeMapper;
  21. /**
  22. * @Cacheable注解: 将方法的运行结果进行缓存,以后再要相同的数据,直接从缓存中获取,不需要再调用方法
  23. * CacheManager管理多个Cache组件,对缓存的真正CRUD操作在Cache组件中,每一个缓存组件都有自己唯一的名称
  24. * @Cacheable注解的属性: value/cacheNames:指定缓存组件的名称
  25. * key:缓存数据使用的key,默认是方法参数的值(如果传入的参数是1,那么值就是1对应的返回的返回值。),支持SPEL表达式。
  26. * keyGenerator:key的生成器,可以自己指定key的生成器的组件id。key和keyGenerator二选一。
  27. * cacheManager:指定缓存管理器。
  28. * cacheResolver:指定缓存解析器。
  29. * condition:指定符合条件的情况下才缓存。支持SPEL表达式。
  30. * unless:否定缓存。当unless指定的条件为true,方法的返回值就不会返回。
  31. * sync:是否同步,默认值是false,即异步,在多线程环境中需要设置为true,避免缓存击穿的问题。
  32. */
  33. @Cacheable(cacheNames = "emp", key = "#id")
  34. @Override
  35. public Employee findEmpById(Integer id) {
  36. return Optional.ofNullable(employeeMapper.findEmpById(id)).orElse(new Employee());
  37. }
  38. /**
  39. * @CachePut employee 既调用方法,又更新缓存数据
  40. * 修改了数据库的某个数据,同时更新缓存
  41. * 运行时机:①先调用目标方法②将目标方法的结果放入到缓存中
  42. */
  43. @CachePut(cacheNames = "emp", key = "#employee.id")
  44. @Override
  45. public Employee updateEmp(Employee employee) {
  46. employeeMapper.updateEmp(employee);
  47. return employee;
  48. }
  49. /**
  50. * @CacheEvict 清除缓存
  51. * key:缓存的key。
  52. * allEntries:清空所有的缓存,默认是false。
  53. * beforeInvocation:是否在方法之前执行,默认值是false,即表示方法执行之后执行。
  54. */
  55. @CacheEvict(cacheNames = "emp",key = "#id")
  56. @Override
  57. public void deleteEmp(Integer id) {
  58. employeeMapper.deleteEmpById(id);
  59. }
  60. }

5 整合Redis实现缓存

5.1 导入SpringBoot和Redis的整合包的Maven坐标

  • spring-boot-starter-data-redis的坐标:
  1. <dependency>
  2. <groupId>org.springframework.boot</groupId>
  3. <artifactId>spring-boot-starter-data-redis</artifactId>
  4. </dependency>
  • 完整的pom.xml:
  1. <dependency>
  2. <groupId>org.springframework.boot</groupId>
  3. <artifactId>spring-boot-configuration-processor</artifactId>
  4. <optional>true</optional>
  5. </dependency>
  6. <dependency>
  7. <groupId>mysql</groupId>
  8. <artifactId>mysql-connector-java</artifactId>
  9. </dependency>
  10. <dependency>
  11. <groupId>org.springframework.boot</groupId>
  12. <artifactId>spring-boot-starter-jdbc</artifactId>
  13. </dependency>
  14. <dependency>
  15. <groupId>org.springframework.boot</groupId>
  16. <artifactId>spring-boot-starter-data-redis</artifactId>
  17. </dependency>
  18. <dependency>
  19. <groupId>org.springframework.boot</groupId>
  20. <artifactId>spring-boot-starter-test</artifactId>
  21. <scope>test</scope>
  22. </dependency>
  23. <dependency>
  24. <groupId>org.springframework.boot</groupId>
  25. <artifactId>spring-boot-starter-web</artifactId>
  26. </dependency>
  27. <dependency>
  28. <groupId>com.alibaba</groupId>
  29. <artifactId>druid-spring-boot-starter</artifactId>
  30. <version>1.1.22</version>
  31. </dependency>
  32. <dependency>
  33. <groupId>org.springframework.boot</groupId>
  34. <artifactId>spring-boot-starter-cache</artifactId>
  35. </dependency>
  36. <dependency>
  37. <groupId>org.mybatis.spring.boot</groupId>
  38. <artifactId>mybatis-spring-boot-starter</artifactId>
  39. <version>2.1.3</version>
  40. </dependency>

5.2 修改application.yml增加redis的相关配置

  • application.yml:
  1. spring:
  2. #redis的配置
  3. redis:
  4. # 主机地址
  5. host: 192.168.1.57
  6. # 端口
  7. port: 6379
  8. # 密码
  9. password:
  • 完整的application.yml:
  1. spring:
  2. datasource:
  3. username: root
  4. password: 123456
  5. url: jdbc:mysql://192.168.1.57:3306/test?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true
  6. driver-class-name: com.mysql.cj.jdbc.Driver
  7. type: com.alibaba.druid.pool.DruidDataSource
  8. # 数据源的其他配置
  9. druid:
  10. # 初始化
  11. initial-size: 5
  12. # 最小
  13. min-idle: 5
  14. # 最大
  15. max-active: 20
  16. # 连接等待超时时间
  17. max-wait: 60000
  18. # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
  19. time-between-eviction-runs-millis: 60000
  20. # 配置一个连接在池中最小生存的时间,单位是毫秒
  21. min-evictable-idle-time-millis: 300000
  22. validation-query: SELECT 1 FROM DUAL
  23. test-on-borrow: false
  24. test-while-idle: true
  25. test-on-return: false
  26. # 打开PSCache,并且指定每个连接上PSCache的大小
  27. pool-prepared-statements: true
  28. max-pool-prepared-statement-per-connection-size: 20
  29. # 配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙
  30. filters: stat,wall
  31. #合并多个DruidDataSource的监控数据
  32. use-global-data-source-stat: true
  33. # 通过connectProperties属性来打开mergeSql功能;慢SQL记录
  34. connection-properties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
  35. # 配置DruidStatFilter
  36. web-stat-filter:
  37. enabled: true
  38. url-pattern: "/*"
  39. exclusions: "*.js,*.gif,*.jpg,*.bmp,*.png,*.css,*.ico,/druid/*"
  40. # 配置DruidStatViewServlet
  41. stat-view-servlet:
  42. enabled: true
  43. url-pattern: "/druid/*"
  44. # IP白名单(没有配置或者为空,则允许所有访问)
  45. # allow:
  46. # IP黑名单 (存在共同时,deny优先于allow)
  47. # deny:
  48. # 禁用HTML页面上的“Reset All”功能
  49. reset-enable: false
  50. # 登录名
  51. login-username: admin
  52. # 登录密码
  53. login-password: 123456
  54. # redis的配置
  55. redis:
  56. # 主机地址
  57. host: 192.168.1.57
  58. # 端口
  59. port: 6379
  60. # 密码
  61. password:
  62. # 开启mybatis的日志
  63. logging:
  64. level:
  65. com.sunxiaping.springboot.mapper: debug #打印sql

5.3 修改默认的序列化机制

  • spring-data-redis的默认序列化机制是JDK序列化器,实际使用的时候,可以改为JSON形式的序列化器。
  • 更改默认的序列化器:
  1. package com.sunxiaping.springboot.config;
  2. import org.springframework.context.annotation.Bean;
  3. import org.springframework.context.annotation.Configuration;
  4. import org.springframework.data.redis.connection.RedisConnectionFactory;
  5. import org.springframework.data.redis.core.RedisTemplate;
  6. import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
  7. /**
  8. * @author 许威威
  9. * @version 1.0
  10. */
  11. @Configuration
  12. public class RedisConfig {
  13. @Bean
  14. public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory) {
  15. RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>();
  16. redisTemplate.setConnectionFactory(redisConnectionFactory);
  17. redisTemplate.setDefaultSerializer(new Jackson2JsonRedisSerializer<>(Object.class));
  18. //开启事务
  19. redisTemplate.setEnableTransactionSupport(true);
  20. return redisTemplate;
  21. }
  22. }

5.4 测试SpringBoot整合Redis

  • 测试:
  1. package com.sunxiaping.springboot;
  2. import com.sunxiaping.springboot.domain.Employee;
  3. import com.sunxiaping.springboot.mapper.EmployeeMapper;
  4. import org.junit.Test;
  5. import org.junit.runner.RunWith;
  6. import org.springframework.beans.factory.annotation.Autowired;
  7. import org.springframework.boot.test.context.SpringBootTest;
  8. import org.springframework.data.redis.core.RedisTemplate;
  9. import org.springframework.data.redis.core.StringRedisTemplate;
  10. import org.springframework.data.redis.core.ValueOperations;
  11. import org.springframework.test.context.junit4.SpringRunner;
  12. import java.util.Optional;
  13. @SpringBootTest
  14. @RunWith(SpringRunner.class)
  15. public class SpringbootApplicationTests {
  16. /**
  17. * RedisTemplate k-v都是Object
  18. */
  19. @Autowired
  20. private RedisTemplate redisTemplate;
  21. @Autowired
  22. private EmployeeMapper employeeMapper;
  23. /**
  24. * StringRedisTemplate k-v都是字符串
  25. */
  26. @Autowired
  27. private StringRedisTemplate stringRedisTemplate;
  28. /**
  29. * Redis常见的五大数据类型:
  30. * String(字符串)、List(列表)、Set(集合)、Hash(散列)、ZSet(有序集合)
  31. * <p>
  32. * //操作String字符串
  33. * ValueOperations valueOperations = stringRedisTemplate.opsForValue();
  34. * //操作List列表
  35. * ListOperations listOperations = stringRedisTemplate.opsForList();
  36. * //操作Set集合
  37. * SetOperations setOperations = stringRedisTemplate.opsForSet();
  38. * //操作Hash散列
  39. * HashOperations hashOperations = stringRedisTemplate.opsForHash();
  40. * //操作Zset有序集合
  41. * ZSetOperations zSetOperations = stringRedisTemplate.opsForZSet();
  42. */
  43. @Test
  44. public void test() {
  45. ValueOperations valueOperations = stringRedisTemplate.opsForValue();
  46. valueOperations.set("k1", "v1");
  47. Object value = valueOperations.get("k1");
  48. System.out.println("value = " + value);
  49. }
  50. /**
  51. * RedisTemplate默认用的是JDK的序列化
  52. */
  53. @Test
  54. public void test2() {
  55. Employee employee = employeeMapper.findEmpById(1);
  56. Optional.ofNullable(employee).ifPresent((emp) -> {
  57. redisTemplate.opsForValue().set("emp01", emp);
  58. });
  59. }
  60. }

5.5 自定义CacheManager,使其缓存变为Redis

SpringBoot的版本是2.x。

5.5.1 原理

  • SpringBoot和缓存 - 图23引入spring-boot-starter-data-redis.jar包以后,容器中保存的是RedisCacheManager。
  • SpringBoot和缓存 - 图24RedisCacheManager帮我们创建RedisCache作为缓存组件,RedisCache通过操作Redis缓存数据。
  • SpringBoot和缓存 - 图25默认保存数据k-v都是Object类型,利用的是JDK的序列化机制进行序列化的。
    • 引入了Redis的starter,cacheManager变为RedisCacheManager。
    • 默认创建的RedisCacheManager操作Redis的时候使用的是RedisTemplate
    • RedisTemplate默认默认使用的是JDK的序列化机制。
  • SpringBoot和缓存 - 图26自定义CacheManager。

5.5.2 SpringBoot使用Redis作为缓存管理器

  • RedisConfig.java
  1. package com.sunxiaping.springboot.config;
  2. import com.fasterxml.jackson.annotation.JsonAutoDetect;
  3. import com.fasterxml.jackson.annotation.PropertyAccessor;
  4. import com.fasterxml.jackson.databind.ObjectMapper;
  5. import org.springframework.context.annotation.Bean;
  6. import org.springframework.context.annotation.Configuration;
  7. import org.springframework.data.redis.cache.RedisCacheConfiguration;
  8. import org.springframework.data.redis.cache.RedisCacheManager;
  9. import org.springframework.data.redis.connection.RedisConnectionFactory;
  10. import org.springframework.data.redis.core.RedisTemplate;
  11. import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
  12. import org.springframework.data.redis.serializer.RedisSerializationContext;
  13. import org.springframework.data.redis.serializer.RedisSerializer;
  14. import org.springframework.data.redis.serializer.StringRedisSerializer;
  15. import java.time.Duration;
  16. /**
  17. * @author 许威威
  18. * @version 1.0
  19. */
  20. @Configuration
  21. public class RedisConfig {
  22. @Bean
  23. public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory) {
  24. RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>();
  25. redisTemplate.setConnectionFactory(redisConnectionFactory);
  26. redisTemplate.setDefaultSerializer(new Jackson2JsonRedisSerializer<>(Object.class));
  27. //开启事务
  28. redisTemplate.setEnableTransactionSupport(true);
  29. return redisTemplate;
  30. }
  31. /**
  32. * 自定义Redis缓存管理器
  33. *
  34. * @param factory
  35. * @return
  36. */
  37. @Bean
  38. public RedisCacheManager redisCacheManager(RedisConnectionFactory factory) {
  39. RedisSerializer<String> redisSerializer = new StringRedisSerializer();
  40. Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
  41. //解决查询缓存转换异常的问题
  42. ObjectMapper om = new ObjectMapper();
  43. om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
  44. om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
  45. jackson2JsonRedisSerializer.setObjectMapper(om);
  46. // 配置序列化(解决乱码的问题)
  47. RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
  48. .entryTtl(Duration.ZERO)
  49. .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer))
  50. .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer))
  51. .disableCachingNullValues();
  52. RedisCacheManager cacheManager = RedisCacheManager.builder(factory)
  53. .cacheDefaults(config)
  54. .build();
  55. return cacheManager;
  56. }
  57. }