6

从 0 学习 Spring 缓存数据

 3 years ago
source link: https://segmentfault.com/a/1190000038436066
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

从 0 学习 Spring 缓存数据

1460000038436072

昨天在开发业务时,打算加入缓存层来提高系统响应速度。查找了一些资料,发现 Spring 的缓存功能十分强大!只需要添加少量的代码,就可以轻松缓存方法所返回的对象。这篇文章通过描述一个实际使用例子,介绍 Spring Cache 的使用限制以及注意事项。

  • Redis 5+
  • JDK 1.8+
  • Gradle 6+
  • 一款你喜爱的 IDE

打开 build.gradle 文件,添加 Spring Cache 依赖。

implementation 'org.springframework.boot:spring-boot-starter-cache'
implementation 'org.springframework.boot:spring-boot-starter-data-redis'
@Data
@AllArgsConstructor
public class Post implements Serializable {

    private Long id;

    private String title;

    private String content;
}

PS:这里使用到了 Lombok 插件,如果不熟悉可先查询相关资料进行了解。

创建模型仓库

public interface PostRepository {

    Post getById(Long id);

}
@Component
public class PostRepositoryImpl implements PostRepository {
    @Override
    public Post getById(Long id) {
        // 模拟查询时间
        simulateSlowService();
        return new Post(100L, "title", "content");
    }

    private void simulateSlowService() {
        try {
            Long time = 3000L;
            Thread.sleep(time);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

编写控制器

@RestController
public class PostController {

    private final PostRepository postRepository;

    public PostController(PostRepository postRepository) {
        this.postRepository = postRepository;
    }

    @GetMapping("posts/{id}")
    public Post getPostById(@PathVariable("id") Long id) {
        return postRepository.getById(id);
    }
}

针对一些不容易被修改的资源,如果每次都需要到持久化数据库中进行查询,无疑是十分浪费的,体验也差,下面我们使用 Spring Cache 来改进一波。

使用 Spring Cache

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

添加 @EnableCaching 注解启动 Spring Cache。

spring:
  cache:
    type: redis
  redis:
    host: 127.0.0.1
    port: 6379

这里用 Redis 作为缓存引擎,如果小伙伴想用其他引擎,可自行查阅文档进行配置。

@RestController
public class PostController {

    private final PostRepository postRepository;

    public PostController(PostRepository postRepository) {
        this.postRepository = postRepository;
    }
    
    @Cacheable(cacheNames = "getPostById", key = "#id")
    @GetMapping("posts/{id}")
    public Post getPostById(@PathVariable("id") Long id) {
        return postRepository.getById(id);
    }
}

使用 @Cacheable 注解 getPostById 方法,使用了 cacheNames 和 key 参数。这里先不展开说,下面会集中梳理几种注解以及它们的参数意义。

Spring Cache 注解

Spring Cache 常用的 5 个注解,分别是:

  • @EnableCaching
  • @Cacheable
  • @CachePut
  • @CacheEvict
  • @CacheConfig

@EnableCaching

在启动类添加 @EnableCaching 注解让系统开启缓存功能。

@Cacheable

功能是开启缓存,可以标记在类上或者是方法上。在调用方法时,会先从缓存中获取结果,若不存在再执行方法。主要参数包括 cacheNames、key、condition 和 unless 等。

  • cacheNames:用于指定缓存存储的集合名,必须填写。
  • key:缓存对象存储在集合中的 key 值,缺省按照函数的所有参数组合作为 key 值。
  • condition:缓存对象的条件,需使用 SpEL 表达式。只有满足表达式条件的内容才会被缓存。
  • unless:缓存对象的条件,需使用 SpEL 表达式。它不同于 condition 参数的地方在于它的判断时机,该条件是在函数被调用之后才做判断的,所以它可以通过对返回对象进行判断。
  • keyGenerator:用于指定 key 生成器。若需要自定义 key 生成器,需要实现 KeyGenerator 接口,并使用该参数来指定。
  • cacheManager:用于指定使用哪个缓存管理器。
  • cacheResolver:用于指定使用那个缓存解析器。

@CachePut

针对方法配置,与 @Cacheable 不同的地方在于它每次都会触发真实方法的调用。简单来说就是更新缓存数据。主要参数和 @Cacheable 一致。

@CacheEvict

针对方法配置,用来从缓存中移除相应数据。除了与 @Cacheable 相同的参数以外,还有 allEntries 和 beforeInvocation。

  • allEntries 非必须,默认为 false。当为 true 时,会移除所有数据。
  • beforeInvocation 非必须,默认为 false,会在调用方法之后移除数据。当为 true 时,会在调用方法之前移除数据。

@CacheConfig

该注解是一个类级注解,可以让类下面的方法共享 cacheNames、keyGenerator、cacheManager 和 cacheResolver 参数。

自定义 cacheNames

这里是为了让我们的缓存注解支持自定义 TTL 失效时间,类似下面这种效果。

// 3600 秒后缓存集合自动过期
@Cacheable(cacheNames = "getPostById#3600", key = "#id")

为了实现这种效果,我们创建一个 CustomRedisCacheManager 自定义类,如下所示。

public class CustomRedisCacheManager extends RedisCacheManager {
    public CustomRedisCacheManager(RedisCacheWriter cacheWriter, RedisCacheConfiguration defaultCacheConfiguration) {
        super(cacheWriter, defaultCacheConfiguration);
    }
    
    @Override
    protected RedisCache createRedisCache(String name, RedisCacheConfiguration cacheConfig) {

        String[] array = StringUtils.delimitedListToStringArray(name, "#");

        name = array[0];
        
        if (array.length > 1) {
            long ttl = Long.parseLong(array[1]);
            cacheConfig = cacheConfig.entryTtl(Duration.ofSeconds(ttl));
        }

        return super.createRedisCache(name, cacheConfig);
    }
}

使用自定义 CustomRedisCacheManager 配置 CacheConfig。

public class CacheConfig extends CachingConfigurerSupport {

    @Value("${spring.redis.host}")
    private String redisHost;

    @Value("${spring.redis.port}")
    private Integer redisPort;

    @Value("${spring.redis.database}")
    private Integer redisDatabase;

    @Override
    @Bean
    public CacheManager cacheManager() {
        RedisCacheConfiguration defaultCacheConfig = RedisCacheConfiguration.defaultCacheConfig()
                .entryTtl(Duration.ofDays(1))
                .computePrefixWith(cacheName -> "caching:" + cacheName);

        return new CustomRedisCacheManager(RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory()), defaultCacheConfig);
    }

    @Bean
    public RedisConnectionFactory redisConnectionFactory() {
        RedisStandaloneConfiguration configuration = new RedisStandaloneConfiguration();
        configuration.setHostName(redisHost);
        configuration.setPort(redisPort);
        configuration.setDatabase(redisDatabase);
        return new LettuceConnectionFactory(configuration);
    }
}

本文主要介绍了 Spring Cache 的基本使用方式和常用注解。后续文章准备深入了解其底层原理。

欢迎大家关注我的公众号「是然的笔记本」。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK