SpringBoot 缓存使用及整合redis作为缓存

JSR-107

什么是JSR-107
要回答这个问题,首先要知道JSR是什么,JSR是Java Specification Requests 的缩写 ,Java规范请求,故名思议提交Java规范,大家一同遵守这个规范的话,会让大家‘沟通’起来更加轻松。 规范是很重要的 ,举个例子大家都知道红灯停,路灯行吧,如果每个城市的信号灯代表不一样,那就麻烦了,B城市红灯行,绿灯停,C城市甚至出现紫灯行,闪灯行,想想都知道,如果我们保证不出问题,必须知道每个城市的信号等代表的意义。我们一直使用的JDBC就一个访问数据库的一个规范的例子。 JSR-107呢就是关于如何使用缓存的规范。

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

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

看下面这张图你就能更清楚的明白他们之间的对应关系:
在这里插入图片描述
在pom.xml中导入我们的标准的规范依赖:

        <dependency>
            <groupId>javax.cache</groupId>
            <artifactId>cache-api</artifactId>
        </dependency>

在这里插入图片描述

spring缓存抽象

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

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

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

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

@Cacheable/@CachePut/@CacheEvict 主要的参数

参数名说明举例
value缓存的名称,在 spring 配置文件中定义,必须指定 至少一个例如: @Cacheable(value=”mycache”) 或者 @Cacheable(value={”cache1”,”cache2”}
key缓存的 key,可以为空,如果指定要按照 SpEL 表达式编写,如果不指定,则缺省按照方法的所有参数进行组合例如:@Cacheable(value=”testcache”,key=”#userName”)
condition缓存的条件,可以为空,使用 SpEL 编写,返回 true 或者 false,只有为 true 才进行缓存/清除缓存,在调用方法之前之后都能判断例如:@Cacheable(value=”testcache”,condition=”#userNam e.length()>2”)
allEntries (@CacheEvict )是否清空所有缓存内容,缺省为 false,如果指定为 true,则方法调用后将立即清空所有缓存例如:@CachEvict(value=”testcache”,allEntries=true)
beforeInvocation(@CacheEvict)是否在方法执行前就清空,缺省为 false,如果指定为 true,则在方法还没有执行的时候就清空缓存, 缺省情况下,如果方法执行抛出异常,则不会清空缓存例如: @CachEvict(value=”testcache”, beforeInvocation=true)
unless(@CachePut) (@Cacheable)用于否决缓存的,不像condition,该表达式只在方法执行之后判断,此时可以拿到返回值result进行判断。条件为true不会缓存,fasle才缓存例如:@Cacheable(value=”testcache”,unless=”#result == null”)

Cache SpEL available metadata 下图为在使用缓存注解的能用的表达式

名字位置描述示例
methodNameroot object当前被调用的方法名#root.methodName
methodroot object当前被调用的方法#root.method.name
targetroot object当前被调用的目标对象#root.target
targetClassroot object当前被调用的目标对象类#root.targetClass
argsroot object当前被调用的方法的参数列表#root.args[0]
cachesroot object当前方法调用使用的缓存列表(如@Cacheable(value={“cache1”,“cache2”})),则有两个cache#root.caches[0].name
argument nameevaluation context方法参数的名字. 可以直接 #参数名 ,也可以使用 #p0或#a0 的 形式,0代表参数的索引#iban 、 #a0 、 #p0 ;
resultevaluation context方法执行后的返回值(仅当方法执行之后的判断有效,如‘unless’,’cache put’的表达式 ’cache evict’的表达式beforeInvocation=false)#result

springboot 缓存注解式使用

搭建环境

记得,想要使用缓存,要在主配置类中加上@EnableCaching//缓存注解
在这里插入图片描述
pom.xml

        <!-- 引入spring-boot-starter-cache模块-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-cache</artifactId>
        </dependency>

这是用于演示的实体类

public class Book implements Serializable {
    private Integer bookId;
    private String bookName;
    private Float price;
	//此处省略 get/set ......
}

@Cacheable

@Cacheable 将方法的运行结果进行缓存;以后再要相同的数据,直接从缓存中获取,不用调用方法;
CacheManager管理多个Cache组件的,对缓存对真正CRUD操作在Cache组件中,每一个缓存组件有自己唯一一个名字

@Cacheable 几个属性

属性名说明
cacheNames/value指定缓存组件的名字;将方法的返回结果放在哪个缓存中,是数组的形式可以指定多个
key缓存数据使用的key,可以有它指定,默认是方法参数的值。将方法的运行结果进行缓存,再要相同的数据直接从缓存中获取,
keyGeneratorkey的生成器;可以自己指定key的生成器的组件id
注: keyGenerator/key:二选一使用
cacheManager指定缓存管理器 或者cacheResolver指定获取解析器
condition指定符合条件的情况下缓存
unless否定缓存,当unless指定的条件为true,方法的返回值就不会缓存;可以获取到结果进行判断
sync是否使用异步 使用这个属性的话就不支持使用unless

这里Mybatis 的代码就直接省略了,直接看这个更具id查单个的方法吧:

    /**
     * 几个属性:
     * cacheName/value:指定缓存组件的名字;将方法的返回结果放在哪个缓存中,是数组的形式
     * 可以指定多个
     * key:缓存数据使用的key,可以有它指定,默认是方法参数的值
     * //将方法的运行结果进行缓存,再要相同的数据直接从缓存中获取,
     *bookId;参数bookId的值 当然你也可以这么搞:  #a0   #p0     #root.args[0]
     * keyGenerator:key的生成器,可以自己指定
     *  key/keyRenerator:二选一
     * cacheManger:指定缓存管理器  或者cacheResolver指定获取解析器
     * condition:指定符合条件的情况下缓存
     * ,condition = "#id>0"
     * unless:否定缓存,当unless指定的条件为true,方法的返回值就不会缓存;可以获取到结果进行判断
     * ,unless = "#result==null"
     * unless ="#a0==2" 如果第一个参数的结果为2,不缓存
     * sync:是否使用异步  使用这个属性的话就不支持使用unless
     */
    @Override
    @Cacheable(value = "book",keyGenerator="myKeyGenerator",unless ="#a0==2")
    public Book selectByPrimaryKey(Integer bookId) {
        return bookMapper.selectByPrimaryKey(bookId);
    }

自定义KeyGenerator:

/**
 * @Description: 自定义KeyGenerator,默认是使用方法参数+返回值
 * @Author: cpc
 * @Date: 2019-11-12 23:23
 * @Version: V1.0
 */
@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() +"]";
            }
        };
    }

}

说明一下:上面这种方式需要制定使用这个KeyGenerator,如果想默认使用的就是这个策略的话可以照下面这种方式来玩(就不需要制定了):

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

测试一波:

    @Test
    public void selectByPrimaryKey() {
        Book book = bookService.selectByPrimaryKey(19);
        System.out.println(book);
        System.out.println("---------------------------------------------");
        Book book2 = bookService.selectByPrimaryKey(19);
        System.out.println(book2);
    }

在这里插入图片描述

@CachePut

既调用方法,又更新缓存数据:同步更新
修改数据库中的某个数据,同时更新缓存
value/cacheNames要和查询缓存中的一致才能达到效果
运行时机:

  • 1.先调用目标方法
  • 2.将目标方法的结果缓存起来
  • key="#employee.id"为修改的id
  • key="#result.id"为修改后的id
    @CachePut的key是不能用 #result.id
    只要这个key和之前上面那个查询的缓存key一致的话就会更新
    @CachePut(value = "book",key = "#book.bookId")
    public  Book updateBook(Book book){
        System.out.println("update:"+book);
        bookMapper.update(book);
        return book;
    }

@CacheEvict

清除缓存

参数名说明
key指定清除的数据
allEntries是否删除缓存中所有数据 默认为false
beforInvocation缓存的清除是否在方法之前执行 默认为false
@CacheEvict(value = "book",key = "#bookId")
public void deleteBook(Integer bookId){
    System.out.println("删除"+bookId);
  	bookMapper.deleteBook(id);
}

@Caching

定义复杂的缓存规则

    @Caching(
        cacheable = {
            @Cacheable(value = "book",key="#bookName")
        },
        put = {
            @CachePut(value = "book",key="#result.bookId")
        }
    )
    public Book getBookByName(String bookName){
        return bookMapper.getBookByName(bookName);
    }

@CacheConfig

抽取公共配置

@CacheConfig(cacheNames="book") 
@Service 
public class BookService {

整合redis作为缓存

缓存注解都一样,没什么变化

整合部分代码

pom.xml

<!--        springboot 整合redis -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
        </dependency>

添加配置:

spring:
  redis:
    database: 0
    host: 192.168.43.170
    port: 6379
    password: root
    jedis:
      pool:
        max-active: 100
        max-idle: 3
        max-wait: -1
        min-idle: 0
    timeout: 1000

写RedisConfig配置类,来配置我们的redis:
这个配置类的注释已经详细说明各项配置是干嘛的啦,我就不多废话了哈

package com.cpc.springboot01.conf;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.interceptor.KeyGenerator;
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.core.StringRedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

import java.lang.reflect.Method;
import java.time.Duration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;



/**
 * redis配置类
 **/
@Configuration
@EnableCaching//开启注解式缓存
//继承CachingConfigurerSupport,为了自定义生成KEY的策略。可以不继承。
public class RedisConfig extends CachingConfigurerSupport {

    /**
     * 生成key的策略 根据类名+方法名+所有参数的值生成唯一的一个key
     *
     * @return
     */
    @Bean
    @Override
    public KeyGenerator keyGenerator() {
        return new KeyGenerator() {
            @Override
            public Object generate(Object target, Method method, Object... params) {
                StringBuilder sb = new StringBuilder();
                sb.append(target.getClass().getName());
                sb.append(method.getName());
                for (Object obj : params) {
                    sb.append(obj.toString());
                }
                return sb.toString();
            }
        };
    }

    /**
     * 管理缓存
     *
     * @param redisConnectionFactory
     * @return
     */
    @Bean
    public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
        //通过Spring提供的RedisCacheConfiguration类,构造一个自己的redis配置类,从该配置类中可以设置一些初始化的缓存命名空间
        // 及对应的默认过期时间等属性,再利用RedisCacheManager中的builder.build()的方式生成cacheManager:
        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig();  // 生成一个默认配置,通过config对象即可对缓存进行自定义配置
        config = config.entryTtl(Duration.ofMinutes(1))     // 设置缓存的默认过期时间,也是使用Duration设置
                .disableCachingNullValues();     // 不缓存空值

        // 设置一个初始化的缓存空间set集合
        Set<String> cacheNames = new HashSet<>();
        cacheNames.add("my-redis-cache1");
        cacheNames.add("my-redis-cache2");

        // 对每个缓存空间应用不同的配置
        Map<String, RedisCacheConfiguration> configMap = new HashMap<>();
        configMap.put("my-redis-cache1", config);
        configMap.put("my-redis-cache2", config.entryTtl(Duration.ofSeconds(120)));

        RedisCacheManager cacheManager = RedisCacheManager.builder(redisConnectionFactory)     // 使用自定义的缓存配置初始化一个cacheManager
                .initialCacheNames(cacheNames)  // 注意这两句的调用顺序,一定要先调用该方法设置初始化的缓存名,再初始化相关的配置
                .withInitialCacheConfigurations(configMap)
                .build();
        return cacheManager;
    }

    @Bean
    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
        RedisTemplate<Object, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(connectionFactory);

        //使用Jackson2JsonRedisSerializer来序列化和反序列化redis的value值(默认使用JDK的序列化方式)
        Jackson2JsonRedisSerializer serializer = new Jackson2JsonRedisSerializer(Object.class);

        ObjectMapper mapper = new ObjectMapper();
        mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        serializer.setObjectMapper(mapper);

        template.setValueSerializer(serializer);
        //使用StringRedisSerializer来序列化和反序列化redis的key值
        template.setKeySerializer(new StringRedisSerializer());
        template.afterPropertiesSet();
        return template;
    }

    @Bean
    public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory factory) {
        StringRedisTemplate stringRedisTemplate = new StringRedisTemplate();
        stringRedisTemplate.setConnectionFactory(factory);
        return stringRedisTemplate;
    }
}

测试


    /**
     * 使用 my-redis-cache1 这个槽,key为 book +  bookId,当参数bookId 大于 10的时候才进行缓存
     * @param bookId
     * @return
     */
    @Override
    @Cacheable(value = "my-redis-cache1",key = "'book'+#bookId",condition = "#bookId>10")
    public Book selectByPrimaryKey(Integer bookId) {
        return bookMapper.selectByPrimaryKey(bookId);
    }
    @Test
    public void selectByPrimaryKey() {
        Book book = bookService.selectByPrimaryKey(19);
        System.out.println(book);
        System.out.println("--------------我是分割线--------------------------");
        Book book2 = bookService.selectByPrimaryKey(19);
        System.out.println(book2);
    }

reids中缓存的数据:
在这里插入图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值