0


SpringCache

SpringCache缓存

一.为什么使用缓存

​ 前台请求,后台先从缓存中取数据,取到直接返回结果,取不到时从数据库中取,数据库取到更新缓存,并返回结果,数据库也没取到,那直接返回空结果:

耗时比较大的往往有两个地方:

1、查数据库;

2、调用其它服务的API(因为其它服务最终也要去做查数据库等耗时操作);

重复查询也有两种:

1、我们在应用程序中代码写得不好,写的for循环,可能每次循环都用重复的参数去查询了。

2、大量的相同或相似请求造成的。比如资讯网站首页的文章列表、电商网站首页的商品列表、微博等社交媒体热搜的文章等等,当大量的用户都去请求同样的接口,同样的数据,如果每次都去查数据库,那对数据库来说是一个不可承受的压力。所以我们通常会把高频的查询进行缓存,我们称它为“热点”。

二.SpringCache概述

使用Spring Cache的好处:

  • 提供基本的Cache抽象,方便切换各种底层Cache;
  • 通过注解Cache可以实现类似于事务一样,缓存逻辑透明的应用到我们的业务代码上,且只需要更少的代码就可以完成;
  • 提供事务回滚时也自动回滚缓存;
  • 支持比较复杂的缓存逻辑;

Spring Cache就是一个缓存框架。它利用了AOP(将缓存逻辑与服务逻辑解耦),实现了基于注解的缓存功能(声明式缓存),并且进行了合理的抽象,业务代码不用关心底层是使用了什么缓存框架,只需要简单地加一个注解,就能实现缓存功能了。而且Spring Cache也提供了很多默认的配置,用户可以快速将缓存集成到项目中;

三.如何导入SpringCache框架

第一步.导入依赖

我这里底层的缓存选择的是redis,所以redis的缓存也要导入进来

  1. <!--spring整合cache的场景依赖-->
  2. <dependency>
  3. <groupId>org.springframework.boot</groupId>
  4. <artifactId>spring-boot-starter-cache</artifactId>
  5. </dependency>
  6. <dependency>
  7. <groupId>org.springframework.boot</groupId>
  8. <artifactId>spring-boot-starter-data-redis</artifactId>
  9. </dependency>

第二步.在application编写配置信息

  1. #端口
  2. server:
  3. port: 8888
  4. #数据库配置
  5. spring:
  6. redis:
  7. host: 192.168.230.100 # Redis服务器地址
  8. database: 0 # Redis数据库索引(默认为0)
  9. port: 6379 # Redis服务器连接端口
  10. # password: ld123456 # Redis服务器连接密码(默认为空)
  11. datasource:
  12. url: jdbc:mysql://192.168.230.100:3306/tmp_db?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=UTC
  13. username: root
  14. password: 1234
  15. driver-class-name: com.mysql.jdbc.Driver
  16. #打印日志
  17. logging:
  18. level:
  19. com.donleo.cache.mapper: debug
  20. mybatis:
  21. mapper-locations: classpath:mappers/*.xml
  22. type-aliases-package: com.itheima.cache.model
  23. configuration:
  24. map-underscore-to-camel-case: true
  25. log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

第三步.缓存配置类定义

SpringCache抽象出公共的缓存接口,同时面向用户屏蔽了底层实现细节,用户可通过配置缓存管理器来实现缓存方案的替换:

可以看到CacheManager集成了大多数缓存的接口

当前以Redis作为SpringCache缓存底层实现为例

  1. /**
  2. * @author hhh
  3. * code 自定义redis序列化配置类
  4. */
  5. @Configuration
  6. //开启Springcaching的支持,底层自动识别相关springCache的注解
  7. @EnableCaching
  8. public class RedisCacheConfig {
  9. /**
  10. * 配置 cacheManager 代替默认的cacheManager (缓存管理器)
  11. * 当前使用的redis缓存做为底层实现,如果将来想替换缓存方案,那么只需调整CacheManager的实现细节即可
  12. * 其他代码无需改动
  13. * @param factory RedisConnectionFactory
  14. * @return CacheManager
  15. */
  16. @Bean
  17. public CacheManager cacheManager(RedisConnectionFactory factory) {
  18. //定义redis数据序列化的对象
  19. RedisSerializer<String> redisSerializer = new StringRedisSerializer();
  20. //jackson序列化方式对象
  21. Jackson2JsonRedisSerializer serializer = new Jackson2JsonRedisSerializer(Object.class);
  22. ObjectMapper objectMapper = new ObjectMapper();
  23. //设置被序列化的对象的属性都可访问:暴力反射
  24. objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
  25. //仅仅序列化对象的属性,且属性不可为final修饰
  26. objectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance,ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
  27. serializer.setObjectMapper(objectMapper);
  28. // 配置key value序列化
  29. RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
  30. .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer))
  31. .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(serializer))
  32. //关闭控制存储--》禁止缓存value为null的数据
  33. .disableCachingNullValues()
  34. //修改前缀与key的间隔符号,默认是:: eg:name:findById
  35. .computePrefixWith(cacheName->cacheName+":");
  36. //设置特有的Redis配置
  37. Map<String, RedisCacheConfiguration> cacheConfigurations = new HashMap<>();
  38. //定制化的Cache 设置过期时间 eg:以role:开头的缓存存活时间为10s
  39. // cacheConfigurations.put("role",customRedisCacheConfiguration(config,Duration.ofSeconds(20)));
  40. cacheConfigurations.put("stock",customRedisCacheConfiguration(config,Duration.ofSeconds(3000)));
  41. cacheConfigurations.put("market",customRedisCacheConfiguration(config,Duration.ofSeconds(300)));
  42. //设置一role开头的缓存存活周期为30s
  43. cacheConfigurations.put("role",customRedisCacheConfiguration(config,Duration.ofSeconds(30)));
  44. //构建redis缓存管理器
  45. RedisCacheManager cacheManager = RedisCacheManager.builder(factory)
  46. //Cache事务支持,保证reids下的缓存与数据库下的数据一致性
  47. .transactionAware()
  48. .withInitialCacheConfigurations(cacheConfigurations)
  49. .cacheDefaults(config)
  50. .build();
  51. //设置过期时间
  52. return cacheManager;
  53. }
  54. /**
  55. * 设置RedisConfiguration配置
  56. * @param config
  57. * @param ttl
  58. * @return
  59. */
  60. public RedisCacheConfiguration customRedisCacheConfiguration(RedisCacheConfiguration config, Duration ttl) {
  61. //设置缓存缺省超时时间
  62. return config.entryTtl(ttl);
  63. }
  64. }

四.SpringCache注解的使用

第一个 @Cacheable

==如果缓存中没有:查询数据库,存储缓存,返回结果,==

==如果缓存中有:直接返回结果==

作用:可以用来进行缓存的写入,将结果存储在缓存中,以便于在后续调用的时候可以直接返回缓存中的值,而不必再执行实际的方法。 最简单的使用方式,注解名称=缓存名称,使用例子如下:

使用:

在一个方法上使用注解@Cacheable

属性:cacheNames(value)->存入redis缓存中的key值的前缀,

  1. key->指定要存入redis缓存中的key,**使用#id表示引用方法参数的id的值作为key值**

value为该方法的返回值

  1. @Cacheable(cacheNames = "role",key = "#id")
  2. @Override
  3. public Role findById(Integer id) {
  4. return roleMapper.selectByPrimaryKey(id);
  5. }

我们会发现如果在每个方法的方面都加上cacheNames来表示key值的前缀十分冗余,所以我们可以在类上使用@CacheConfig(cacheNames = "role")//提取缓存的前缀配置

这样一来这个类下的每个方法存入redis时都会有role前缀

  1. @Service
  2. @CacheConfig(cacheNames = "role")
  3. public class RoleServiceImpl implements IRoleService {
  4. @Autowired
  5. private RoleMapper roleMapper;
  6. @Cacheable(cacheNames = "role",key = "#id")
  7. @Override
  8. public Role findById(Integer id) {
  9. return roleMapper.selectByPrimaryKey(id);
  10. }
  11. }
测试:

第一遍会去数据库中加载数据,并存入redis缓存中

  1. @Test
  2. public void test3(){
  3. Role role = roleService.findById(8);
  4. System.out.println(role);
  5. }

** 第二次:直接根据key从redis从获取数据**

第二个 @CacheEvict

删除数据库数据的同时,还对缓存的数据进行删除

@CacheEvict:删除缓存的注解,这对删除旧的数据和无用的数据是非常有用的。这里还多了一个参数(allEntries),设置allEntries=true时,可以对整个条目进行批量删除

使用
  1. @Override
  2. @CacheEvict(key="#id")//根据id为key去redis缓存中删除数据
  3. public Integer delete(Integer id) {
  4. return roleMapper.deleteByPrimaryKey(id);
  5. }

第三个 @CachePut注解

@CachePut:当需要更新缓存而不干扰方法的运行时 ,可以使用该注解。也就是说,始终执行该方法,并将结果放入缓存

本质上说,如果存在对应的缓存,则更新覆盖(先删除原来key相同的缓存,再添加),不存在则添加;

使用

  1. @Override
  2. @CachePut(key="#role.id") //使用对象的成员变量id作为key值
  3. public Role update(Role role) {
  4. roleMapper.updateByPrimaryKey(role);
  5. return role;
  6. }

会使用id=8为key值,然后然后类上使用的注解@CacheConfig(cacheNames="role")role前缀

  1. @Test
  2. public void testUpdate(){
  3. Role role = Role.builder().id(8).rolecode("080").rolename("008Role").introduce("008Introduce").build();
  4. Role update = roleService.update(role);
  5. System.out.println(role);
  6. }

** **

第四个 @Caching注释

​ 在使用缓存的时候,有可能会同时进行更新和删除,会出现同时使用多个注解的情况.而@Caching可以实现,对于复杂的缓存策略,我们可借助SpEL实现;

使用

  1. //执行这个添加方法的时候,向redis中添加三个key,value为返回值,并删除key为8的值
  2. @Caching(
  3. cacheable = @Cacheable(key="#role.rolename"),
  4. put = {@CachePut(key="#role.id"),@CachePut(key="#role.rolecode")},
  5. evict = @CacheEvict(key="8")
  6. )
  7. @Override
  8. public R add(Role role) {
  9. try {
  10. roleMapper.insert(role);
  11. } catch (Exception e) {
  12. return R.error();
  13. }
  14. return R.ok(role.getId());
  15. }
  1. @Test
  2. public void testCaching(){
  3. Role role = Role.builder().id(15).rolecode("015").rolename("015Role").introduce("015Introduce").build();
  4. roleService.add(role);
  5. }

五.注解小结

对于缓存声明,spring的缓存提供了一组java注解:

  • @Cacheable - 功能:触发缓存写入,如果缓存中没有,查询数据库,存储缓存,返回结果,如果缓存中有,直接返回结果- 应用:查询数据库方法,且查询的数据时热点数据
  • @CacheEvict - 功能:触发缓存清除- 应用:删除或修改数据库方法
  • @CachePut - 功能:缓存写入(不会影响到方法的运行)。有则更新,无则添加,直接操作缓存,跟数据库没关系- 应用:新增到数据库方法
  • @Caching - 功能:重新组合要应用于方法的多个缓存操作- 应用:上面的注解的组合使用
  • @CacheConfig(cacheNames = "xxx") - 功能:可以提取公共的缓存key的前缀,一般是业务的前缀- 应用:作用在类之上

缓存层

选择Face的理由:

  • controller层功能过于粗狂、组装数据返回前端,不易缓存的维护;
  • service的功能过于细腻,切关联甚广;
  • 使用face处理缓存等一些特殊场景,与开发服务逻辑隔离,方便维护;

项目集成SpringCache

一.导入依赖

  1. <!--不要将缓存放在中间common层,因为如果引用common的第三方不适用缓存,会导致因为场景依赖自动装配的机制导致启动失败-->
  2. <dependency>
  3. <groupId>org.springframework.boot</groupId>
  4. <artifactId>spring-boot-starter-cache</artifactId>
  5. </dependency>
  6. <!--引入redis的starter依赖-->
  7. <dependency>
  8. <groupId>org.springframework.boot</groupId>
  9. <artifactId>spring-boot-starter-data-redis</artifactId>
  10. </dependency>
  11. <!-- redis创建连接池,默认不会创建连接池 -->
  12. <dependency>
  13. <groupId>org.apache.commons</groupId>
  14. <artifactId>commons-pool2</artifactId>
  15. </dependency>

二.编写配置文件

  1. spring:
  2. # 配置缓存
  3. redis:
  4. host: 192.168.230.100
  5. port: 6379
  6. database: 0 #Redis数据库索引(默认为0)
  7. lettuce:
  8. pool:
  9. max-active: 8 # 连接池最大连接数(使用负值表示没有限制)
  10. max-wait: -1ms # 连接池最大阻塞等待时间(使用负值表示没有限制)
  11. max-idle: 8 # 连接池中的最大空闲连接
  12. min-idle: 1 # 连接池中的最小空闲连接
  13. timeout: PT10S # 连接超时时间

三.编写配置类

  1. package com.hhh.stock.config;
  2. import com.fasterxml.jackson.annotation.JsonAutoDetect;
  3. import com.fasterxml.jackson.annotation.JsonTypeInfo;
  4. import com.fasterxml.jackson.annotation.PropertyAccessor;
  5. import com.fasterxml.jackson.databind.ObjectMapper;
  6. import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
  7. import org.springframework.cache.CacheManager;
  8. import org.springframework.cache.annotation.EnableCaching;
  9. import org.springframework.context.annotation.Bean;
  10. import org.springframework.context.annotation.Configuration;
  11. import org.springframework.data.redis.cache.RedisCacheConfiguration;
  12. import org.springframework.data.redis.cache.RedisCacheManager;
  13. import org.springframework.data.redis.connection.RedisConnectionFactory;
  14. import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
  15. import org.springframework.data.redis.serializer.RedisSerializationContext;
  16. import org.springframework.data.redis.serializer.RedisSerializer;
  17. import org.springframework.data.redis.serializer.StringRedisSerializer;
  18. import java.time.Duration;
  19. import java.util.HashMap;
  20. import java.util.Map;
  21. /**
  22. * @author hhh
  23. * code 自定义redis序列化配置类
  24. */
  25. @Configuration
  26. //开启Springcaching的支持,底层自动识别相关springCache的注解
  27. @EnableCaching
  28. public class CacheConfig {
  29. /**
  30. * 配置 cacheManager 代替默认的cacheManager (缓存管理器)
  31. * 当前使用的redis缓存做为底层实现,如果将来想替换缓存方案,那么只需调整CacheManager的实现细节即可
  32. * 其他代码无需改动
  33. * @param factory RedisConnectionFactory
  34. * @return CacheManager
  35. */
  36. @Bean
  37. public CacheManager cacheManager(RedisConnectionFactory factory) {
  38. //定义redis数据序列化的对象
  39. RedisSerializer<String> redisSerializer = new StringRedisSerializer();
  40. //jackson序列化方式对象
  41. Jackson2JsonRedisSerializer serializer = new Jackson2JsonRedisSerializer(Object.class);
  42. ObjectMapper objectMapper = new ObjectMapper();
  43. //设置被序列化的对象的属性都可访问:暴力反射
  44. objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
  45. //仅仅序列化对象的属性,且属性不可为final修饰
  46. objectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance,ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
  47. serializer.setObjectMapper(objectMapper);
  48. // 配置key value序列化
  49. RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
  50. .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer))
  51. .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(serializer))
  52. //关闭控制存储--》禁止缓存value为null的数据
  53. .disableCachingNullValues()
  54. //修改前缀与key的间隔符号,默认是:: eg:name:findById
  55. .computePrefixWith(cacheName->cacheName+":");
  56. //设置特有的Redis配置
  57. Map<String, RedisCacheConfiguration> cacheConfigurations = new HashMap<>();
  58. //定制化的Cache 设置过期时间 eg:以role:开头的缓存存活时间为10s
  59. //cacheConfigurations.put("role",customRedisCacheConfiguration(config,Duration.ofSeconds(20)));
  60. cacheConfigurations.put("stock",customRedisCacheConfiguration(config,Duration.ofSeconds(3000)));
  61. cacheConfigurations.put("market",customRedisCacheConfiguration(config,Duration.ofSeconds(300)));
  62. //设置一role开头的缓存存活周期为30s
  63. //cacheConfigurations.put("role",customRedisCacheConfiguration(config,Duration.ofSeconds(300)));
  64. //构建redis缓存管理器
  65. RedisCacheManager cacheManager = RedisCacheManager.builder(factory)
  66. //Cache事务支持,保证reids下的缓存与数据库下的数据一致性
  67. .transactionAware()
  68. .withInitialCacheConfigurations(cacheConfigurations)
  69. .cacheDefaults(config)
  70. .build();
  71. //设置过期时间
  72. return cacheManager;
  73. }
  74. /**
  75. * 设置RedisConfiguration配置
  76. * @param config
  77. * @param ttl
  78. * @return
  79. */
  80. public RedisCacheConfiguration customRedisCacheConfiguration(RedisCacheConfiguration config, Duration ttl) {
  81. //设置缓存缺省超时时间
  82. return config.entryTtl(ttl);
  83. }
  84. }

四.编写缓存层

向数据库查到数据的同时并存入缓存中,下一次查询时,如果缓存中有对应的key时,直接获取缓存中的数据

  1. @Component("stockCacheFace")
  2. public class StockCacheFaceImpl implements StockCacheFace {
  3. @Autowired
  4. private StockBusinessMapper stockBusinessMapper;
  5. /**
  6. * 获取所有股票编码,并添加上证或者深证的股票前缀编号:sh sz
  7. */
  8. @Override
  9. @Cacheable(cacheNames = "stock",key = "'stockCodes'")//常量要使用单引号,不然会报错
  10. public List<String> getAllStockCodeWithPredix() {
  11. //获取所有的A股编码信息
  12. List<String>allCodes=stockBusinessMapper.getAllStockCode();
  13. //http://hq.sinajs.cn/list=sh601003,sh601001,sz000019
  14. //TODO:给取出的编码加上前缀,6开头加sh,0开头加sz
  15. allCodes = allCodes.stream().map(code -> code.startsWith("6") ? "sh" + code : "sz" + code).collect(Collectors.toList());
  16. return allCodes;
  17. }
  18. }

五.使用

  1. @Autowired
  2. private StockCacheFace stockCacheFace;
  3. @Override
  4. public void getStockRtInfo() {
  5. //获取所有的A股编码信息
  6. /* List<String>allCodes=stockBusinessMapper.getAllStockCode();
  7. //http://hq.sinajs.cn/list=sh601003,sh601001,sz000019
  8. //TODO:给取出的编码加上前缀,6开头加sh,0开头加sz
  9. allCodes = allCodes.stream().map(code -> code.startsWith("6") ? "sh" + code : "sz" + code).collect(Collectors.toList());*/
  10. List<String>allCodes=stockCacheFace.getAllStockCodeWithPredix();
  11. }

成功存入缓存


本文转载自: https://blog.csdn.net/luosuss/article/details/142663029
版权归原作者 落落落sss 所有, 如有侵权,请联系我们删除。

“SpringCache”的评论:

还没有评论