SpringBoot整合Cache缓存技术(二十一)上

本文涉及的产品
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
云数据库 Tair(兼容Redis),内存型 2GB
简介: 一. SpringCache一.一 SpringCache 的出现一.二 SpringCache 的简单使用

一. SpringCache

一.一 SpringCache 的出现

在SpringBoot 整合 Redis 时,无论是使用 Lettuce 还是使用 Jedis 连接池, 在查询单个对象,查询全部对象的时候,都是我们自己手动进行判断缓存的信息。

SpringBoot 使用 Lettuce 连接池时:

 @Override
    public User findById(int id) {
        log.info("先从缓存中查询用户编号为{} 是否存在",id);
        User user=redisUtil.get(KEY_PRE+id);
        if(user!=null){
            log.info(">>>>>>>>>>使用的是缓存中的数据");
            return user;
        }
        log.info(">>>>>>>>>>>从数据库中查询,并放置到缓存中");
        user= userMapper.findById(id);
        redisUtil.set(KEY_PRE+id,user);
        return user;
    }
    @Override
    public List<User> findAll() {
        log.info("先从缓存中查询用户列表是否存在");
        List<User> userList= (List<User>) redisUtil.range(KEY_PRE+"ALL");
        if(!CollectionUtils.isEmpty(userList)){
            log.info(">>>>>>>>>>使用的是缓存中的数据");
            return userList;
        }
        log.info(">>>>>>>>>>>从数据库中查询,并放置到缓存中");
        userList= userMapper.findAll();
        redisUtil.leftPushAll(KEY_PRE+"ALL",userList);
        return userList;
    }

SpringBoot 使用 Jedis 连接池时:

 @Override
    public User findById(int id) {
        log.info("先从缓存中查询用户编号为{} 是否存在",id);
        User user=BeanConvertUtil.stringToBean(redisUtil.get(KEY_PRE+id,redisDB),User.class);
        if(user!=null){
            log.info(">>>>>>>>>>使用的是缓存中的数据");
            return user;
        }
        log.info(">>>>>>>>>>>从数据库中查询,并放置到缓存中");
        user= userMapper.findById(id);
        redisUtil.set(KEY_PRE+id,BeanConvertUtil.beanToString(user),redisDB);
        return user;
    }
    @Override
    public List<User> findAll() {
        log.info("先从缓存中查询用户列表是否存在");
        List<String> userStringList= (List<String>) redisUtil.lrange(KEY_PRE+"ALL",0,-1,redisDB);
        List<User> userList=new ArrayList<>();
        if(!CollectionUtils.isEmpty(userStringList)){
            log.info(">>>>>>>>>>使用的是缓存中的数据");
            for(String userString:userStringList){
                userList.add(BeanConvertUtil.stringToBean(userString,User.class));
            }
            return userList;
        }
        log.info(">>>>>>>>>>>从数据库中查询,并放置到缓存中");
        userList= userMapper.findAll();
        for(User user:userList){
            redisUtil.lpush(redisDB,KEY_PRE+"ALL",BeanConvertUtil.beanToString(user));
        }
        return userList;
    }

可以发现, 两个都需要开发者自己手动处理缓存的信息。

并且,如果缓存的工具不同,处理的方式也不同。

实际上,这些与业务是没有太大的联系的。

我们希望能够有一种方式,能够通过简单的配置+注解,达到以前原生的写法就完美了。

    @Override
    public User findById(int id) {
        return userMapper.findById(id);
    }
    @Override
    public List<User> findAll() {
        return userMapper.findAll();
    }

在这两个方法上,添加某个注解, 能够达到 有缓存走缓存,没有缓存走数据库查询,

然后将查询结果放置在缓存中,下一次查询时走缓存的结果,

并且与缓存的实现方式无关 (无论是 Lettuce 还是 Jedis)


有这么一种技术, 叫做SpringCache


一.二 SpringCache 的简单使用

按照 SpringBoot_Redis 项目,创建 SpringBoot_Cache 项目。


Redis服务器打开,使用的仍然是 database 15 数据库。


采用的是 springboot 2.2.13 版本。


目前数据库表 user 里面有三条记录

0.png

一.二.一 pom.xml 添加依赖

        <!--依赖 data-redis的依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <!--不能忘记这个依赖-->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-pool2</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-cache</artifactId>
        </dependency>
        <!--添加cache的依赖信息-->

一.二.二 application.yml 进行配置

与redis整合时一样,没有改变。

# 引入 数据库的相关配置
spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/springboot?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=UTF-8&useSSL=false&allowMultiQueries=true
    username: root
    password: abc123
  # 配置Redis的使用
  redis:
    database: 15 # 所使用的数据库  默认是0
    host: 127.0.0.1  #所使用的redis的主机地址
    port: 6379  # 端口号  默认是 6379
    password: zk123 # 密码
    timeout: 5000 # 超时时间  5000毫秒
    # 连接池 lettuce 的配置
    lettuce:
      pool:
        max-active: 100
        min-idle: 10
        max-wait: 100000
#整合mybatis时使用的
mybatis:
  #包别名
  type-aliases-package: top.yueshushu.learn.pojo
  #映射文件路径
  mapper-locations: classpath:mybatis/mapper/**/*.xml
  configuration:
    #日志信息
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

一.二.三 启动类上 添加 @EnableCaching 注解

需要在启动类上 添加 @EnableCaching 注解, 开启缓存。

@MapperScan("top.yueshushu.learn.mapper")
@SpringBootApplication
//开启缓存
@EnableCaching 
public class RedisApplication {
    public static void main(String[] args) {
        SpringApplication.run(RedisApplication.class,args);
        System.out.println("运行 Redis Cache缓存");
    }
}

一.二.四 不使用缓存时处理

一.二.四.一 查询 findById 实现

    @Override
    public User findById(int id) {
        return userMapper.findById(id);
    }

一.二.四.二 查询测试

  @Test
    public void findByIdTest(){
        User user=userService.findById(40); //id随时更换
        log.info(user);
    }

运行测试方法 findByIdTest()

第一次查询

1.png

发现查询了数据库

第二次查询

2.png

依然走的是数据库查询.

这是以前的常规的写法。

一.二.五 使用SpringCache 缓存时处理

一.二.五.一 查询 findById 实现

    @Override
    // 指定了参数为 id:  变成了:  value::id 的key值
    @Cacheable(value=KEY_PRE,key = "#id")
    public User findById(int id) {
        return userMapper.findById(id);
    }

在方法上 添加了一个注解 @Cacheable ,补充属性信息

value 表示使用的缓存组, key 表示缓存的值。

一.二.五.二 查询测试

  @Test
    public void findByIdTest(){
        User user=userService.findById(40); //id随时更换
        log.info(user);
    }

运行测试方法 findByIdTest()

第一次查询

3.png

发现查询了数据库

第二次查询

4.png

发现,并没有查询数据库,走的是缓存里面的数据。

查看 Redis客户端

5.png

发现存储的数据乱码了.

一.二.六 处理存储信息乱码问题

除了 RedisConfig.java 配置之外 ,再添加一个 CacheConfig.java 的配置信息

package top.yueshushu.learn.config;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.log4j.Log4j2;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.interceptor.*;
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.cache.RedisCacheWriter;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import javax.annotation.Resource;
import java.time.Duration;
import java.util.HashMap;
import java.util.Map;
/**
 * @author :zk_yjl
 * @description:Cache的缓存配置信息,可以解决乱码问题
 * @date :2021/09/23 17:09
 */
@Log4j2
@Configuration
public class CacheConfig extends CachingConfigurerSupport {
    @Resource
    private RedisConnectionFactory factory;
    /**
     * 自定义生成redis-key
     *
     * @return
     */
    @Override
    @Bean
    public KeyGenerator keyGenerator() {
        return (o, method, objects) -> {
            StringBuilder sb = new StringBuilder();
            sb.append(o.getClass().getName()).append(".");
            sb.append(method.getName()).append(".");
            for (Object obj : objects) {
                sb.append(obj.toString());
            }
            log.info("keyGenerator=" + sb.toString());
            return sb.toString();
        };
    }
    @Bean
    @Override
    public CacheResolver cacheResolver() {
        return new SimpleCacheResolver(cacheManager());
    }
    @Bean
    @Override
    public CacheErrorHandler errorHandler() {
        // 用于捕获从Cache中进行CRUD时的异常的回调处理器。
        return new SimpleCacheErrorHandler();
    }
    @Bean
    @Override
    public CacheManager cacheManager() {
        return new RedisCacheManager(
                RedisCacheWriter.nonLockingRedisCacheWriter(factory),
                this.getRedisCacheConfigurationWithTtl(30*60), // 默认策略,未配置的 key 会使用这个
                this.getRedisCacheConfigurationMap() // 指定 key 策略
        );
    }
    private Map<String, RedisCacheConfiguration> getRedisCacheConfigurationMap() {
        Map<String, RedisCacheConfiguration> redisCacheConfigurationMap = new HashMap<>();
        //DayCache和SecondsCache进行过期时间配置translates缓存丢弃改为了redis
        redisCacheConfigurationMap.put("translates", this.getRedisCacheConfigurationWithTtl(12*60*60));
        redisCacheConfigurationMap.put("strategies", this.getRedisCacheConfigurationWithTtl(60));
        return redisCacheConfigurationMap;
    }
    private RedisCacheConfiguration getRedisCacheConfigurationWithTtl(Integer seconds) {
        Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(om);
        RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig();
        redisCacheConfiguration = redisCacheConfiguration.serializeValuesWith(
                RedisSerializationContext
                        .SerializationPair
                        .fromSerializer(jackson2JsonRedisSerializer)
        ).entryTtl(Duration.ofSeconds(seconds));
        return redisCacheConfiguration;
    }
}

重新运行测试 (此时缓存信息并没有清空)

6.png

出现了异常.

将缓存信息 key 清空后再执行, 运行是成功的,

从数据库里面查询, 将查询结果放置到Redis缓存里面,并且缓存信息正常展示。

7.png

相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
相关文章
|
30天前
|
人工智能 自然语言处理 前端开发
SpringBoot + 通义千问 + 自定义React组件:支持EventStream数据解析的技术实践
【10月更文挑战第7天】在现代Web开发中,集成多种技术栈以实现复杂的功能需求已成为常态。本文将详细介绍如何使用SpringBoot作为后端框架,结合阿里巴巴的通义千问(一个强大的自然语言处理服务),并通过自定义React组件来支持服务器发送事件(SSE, Server-Sent Events)的EventStream数据解析。这一组合不仅能够实现高效的实时通信,还能利用AI技术提升用户体验。
149 2
|
18天前
|
存储 缓存 数据库
缓存技术有哪些应用场景呢
【10月更文挑战第19天】缓存技术有哪些应用场景呢
|
17天前
|
存储 缓存 运维
缓存技术有哪些优缺点呢
【10月更文挑战第19天】缓存技术有哪些优缺点呢
|
1月前
|
存储 Java API
简单两步,Spring Boot 写死的定时任务也能动态设置:技术干货分享
【10月更文挑战第4天】在Spring Boot开发中,定时任务通常通过@Scheduled注解来实现,这种方式简单直接,但存在一个显著的限制:任务的执行时间或频率在编译时就已经确定,无法在运行时动态调整。然而,在实际工作中,我们往往需要根据业务需求或外部条件的变化来动态调整定时任务的执行计划。本文将分享一个简单两步的解决方案,让你的Spring Boot应用中的定时任务也能动态设置,从而满足更灵活的业务需求。
70 4
|
1月前
|
存储 JSON 算法
JWT令牌基础教程 全方位带你剖析JWT令牌,在Springboot中使用JWT技术体系,完成拦截器的实现 Interceptor (后附源码)
文章介绍了JWT令牌的基础教程,包括其应用场景、组成部分、生成和校验方法,并在Springboot中使用JWT技术体系完成拦截器的实现。
58 0
JWT令牌基础教程 全方位带你剖析JWT令牌,在Springboot中使用JWT技术体系,完成拦截器的实现 Interceptor (后附源码)
|
2月前
|
存储 缓存 NoSQL
解决Redis缓存击穿问题的技术方法
解决Redis缓存击穿问题的技术方法
64 2
|
2月前
|
存储 缓存 Java
在Spring Boot中使用缓存的技术解析
通过利用Spring Boot中的缓存支持,开发者可以轻松地实现高效和可扩展的缓存策略,进而提升应用的性能和用户体验。Spring Boot的声明式缓存抽象和对多种缓存技术的支持,使得集成和使用缓存变得前所未有的简单。无论是在开发新应用还是优化现有应用,合理地使用缓存都是提高性能的有效手段。
37 1
|
30天前
|
机器学习/深度学习 移动开发 自然语言处理
基于人工智能技术的智能导诊系统源码,SpringBoot作为后端服务的框架,提供快速开发,自动配置和生产级特性
当身体不适却不知该挂哪个科室时,智能导诊系统应运而生。患者只需选择不适部位和症状,系统即可迅速推荐正确科室,避免排错队浪费时间。该系统基于SpringBoot、Redis、MyBatis Plus等技术架构,支持多渠道接入,具备自然语言理解和多输入方式,确保高效精准的导诊体验。无论是线上医疗平台还是大型医院,智能导诊系统均能有效优化就诊流程。
消息中间件 缓存 监控
107 0
|
3月前
|
Java 数据库连接 数据库
告别繁琐 SQL!Hibernate 入门指南带你轻松玩转 ORM,解锁高效数据库操作新姿势
【8月更文挑战第31天】Hibernate 是一款流行的 Java 持久层框架,简化了对象关系映射(ORM)过程,使开发者能以面向对象的方式进行数据持久化操作而无需直接编写 SQL 语句。本文提供 Hibernate 入门指南,介绍核心概念及示例代码,涵盖依赖引入、配置文件设置、实体类定义、工具类构建及基本 CRUD 操作。通过学习,你将掌握使用 Hibernate 简化数据持久化的技巧,为实际项目应用打下基础。
159 0
下一篇
无影云桌面