SpringCache整合Redis

Lou.Chen
大约 9 分钟

Spring Cache 整合redis缓存

一、概述

SpringCache本身是一个缓存体系的抽象实现,并没有具体的缓存能力,要使用SpringCache还需要配合具体的缓存实现来完成。

虽然如此,但是SpringCache是所有Spring支持的缓存结构的基础,而且所有的缓存的使用最后都要归结于SpringCache,那么一来,要想使用SpringCache,还是要仔细研究一下的。

SpringCache只是定义的一种规范,所有的实现均有redis实现,即我们需要使用其他的缓存只需要替换引用操作的缓存件即可。

二、缓存注解

SpringCache缓存功能的实现是依靠下面的这几个注解完成的。

  • @EnableCaching:开启缓存功能
  • @Cacheable:定义缓存,用于触发缓存
  • @CachePut:定义更新缓存,触发缓存更新
  • @CacheEvict:定义清除缓存,触发缓存清除
  • @Caching:组合定义多种缓存功能
  • @CacheConfig:定义公共设置,位于class之上
2.1 @EnableCaching
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(CachingConfigurationSelector.class)
public @interface EnableCaching {
    // 用于设置使用哪种代理方式,默认为基于接口的JDK动态代理(false),
    // 设置为true,则使用基于继承的CGLIB动态代理
    boolean proxyTargetClass() default false;
    // 用于设置切面织入方式(设置面向切面编程的实现方式),
    // 默认为使用动态代理的方式织入,当然也可以设置为ASPECTJ的方式来实现AOP
    AdviceMode mode() default AdviceMode.PROXY;
    // 用于设置在一个切点存在多个通知的时候各个通知的执行顺序,默认为最低优先级,
    // 其中数字却大优先级越低,这里默认为最低优先级,int LOWEST_PRECEDENCE =
    // Integer.MAX_VALUE;,却是整数的最大值
    int order() default Ordered.LOWEST_PRECEDENCE;
}
public enum AdviceMode {
    PROXY,
    ASPECTJ
}
public interface Ordered {
    int HIGHEST_PRECEDENCE = Integer.MIN_VALUE;
    int LOWEST_PRECEDENCE = Integer.MAX_VALUE;
    int getOrder();
}
2.2 @Cacheable

​ 该注解用于标注于方法之上用于标识该方法的返回结果需要被缓存起来,标注于类之上标识该类中所有方法均需要将结果缓存起来。

​ 该注解标注的方法每次被调用前都会触发缓存校验,校验指定参数的缓存是否已存在(已发生过相同参数的调用),若存在,直接返回缓存结果,否则执行方法内容,最后将方法执行结果保存到缓存中。

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Cacheable {
    // 用于指定缓存名称,与cacheNames()方法效果一致
    @AliasFor("cacheNames")
    String[] value() default {};
    // 用于指定缓存名称,与value()方法效果一致
    @AliasFor("value")
    String[] cacheNames() default {};
    // 用于使用SPEL手动指定缓存键的组合方式,默认情况使用所有的参数来组合成键,除非自定义了keyGenerator。
    // 使用SPEL表达式可以根据上下文环境来获取到指定的数据:
    // #root.method:用于获取当前方法的Method实例
    // #root.target:用于获取当前方法的target实例
    // #root.caches:用于获取当前方法关联的缓存
    // #root.methodName:用于获取当前方法的名称
    // #root.targetClass:用于获取目标类类型
    // #root.args[1]:获取当前方法的第二个参数,等同于:#p1和#a1和#argumentName
    String key() default "";
    // 自定义键生成器,定义了该方法之后,上面的key方法自动失效,这个键生成器是:
    // org.springframework.cache.interceptor.KeyGenerator,这是一个函数式接口,
    // 只有一个generate方法,我们可以通过自定义的逻辑来实现自定义的key生成策略。
    String keyGenerator() default "";
    // 用于设置自定义的cacheManager(缓存管理器),可以自动生成一个cacheResolver
    // (缓存解析器),这一下面的cacheResolver()方法设置互斥
    String cacheManager() default "";
    // 用于设置一个自定义的缓存解析器
    String cacheResolver() default "";
    // 用于设置执行缓存的条件,如果条件不满足,方法返回的结果就不会被缓存,默认无条件全部缓存。
    // 同样使用SPEL来定义条件,可以使用的获取方式同key方法。
    String condition() default "";
    // 这个用于禁止缓存功能,如果设置的条件满足,就不执行缓存结果,与上面的condition不同之处在于,
    // 该方法执行在当前方法调用结束,结果出来之后,因此,它除了可以使用上面condition所能使用的SPEL
    // 表达式之外,还可以使用#result来获取方法的执行结果,亦即可以根据结果的不同来决定是否缓存。
    String unless() default "";
    // 设置是否对多个针对同一key执行缓存加载的操作的线程进行同步,默认不同步。这个功能需要明确确定所
    // 使用的缓存工具支持该功能,否则不要滥用。
    boolean sync() default false;
}
2.3 @CachePut

该注解用于更新缓存,无论结果是否已经缓存,都会在方法执行结束插入缓存,相当于更新缓存。一般用于更新方法之上。

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface CachePut {
    // 同上
    @AliasFor("cacheNames")
    String[] value() default {};
    // 同上
    @AliasFor("value")
    String[] cacheNames() default {};
    // 同上
    String key() default "";
    // 同上
    String keyGenerator() default "";
    // 同上
    String cacheManager() default "";
    // 同上
    String cacheResolver() default "";
    // 同上
    String condition() default "";
    // 同上
    String unless() default "";
}
2.4 @CacheEvict

该注解主要用于删除缓存操作

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface CacheEvict {
    // 同上
    @AliasFor("cacheNames")
    String[] value() default {};
    // 同上
    @AliasFor("value")
    String[] cacheNames() default {};
    // 同上
    String key() default "";
    // 同上
    String keyGenerator() default "";
    // 同上
    String cacheManager() default "";
    // 同上
    String cacheResolver() default "";
    // 同上
    String condition() default "";
    // 这个设置用于指定当前缓存名称名下的所有缓存是否全部删除,默认false。
    boolean allEntries() default false;
    // 这个用于指定删除缓存的操作是否在方法调用之前完成,默认为false,表示先调用方法,在执行缓存删除。
    boolean beforeInvocation() default false;
}
2.5 @Caching

这个注解用于组个多个缓存操作,包括针对不用缓存名称的相同操作等,源码:

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Caching {
    // 用于指定多个缓存设置操作
    Cacheable[] cacheable() default {};
    // 用于指定多个缓存更新操作
    CachePut[] put() default {};
    // 用于指定多个缓存失效操作
    CacheEvict[] evict() default {};
}

用法==>

@Service
@Log4j2
public class AnimalService {
    @Autowired
    private AnimalRepository animalRepository;
    //...
    @Caching(
        evict = {
            @CacheEvict(value = "animalById", key = "#id"),
            @CacheEvict(value = "animals", allEntries = true, beforeInvocation = true)
        }
    )
    public ResponseEntity<Integer> deleteAnimalById(final int id){
        return ResponseEntity.ok(animalRepository.deleteById(id));
    }
    @Cacheable("animals")
    public ResponseEntity<Page<Animal>> getAnimalPage(final Animal animal, final int pageId, final int pageSize){
        Page<Animal> page = new Page<>();
        page.setCurrent(pageId);
        page.setSize(pageSize);
        return ResponseEntity.ok((Page<Animal>) animalRepository.selectPage(page,packWrapper(animal, WrapperType.QUERY)));
    }
    //...
}
2.6 @CacheConfig

该注解标注于类之上,用于进行一些公共的缓存相关配置。源码为:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CacheConfig {
    // 设置统一的缓存名,适用于整个类中的方法全部是针对同一缓存名操作的情况
    String[] cacheNames() default {};
    // 设置统一个键生成器,免去了每个缓存设置中单独设置
    String keyGenerator() default "";
    // 设置统一个自定义缓存管理器
    String cacheManager() default "";
    // 设置统一个自定义缓存解析器
    String cacheResolver() default "";
}

三、基本配置

1、pom.xml

spring-boot-starter-security

spring-boot-starter-cache

spring-boot-starter-data-redis

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.5.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>org.lc</groupId>
    <artifactId>jpa-rest</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>jpa-rest</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>


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

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

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


        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

2、yaml配置
spring:
#  redis配置
  redis:
    host: 47.96.141.44
    database: 0
    password: 147258369
    port: 6379

#    配置缓存名称
  cache:
    cache-names: c1

3、实体Bean
@Getter
@Setter
@ToString
public class User implements Serializable {
    private Integer id;
    private String username;
    private String address;
}

4、开启缓存
@SpringBootApplication
//启用缓存
@EnableCaching
public class JpaRestApplication {

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

}

5、service层
①缓存存储: key:指定一个或多个参数作为key
@Service
public class UserService {
/**
     * 方法的参数为key
     * 方法返回的结果为value
     *
     * cacheNames配置的缓存名称
     * key 只指定某一个参数作为key,或者联合多个参数作为key
     * @param id
     * @return
     */
    @Cacheable(cacheNames = "c1",key = "#id+'-'+#name")
    public User getUserById(Integer id,String name) {
        System.out.println("getUserId===>>>>" + id);
        User u=new User();
        u.setId(id);
        return u;
    }
}

当参数即key相同时,直接从redis中取

测试==>

@SpringBootTest
class JpaRestApplicationTests {

    @Autowired
    private UserService userService;

    @Test
    void contextLoads() {
        User userById = userService.getUserById(1,"bb");
        User userById1 = userService.getUserById(1,"bb");
        System.out.println(userById);
        System.out.println(userById1);
    }
}
②自定义key
@Component
public class RedisKeyGenerator implements KeyGenerator {
    /**
     * 自定义返回的key  当前定义的key形式为 " 方法名:所有参数组成的字符串数组  "
     * @param o
     * @param method 当前方法名
     * @param objects 方法的参数
     * @return
     */
    @Override
    public Object generate(Object o, Method method, Object... objects) {
        return method.getName()+":"+ Arrays.toString(objects);
    }
}

@Service
public class UserService {
    /**
     * keyGenerator 自定义生成的key 当前引用的配置类为开头字母小写的类名
     * @param id
     * @param name
     * @return
     */
    @Cacheable(cacheNames = "c1",keyGenerator = "redisKeyGenerator")
    public User getUserById1(Integer id,String name,String author) {
        System.out.println("getUserId===>>>>" + id);
        User u=new User();
        u.setId(id);
        return u;
    }
}

测试==>

    @Test
    void contextLoads1() {
        User userById = userService.getUserById1(1,"bb","张三");
        User userById1 = userService.getUserById1(1,"bb","张三");
        System.out.println(userById);
        System.out.println(userById1);
    }
③删除缓存
@Service
public class UserService { 
    @Cacheable(cacheNames = "c1")
    public User getUserById2(Integer id) {
        System.out.println("getUserId===>>>>" + id);
        User u=new User();
        u.setId(id);
        return u;
    }

    /**
     * 删除缓存中的内容
     * 默认根据方法的参数作为key进行删除
     * @param id
     */
    @CacheEvict(cacheNames = "c1")
    public void deleteUserById(Integer id) {
        System.out.println("deleteUserById==>>>>"+id);
    }
}

测试==>

@Test
    void contextLoads2() {
        User userById = userService.getUserById2(1);
        userService.deleteUserById(1);
        User userById1 = userService.getUserById2(1);
        System.out.println(userById);
        System.out.println(userById1);
    }
④更新缓存
@Service
public class UserService { 
/**
     * 更新缓存中的内容  key: 指定user对象中的id作为键
     * @param user
     * @return
     */
    @CachePut(cacheNames = "c1",key = "#user.id")
    public User updateUserById(User user){
        return user;
    }
}

测试==>

 @Test
    void contextLoads3() {
        User userById = userService.getUserById2(2);
        User user = new User();
        user.setId(1);
        user.setUsername("张三");
        user.setAddress("湖北武汉");
        userService.updateUserById(user);
        User userById1 = userService.getUserById2(2);
        System.out.println(userById);
        System.out.println(userById1);
    }
⑤定义全局缓存名称

@CacheConfig(cacheNames = "c1") 无需在每个方法上加cacheName

@Service
@CacheConfig(cacheNames = "c1")
public class UserService {

    /**
     * 方法的参数为key
     * 方法返回的结果为value
     *
     * cacheNames配置的缓存名称
     * key 只指定某一个参数作为key,或者联合多个参数作为key
     * @param id
     * @return
     */
    @Cacheable(key = "#id+'-'+#name")
    public User getUserById(Integer id,String name) {
        System.out.println("getUserId===>>>>" + id);
        User u=new User();
        u.setId(id);
        return u;
    }
}