spring-cache框架使用笔记

spring-cache框架使用笔记

什么是spring-cache框架

spring-cache是spring框架中的一个缓存抽象层,
它提供了一种简便的方式来集成不同的底层缓存实现,
如内存缓存(concurrentMap/ehcache/caffeine)/分布式缓存(redis/couchbase)等
它简化了在app中使用缓存的逻辑,并提供了一组注解和API来实现缓存功能

springCache的特点和功能

1.声明性缓存
spring-cache通过注解的方式,允许开发者在方法级别上声明方法的结果要被缓存。
相关注解有@Cacheable 读取缓存+不存在则缓存
@CacheEvict 清除缓存
@CachePut 强制缓存
@Caching 复合功能注解==(@Cacheable+@CacheEvict+@CachePut)
2.缓存的透明性
spring-cache提供了一致的编程接口,
无论底层使用哪种缓存技术,开发者都可以使用相同的方式访问和管理缓存。
这可以使app轻松切换/替换不同的缓存实现技术,而无需更改业务代码

3.注解定义缓存策略
可以使用注解@Cacheable/@CacheEvict/@CachePut 声明缓存的行为和策略
如声明缓存名称,缓存key
@Cacheable(cacheNames = "user", key = "#id")
会按照key=user::id缓存数据 (可以参见spring-cache-redis缓存效果)

4.支持SpEL表达式
使用SpEL表达式,可以定义缓存的键、条件等。
这允许开发者根据方法参数、方法返回值等动态生成缓存键或决定是否应用缓存。
@Cacheable(cacheNames = "user", key = "#id",condition = "#id != null ")
    public UserDO getById(Long id) {
        returnuserRepo.findById(id).orElse(null);
    }
这个案例只有当id不为空时,才进行缓存
condition属性可以设置缓存的条件,如
#id >=100
#userInfo.id >10
#id%2!=0
等等,必须确保condition能正确返回布尔值,才能决定当前方法最终是否进行缓存

springCache+caffeine配合使用

什么是caffeine

caffeine是一种java内存缓存技术,支持多种缓存策略,
caffeine可以单独使用于普通java项目/springboot/springcloud项目,
这里引入caffeine来作为spring-cache存取缓存的数据区。

xml依赖配置

<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-cache</artifactId>
        </dependency>
        <dependency>
            <groupId>com.github.ben-manes.caffeine</groupId>
            <artifactId>caffeine</artifactId>
            <version>2.8.0</version>
        </dependency>

yml配置

需要在application.yml配置spring-cache使用caffeine

spring:
  cache:
    type: caffeine #设置spring-cache框架使用caffeine存取数据  

caffeine配置类

package cn.test.cache;

import com.github.benmanes.caffeine.cache.Caffeine;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.caffeine.CaffeineCacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.concurrent.TimeUnit;

@EnableCaching
@Configuration
public class CacheCfg {
    @Bean
    public CacheManager cacheManager(){
        //定义要缓存的caccheName有哪些
        CaffeineCacheManager caffeineCacheManager =
                new CaffeineCacheManager("user");
        //设置缓存配置
        caffeineCacheManager.setCaffeine(Caffeine.newBuilder()
                //初始容量10
                .initialCapacity(10)
                //最大容量200
                .maximumSize(200)
                //写入过期时间30s
                .expireAfterWrite(30, TimeUnit.SECONDS));
        return caffeineCacheManager;
    }
}

业务代码使用spring-cache相关注解

/**
     * @Cacheable注解标记一个方法时,spring会在执行方法之前,先检查缓存中是否已存在该方法的返回结果
     * 如果存在,则直接返回缓存的结果,不执行方法的实际逻辑
     * 如果不存在,则执行方法并将结果保存到缓存中
     */
    @Cacheable(cacheNames = "user", key = "#id")
    public UserDO getById(Long id) {
        return userRepo.findById(id).orElse(null);
    }

    /**
     * @CacheEvict注解标记一个方法时,spring会在方法执行成功后,
     * 清空指定的缓存项,以确保下次访问时可以重新计算或查询最新的结果
     */
    @CacheEvict(cacheNames = "user", key = "#id")
    public void delUser(Long id) {
        UserDO userDO = userRepo.findById(id).orElse(null);
        if (userDO != null) {
            userRepo.delete(userDO);
        }
    }

    /**
     * @CachePut注解标记一个方法时,spring会在方法执行后,
     * 将返回值放入指定的缓存中,
     * 以便将来的访问可以直接从缓存中获取结果,而不需要再执行方法的实际逻辑
     *
     * @CachePut注解,适用于 创建新缓存 或 强制更新缓存 操作
     */
    @CachePut(cacheNames = "user", key = "#userInfo.id")
    public UserDO addUser(UserDO userInfo) {
        return userRepo.save(userInfo);
    }

    /**
     * 执行updateUserInfo方法时,这里会按key 强制更新caffeine缓存
     */
    @CachePut(cacheNames = "user", key = "#userInfo.id")
    public UserDO updateUserInfo(UserDO userInfo) {
        UserDO userDO = userRepo.findById(userInfo.getId()).orElse(null);
        if (userDO != null) {
            BeanUtils.copyProperties(userInfo,userDO);
            userRepo.save(userDO);
        }
        return userDO;
    }

springCache+redis配合使用

什么是redis

redis是一种分布式缓存技术,不限语言,C/S架构,支持可视化观测,
redis可以单独使用于普通java项目/springboot/springcloud项目/其他语言项目等,
这里引入redis来作为spring-cache框架存取缓存的数据区。

xml依赖配置

 <dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
 <dependency>
     <groupId>org.apache.commons</groupId>
     <artifactId>commons-pool2</artifactId>
     <version>2.5.0</version>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-cache</artifactId>
</dependency>

yml配置

spring:
  redis:
    # ip
    host: localhost
    # 端口6379
    port: 6379
    #密码,没有密码则不配置这一项
    password:
    #指定使用redis 16个库中的哪一个,不配置的话,默认配置为0
    database: 2
    lettuce:
      pool:
        min-idle: 0   #连接池最新空闲时间
        max-wait: -1ms  #最大等待时间
        max-active: 8   #最大活跃时间
        max-idle: 8    #最大空闲时间
      shutdown-timeout: 100ms  #连接池关闭超时时间
    timeout: 1000ms  #redis连接超时时间
  cache:
    type: redis     #设置spring-cache框架使用redis存取数据  

redis配置类

package cn.test.cache;

import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

import java.time.Duration;

@EnableCaching
@Configuration
public class RedisConfig {

    /**
     * 设置RedisTemplate使用的序列化器,
     * 这里使用string作为key的序列化,使用jackson作为value的序列化
     * @param redisConnectionFactory
     * @return
     */
    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(redisConnectionFactory);

        RedisSerializer<String> stringRedisSerializer = new StringRedisSerializer();
        RedisSerializer<Object> jsonSerializer = new GenericJackson2JsonRedisSerializer();

        template.setKeySerializer(stringRedisSerializer);
        template.setValueSerializer(jsonSerializer);

        template.setHashKeySerializer(stringRedisSerializer);
        template.setHashValueSerializer(jsonSerializer);
        return template;
    }


    /**
     * 设置spring-cache使用redis后,要配置cacheManager缓存管理器
     *
     * @param redisConnectionFactory
     * @return
     */
    @Bean
    public RedisCacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
        RedisSerializer<String> stringRedisSerializer = new StringRedisSerializer();
        RedisSerializer<Object> jsonSerializer = new GenericJackson2JsonRedisSerializer();
        RedisCacheConfiguration cacheConfiguration = RedisCacheConfiguration
                .defaultCacheConfig()
                //设置spring-cache缓存到redis的数据有效期是60s
                .entryTtl(Duration.ofSeconds(60))
                //key的序列化使用字符串
                .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(stringRedisSerializer))
                //value的序列化使用jackson
                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jsonSerializer));

        return RedisCacheManager.builder(redisConnectionFactory)
                .cacheDefaults(cacheConfiguration)
                .build();
    }

}

业务代码使用spring-cache相关注解

 @Cacheable(cacheNames = "user", key = "#id")
    public UserDO getById(Long id) {
        return userRepo.findById(id).orElse(null);
    }

    @CacheEvict(cacheNames = "user", key = "#id")
    public void delUser(Long id) {
        UserDO userDO = userRepo.findById(id).orElse(null);
        if (userDO != null) {
            userRepo.delete(userDO);
        }
    }

    @CachePut(cacheNames = "user", key = "#userInfo.id")
    public UserDO addUser(UserDO userInfo) {
        return userRepo.save(userInfo);
    }

    @CachePut(cacheNames = "user", key = "#userInfo.id")
    public UserDO updateUserInfo(UserDO userInfo) {
        UserDO userDO = userRepo.findById(userInfo.getId()).orElse(null);
        if (userDO != null) {
            BeanUtils.copyProperties(userInfo,userDO);
            userRepo.save(userDO);
        }
        return userDO;
    }

使用RDM工具观察redis存储的数据

因为RedisCacheManager配置了使用jackson序列化,这里缓存数据值是以json格式存储到redis的。

可以观察到spring-cache存入redis的缓存有效期是60s (因为RedisCacheManager配置了60秒);

缓存的key是

user::1

缓存的value是

{
  "@class": "cn.test.orm.user.UserDO",
  "id": 1,
  "account": "ewr3",
  "pwd": "23r3r",
  "remark": "23r3r",
  "createTime": [
    "java.sql.Timestamp",
    "2023-08-02 11:14:02"
  ],
  "updateTime": [
    "java.sql.Timestamp",
    "2023-08-02 11:14:02"
  ],
  "isDel": 0
}

在这里插入图片描述

SpringCache+Ehcache配合使用

xml依赖配置

<!--   ehcache缓存依赖     -->
        <dependency>
            <groupId>net.sf.ehcache</groupId>
            <artifactId>ehcache-core</artifactId>
            <version>2.6.11</version>
        </dependency>
   <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-cache</artifactId>
        </dependency>

yml配置

spring:
  cache:
    type: ehcache

Ehcache配置类

package cn.ath.knowwikibackend.cache;

import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.ehcache.EhCacheCacheManager;
import org.springframework.cache.ehcache.EhCacheManagerFactoryBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;

@EnableCaching
@Configuration
public class EhcacheCfg {

    @Bean
    public CacheManager cacheManager(){
        EhCacheManagerFactoryBean factoryBean = new EhCacheManagerFactoryBean();
        factoryBean.setConfigLocation(new ClassPathResource("ehcache.xml"));
        factoryBean.setShared(true);
        return new EhCacheCacheManager(factoryBean.getObject());
    }
}

ehcache.xml配置文件

<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd"
         updateCheck="false">
    <!--
       diskStore:为缓存路径,ehcache分为内存和磁盘两级,此属性定义磁盘的缓存位置。参数解释如下:
       user.home – 用户主目录
       user.dir  – 用户当前工作目录
       java.io.tmpdir – 默认临时文件路径 win7里是 C:\Users\Administrator\AppData\Local\Temp 目录
     -->
    <diskStore path="java.io.tmpdir/Tmp_EhCache"/>
    <!--
       defaultCache:默认缓存策略,当ehcache找不到定义的缓存时,则使用这个缓存策略。只能定义一个。
     -->
    <!--
      name:缓存名称。
      maxElementsInMemory:缓存最大数目
      maxElementsOnDisk:硬盘最大缓存个数。
      eternal:对象是否永久有效,一但设置了,timeout将不起作用。
      overflowToDisk:是否保存到磁盘,当系统当机时
      timeToIdleSeconds:设置对象在失效前的允许闲置时间(单位:秒)。仅当eternal=false对象不是永久有效时使用,可选属性,默认值是0,也就是可闲置时间无穷大。
      timeToLiveSeconds:设置对象在失效前允许存活时间(单位:秒)。最大时间介于创建时间和失效时间之间。仅当eternal=false对象不是永久有效时使用,默认是0.,也就是对象存活时间无穷大。
      diskPersistent:是否缓存虚拟机重启期数据 Whether the disk store persists between restarts of the Virtual Machine. The default value is false.
      diskSpoolBufferSizeMB:这个参数设置DiskStore(磁盘缓存)的缓存区大小。默认是30MB。每个Cache都应该有自己的一个缓冲区。
      diskExpiryThreadIntervalSeconds:磁盘失效线程运行时间间隔,默认是120秒。
      memoryStoreEvictionPolicy:当达到maxElementsInMemory限制时,Ehcache将会根据指定的策略去清理内存。默认策略是LRU(最近最少使用)。你可以设置为FIFO(先进先出)或是LFU(较少使用)。
      clearOnFlush:内存数量最大时是否清除。
      memoryStoreEvictionPolicy:可选策略有:LRU(最近最少使用,默认策略)、FIFO(先进先出)、LFU(最少访问次数)。
      FIFO,first in first out,这个是大家最熟的,先进先出。
      LFU, Less Frequently Used,就是上面例子中使用的策略,直白一点就是讲一直以来最少被使用的。如上面所讲,缓存的元素有一个hit属性,hit值最小的将会被清出缓存。
      LRU,Least Recently Used,最近最少使用的,缓存的元素有一个时间戳,当缓存容量满了,而又需要腾出地方来缓存新的元素的时候,那么现有缓存元素中时间戳离当前时间最远的元素将被清出缓存。
   -->
    <defaultCache
            eternal="false"
            maxElementsInMemory="10000"
            overflowToDisk="true"
            diskPersistent="true"
            timeToIdleSeconds="1800"
            timeToLiveSeconds="259200"
            memoryStoreEvictionPolicy="LRU"/>

    <cache
            name="user"
            eternal="false"
            maxElementsInMemory="5000"
            overflowToDisk="true"
            diskPersistent="true"
            timeToIdleSeconds="300"
            timeToLiveSeconds="800"
            memoryStoreEvictionPolicy="LRU"/>

</ehcache>

被Ehcache缓存的实体类要实现序列化接口

public class UserDO implements Serializable {


    private static final long serialVersionUID = -2035988554861976400L;

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
     @Column(length = 255)
    private String account;

    @Column(length = 255)
    private String pwd;

    @Column(length = 255)
    private String remark;
    
    private Integer isDel=0;
}    

业务代码使用spring-cache相关注解

 @Cacheable(cacheNames = "user", key = "#id")
    public UserDO getById(Long id) {
        return userRepo.findById(id).orElse(null);
    }

    @CacheEvict(cacheNames = "user", key = "#id")
    public void delUser(Long id) {
        UserDO userDO = userRepo.findById(id).orElse(null);
        if (userDO != null) {
            userRepo.delete(userDO);
        }
    }

    @CachePut(cacheNames = "user", key = "#userInfo.id")
    public UserDO addUser(UserDO userInfo) {
        return userRepo.save(userInfo);
    }

    @CachePut(cacheNames = "user", key = "#userInfo.id")
    public UserDO updateUserInfo(UserDO userInfo) {
        UserDO userDO = userRepo.findById(userInfo.getId()).orElse(null);
        if (userDO != null) {
            BeanUtils.copyProperties(userInfo,userDO);
            userRepo.save(userDO);
        }
        return userDO;
    }

观察log日志中的spring-cache效果

先配置打印spring-cache日志

logging:
  # 设置自定义日志组
  group:
    rest: cn.ath.knowwikibackend.rest.test
    syscache: org.springframework.cache
  level:
    # 为对应组设置日志级别
    rest: info
    syscache: trace # spring-cache要设置trace级别才能看到日志

然后观察log日志,发现cache已经命中

2023-08-04 18:51:06,428  TRACE [16028]  [http-nio-8080-exec-4] [e0e8cc29-f027-4325-b68a-62441e54c094] o.s.c.i.CacheInterceptor [CacheAspectSupport.java : 598] Computed cache key '1' for operation Builder[public cn.ath.knowwikibackend.orm.user.UserDO cn.ath.knowwikibackend.biz.UserService.getById(java.lang.Long)] caches=[user] | key='#id' | keyGenerator='' | cacheManager='' | cacheResolver='' | condition='#id != null ' | unless='' | sync='false'
2023-08-04 18:51:06,428  TRACE [16028]  [http-nio-8080-exec-4] [e0e8cc29-f027-4325-b68a-62441e54c094] o.s.c.i.CacheInterceptor [CacheAspectSupport.java : 574] Cache entry for key '1' found in cache 'user'
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ThinkPet

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值