SpringBoot与缓存

SpringBoot与缓存

JSR-107

Java Caching定义了5个核心接口, 分别是CachingProvider, CacheManager, Cache, Entry, Expiry

由于操作繁琐不推荐使用(不讲解)

CachingProvider

缓存提供者: 定义了创建, 配置, 获取, 管理和多个CacheManager. 一个应用可以在运行期间访问多个CachingProvider.

CacheManager

缓存管理器: 定义了创建, 配置, 获取, 管理和多个唯一命名的Cache, 这些Cache存在于CacheManager的上下文中, 一个CacheManager仅被一个CachingProvider所拥有.

Cache

缓存: 一个类似Map的数据结构并临时存储以key为索引的值, 一个Cache仅被一个CacheManager所拥有.

Entry

数据: 一个存贮在Cache中的key-value(键值)对.

Expiry

有效期: 每一个存储在Cache中的条目有一个定义的有效期, 一旦超过了这个时间, 条目为过期的状态. 一旦过期, 条目将不可访问, 更新, 和删除. 缓存有效期可以通过ExpiryPolicy设置.


Spring缓存抽象

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

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

Cache接口下Spring提供了各种xxxCache的实现, 如RedisCache, EhCacheCahe, ConcurrentMapCache

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

使用Spring缓存抽象时需要关注两点:

  1. 确定方法需要被缓存以及他们的缓存策略
  2. 从缓存中读取之前缓存存储的数据

缓存注解

注解说明
@Cacheable注意针对方法配置, 能够根据方法的请求参数对其结果进行缓存
@CacheEvict清空缓存
@CachePut保证方法被调用, 又希望结果被缓存(更新缓存)
@EnableCaching开启基于注解的缓存
概念说明
Cache缓存接口, 定义缓存操作. 实现有 RedisCache, EhCacheCahe
CacheManager缓存管理器, 管理各种缓存(Cache)组件
KeyGenerator缓存数据时key生成策略
serialize缓存数据时value序列化策略

使用缓存

选择的场景启动器

cache场景, 在没有引入其它provider时, SpringBoot默认使用ConcurrentMapCache (CacheManager)

开启注解

在启动类中加入@EnableCaching注解

@SpringBootApplication
@MapperScan(basePackages = {"com.study.cache.dao"})
@EnableCaching
public class CacheApplication {

   public static void main(String[] args) {
      SpringApplication.run(CacheApplication.class, args);
   }

}
注解使用
@Cacheable

Service中的方法上添加@Cacheable注解, 将返回值添加到缓存中

@Cacheable属性:

  1. cacheNames/value : 指定缓存组件的名字
  2. key : 缓存数据使用的key, 默认使用方法参数的值, 可使用SpEL表达式
  3. keyGenerator : key的生成器, 可以指定key的生成器的组件id (keykeyGenerator 二选一使用)
  4. cacheManager : 指定缓存管理器, 或指定cacheResolver缓存解析器
  5. condition : 指定符合条件的情况下才缓存, 使用SpEL表达式
  6. unless : 否定缓存, 当条件为true, 则不会被缓存(与condition相反), 可以获取到结果进行判断
  7. sync : 是否使用异步模式
@Service
public class EmployeeServiceImpl implements EmployeeService {

    @Autowired
    private EmployeeDao employeeDao;

    /**
    * @Cacheable
    */
    @Cacheable(cacheNames = {"emp"}, key = "#root.method.name+'['+#id+']'", condition = "#root.method eq 'getEmp' and #id=1", unless = "#result==null")
    @Override
    public Employee getEmp(Integer id) {
        return employeeDao.getEmpById(id);
    }
}

@CachePut

在方法上添加@CachePut注解, 先调用目标方法(不在缓存组件中查询), 然后缓存其目标方法的结果

Key中指定相同的值, 可达到同步更新缓存的作用

/**
* @CachePut
*/
@CachePut(cacheNames = {"emp"}, key = "#result.id")
@Override
public Employee updateEmp(Employee employee) {
    employeeDao.updateEmp(employee);
    return employee;
}

@CacheEvict

在方法上添加@CacheEvict注解, 可清除缓存

可以通过key指定需要清除的数据, 默认为参数值

allEntries属性指定为true时, 表示清空当前缓存组件的所有数据

beforInvocation属性指定为ture时, 表示在方法执行之前清除, 默认为fales(方法执行异常, 缓存也会被清除)

/**
* @CacheEvict
*/
@CacheEvict(cacheNames = {"emp"}, key = "#id")
@Override
public void deleteEmp(Integer id) {
    employeeDao.deleteEmpById(id);
}

@Caching

可通过@Caching注解配置复杂的存贮(多个@Cacheable , @CachePut, @CacheEvict)将多个注解相组合.

/**
* @Caching
*/
@Caching(
        cacheable = {
               @Cacheable(cacheNames = "emp", key = "#lastName")
        },
        put = {
                @CachePut(cacheNames = "emp", key = "#result.id"),
                @CachePut(cacheNames = "emp", key = "#result.email")
        }
)
@Override
public Employee getEmpByLastName(String lastName) {
    return employeeDao.getEmpByLastName(lastName);
}

@CacheConfig

通过@CacheConfig注解可以抽取本类中公共的Cache属性, 如 指定组件, key生成策略 等…

/**
* @CacheConfig
*/
@CacheConfig(cacheNames = "emp")
@Service
public class EmployeeServiceImpl implements EmployeeService {
	...
}

添加keyGenerator

创建配置类, 将返回keyGenerator接口的实现类(此处匿名实现), 将其命名.

需要重写generate()方法, 参数为,目标对象 ,方法 , 参数列表. — 方法返回值为生成key的字符串.

@Configuration
public class MyCacheConfig {

    @Bean("myKeyGenerator")
    public KeyGenerator keyGenerator(){
        return new KeyGenerator() {
            @Override
            public Object generate(Object o, Method method, Object... objects) {
                return method.getName()+"["+ Arrays.asList(objects).toString()+']';
            }
        };
    }
}

@Cacheable注解中使用keyGenerator属性指定自己添加的策略

@Cacheable(cacheNames = {"emp"}, keyGenerator = "myKeyGenerator")
@Override
public Employee getEmp(Integer id) {
    return employeeDao.getEmpById(id);
}

运行流程

@Cacheable标注的方法执行前, 检查缓存中是否有对应的数据

  1. 方法运行之前, 先去查询Cache(缓存组件), 按照cacheNames指定的名字获取 ->CacheManager先获取相应的缓存, 如果是第一次获取缓存(没有Cache组件),CacheManager会将缓存组件自动创建

  2. 使用keyCache组件中查询缓存的内容, 默认为参数名(key是按照策略生成的: 默认使用keyGenerator的实现类SimpleKeyGenerator生成)

  3. 如果没有查到对应key值的缓存数据, 就调用目标方法, 并将目标方法返回的结果放入缓存.

    如果查到对应key值的缓存数据, 就直接返回结果, 就不再调用目标方法

流程核心
  1. 使用CacheManager按照名字得到Cache组件
  2. key使用keyGenerator生成, 默认是SimleKeyGenerator

Cache SpEL available metadata

root为当前目标方法

NameLocationDescribeExamples
methodNameroot object当前被调用的方法名#root.methodName
mehtodroot object当前被调用的方法#root.method.name
targetroot object当前被调用的目标对象#root.target
targetClassroot object当前被调用的目标对象类#root.targetClass
argsroot object当前被调用的方法的参数列表#root.args[0]
cachesroot object当前方法调用使用的缓存列表, 即指定的cacheNames#root.caches[0].name
argumentNameevaluation context方法参数的名字#参数名#p0, #a0 - 0为参数索引
resultevaluation context方法执行后的返回值#result

使用Redis

spring-boot-starter-redis

引入Redis的starter

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

配置Reids

在主配置文件中指定reids服务器主机

#redis配置 Lettuce 是一个可伸缩线程安全的 Redis 客户端,多个线程可以共享同一个 RedisConnection,它利用优秀 netty NIO 框架来高效地管理多个连接
spring.redis.host=192.168.0.172
spring.redis.port=6379
spring.redis.password=
# 连接超时时间(毫秒)
spring.redis.timeout=36000ms
# Redis默认情况下有16个分片,这里配置具体使用的分片,默认是0
spring.redis.database=0
# 连接池最大连接数(使用负值表示没有限制) 默认 8
spring.redis.lettuce.pool.max-active=8
# 连接池最大阻塞等待时间(使用负值表示没有限制) 默认 -1
spring.redis.lettuce.pool.max-wait=-1ms
# 连接池中的最大空闲连接 默认 8
spring.redis.lettuce.pool.max-idle=8
# 连接池中的最小空闲连接 默认 0
spring.redis.lettuce.pool.min-idle=0

基本操作

操作字符串使用StringRedisTemplate

StringRedisTemplate自动注入, 调用ops相关方法

@SpringBootTest
class CacheApplicationTests {
    
   @Autowired
   StringRedisTemplate stringRedisTemplate;

   @Test
   void contextLoads() {
      //操作字符串
      //保存数据
      stringRedisTemplate.opsForValue().append("msg", "hello");
      //读取数据
      String msg = stringRedisTemplate.opsForValue().get("msg");
       
      //操作列表
      stringRedisTemplate.opsForList().leftPush("myList", "1");
      stringRedisTemplate.opsForList().leftPush("myList", "2");
      stringRedisTemplate.opsForList().leftPush("myList", "3");
   }

}

操作对象使用RedisTemplate类, 其对象需要是现实序列化接口Serializable

StringRedisTemplate自动注入, 调用相关方法

@SpringBootTest
class CacheApplicationTests {

   @Autowired
   RedisTemplate<Object, Object> redisTemplate;

   @Test
   void contextLoads() {
      //保存一个Employee对象, 指定key为emp-1
      Employee employee = new Employee(0, "admin", "123@qq.com", 0, 0);
      redisTemplate.opsForValue().set("emp-1", employee);
   }
}

在Redis数据库中得到的结果如图:

在这里插入图片描述

因为对象通过序列化保存到Redis, 所以内容不方便阅读, 我们可以通过自定义序列化器, 将对象转换为Json数据格式进行存储, 方便阅读其内容

自定义序列化器

创建一个Redis的配置类, 将自定义的RedisTemplate注入到容器中

RedisTemplate<T, V>中的泛型V为需要序列化的类

Jackson2JsonRedisSerializer将提供的序列化器设置在RedisTemplate中并返回

@Configuration
public class MyRedisConfig {

    /**
     * 自定义序列化器
     * 将对象序列化为json数据
     * @param redisConnectionFactory redisConnectionFactory
     * @return RedisTemplate
     * @throws UnknownHostException 异常
     */
     @Bean
    public RedisTemplate<Object, Employee> redisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
        RedisTemplate<Object, Employee> template = new RedisTemplate<Object, Employee>();
        template.setConnectionFactory(redisConnectionFactory);
        Jackson2JsonRedisSerializer<Employee> serializer = new Jackson2JsonRedisSerializer<Employee>(Employee.class);
        template.setDefaultSerializer(serializer);
        return template;
    }
}

在使用时自动注入的RedisTemplate<T, V>就成为了我们自己定义的template

@SpringBootTest
class CacheApplicationTests {

    /**
    * 最好加上配置的泛型
    */
   @Autowired
   RedisTemplate<Object, Employee> redisTemplate;

   @Test
   void contextLoads() {
      Employee employee = new Employee(0, "admin", "123@qq.com", 0, 0);
      redisTemplate.opsForValue().set("emp-1", employee);
   }
}

在Redis数据库中保存的结果即为Json数据, 方便程序员阅读

在这里插入图片描述


自定义CacheManager

需要导入依赖坐标

<dependency>
   <groupId>com.fasterxml.jackson.datatype</groupId>
   <artifactId>jackson-datatype-jsr310</artifactId>
</dependency>

实现自定义CacheManager

/**
 * 自定义RedisCacheManager
 * @param redisConnectionFactory redisConnectionFactory
 * @return CacheManager
 */
@Bean
public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
    //初始化一个RedisCacheWriter
    RedisCacheWriter redisCacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory);
    //设置CacheManager的值序列化方式为json序列化
    RedisSerializer<Object> jsonSerializer = new GenericJackson2JsonRedisSerializer();
    RedisSerializationContext.SerializationPair<Object> pair = RedisSerializationContext.SerializationPair.fromSerializer(jsonSerializer);
    RedisCacheConfiguration defaultCacheConfig=RedisCacheConfiguration.defaultCacheConfig().serializeValuesWith(pair);
    //设置默认超过期时间是30秒
    defaultCacheConfig.entryTtl(Duration.ofSeconds(30));
    //初始化RedisCacheManager
    return new RedisCacheManager(redisCacheWriter, defaultCacheConfig);
}

当实现了自定义的CacheManager后,我们就可以使用注解对业务进行开发.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值