SprigBoot--Redis之 @Cacheable总结

@Cacheable总结

Redis的作用很多,缓存是其中之一,作为内存数据库,效率不言而喻,热点、高频搜索词汇基本都要进行缓存。Java使用redis是一件麻烦的事情,需要使用客户端API去操作,如Jedis 、lettuce 。Spring对Redis进行整合之后,使用就非常方便了,这里提一嘴,SpringBoot2.0之后将Redis的默认客户端由Jedis更换为lettuce,考虑到旧项目的升级并没有直接剔除Jedis。

1、依赖及配置文件

	<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>com.baomidou</groupId>
        <artifactId>mybatis-plus-boot-starter</artifactId>
        <version>3.1.2</version>
    </dependency>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
    </dependency>

配置文件:application.properties

server.port=9003
spring.datasource.driverClassName=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/test?serverTimezone=UTC&characterEncoding=utf-8
spring.datasource.username=root
spring.datasource.password=123456

#####Redis配置########
# Redis数据库索引(默认为0)
spring.redis.database=0
# Redis服务器地址
spring.redis.host=localhost
# Redis服务器连接端口
spring.redis.port=6379
# Redis服务器连接密码(默认为空)
#spring.redis.password=123456
# 连接池最大连接数(使用负值表示没有限制)
spring.redis.lettuce.pool.max-active=8
# 连接池最大阻塞等待时间(使用负值表示没有限制)
spring.redis.lettuce.pool.max-wait=-1
# 连接池中的最大空闲连接,默认值8
spring.redis.lettuce.pool.max-idle=10
# 连接池中的最小空闲连接
spring.redis.lettuce.pool.min-idle=0
# 连接超时时间(毫秒)
spring.redis.timeout=1000

2、@Cacheable简介

@Cacheable会将方法的返回值缓存到Redis中,第一次走数据库,后面都是直接走Redis查询数据。
源码截图
看源码可以看出,该注解可以使用在接口、类及方法上,生效范围跟注解位置有关,方法上表示该方法开启缓存,类上,表示类的所有方法开启缓存。
使用很简单,在需要缓存的类或方法上添加注解@Cacheable,然后在启动类上添加@EnableCaching开启缓存。

@RestController
public class LogisticController {
    @Autowired
    private LogisticsMapper logisticsMapper;

    @GetMapping("/test01/{id}")
    @Cacheable(value = "logistic")
    public Logistics test01(@PathVariable("id") Long id) {
        Logistics logistics = logisticsMapper.selectById(id);
        System.out.println("走数据库打印我");
        return logistics;
    }
}

访问http://localhost:9003/test01/15,第一次会走数据库,控制台打印“走数据库打印我”,再次访问走Redis,控制台不再输出。
接下来看看注解的属性:

	/**
	 * 缓存块(可视化工具视角就是文件夹),必须要定义,可以为多个,如value = {"logistic","test"},cacheNames的别名
	 */
	@AliasFor("cacheNames")
	String[] value() default {};

	/**
	 * 和value一样的
	 */
	@AliasFor("value")
	String[] cacheNames() default {};

	/**
	 *Redis中的kry
	 */
	String key() default "";

	/**
	 * 缓存key的生成策略
	 */
	String keyGenerator() default "";

	/**
	 * 缓存管理器
	 */
	String cacheManager() default "";

	/**
	 *缓存解析器
	 */
	String cacheResolver() default "";

	/**
	 * 过滤条件,true代表会缓存,反之不会,可以写布尔表达式,默认空,也就是都会缓存
	 */
	String condition() default "";

	/**
	 *否决缓存,此表达式是在方法之后计算的,默认空,从不否决,跟condition相反
	 */
	String unless() default "";

	/**
	 *多线程查询时,是否同步调用,默认false
	 */
	boolean sync() default false;

}

如果我们只定义了缓存块,没有定义key,key会是什么呢?key默认是你的缓存块名称::SimpleKey [],数组里面是方法的参数值;比如

    @Cacheable(value = "logistic")
    public Logistics test02( Long id,String code) {
        Logistics logistics = logisticsMapper.selectById(id);
        System.out.println("走数据库打印我");
        return logistics;
    }

kety就是 logistic::SimpleKey [15,E012454],但是这样是有问题的,假如参数值一样或者没有参数,就会造成key被覆盖,出现脏数据,因此key必须要自定义,比如使用方法名拼接参数,如下:

 	@Cacheable(value = "logistic",key = "'test02'+#id+#code")
    public Logistics test02(@PathVariable("id") Long id,@PathVariable("code")String code) {
        Logistics logistics = logisticsMapper.selectById(id);
        System.out.println("走数据库打印我");
        return logistics;
    }

id=15,code=E012454,key就变为 logistic::test0215E012454
注意:获取传入的参数需要前面加#,字符拼接使用单引号。
如果还是不满足要求,可以使用配置类解决,新建配置类继承CachingConfigurerSupport,重写相关方法,进行自定义:

@Configuration
@Slf4j
public class RedisConfig extends CachingConfigurerSupport {

    /**
     * key生成规则,使用全限定类名+方法名+参数值作为key,注解上如果有定义key规则,这里就不会生效
     *
     * @return
     */
    @Bean
    @Override
    public KeyGenerator keyGenerator() {
        return (target, method, 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();
        };
    }


    public RedisConfig() {
        super();
    }

    /**
     * 缓存管理器,可以甚至一些redis的配置,但是一般直接在properties文件设置了
     *
     * @return
     */
    @Override
    public CacheManager cacheManager() {
        return super.cacheManager();
    }

    @Override
    public CacheResolver cacheResolver() {
        return super.cacheResolver();
    }

    /**
     * Redis数据操作异常处理,进行自定义配置,如果redis挂了,会绕过redis直接走数据库,反之则会抛出异常
     *
     * @return
     */
    @Bean
    @Override
    public CacheErrorHandler errorHandler() {
        CacheErrorHandler cacheErrorHandler = new CacheErrorHandler() {
            @Override
            public void handleCachePutError(RuntimeException exception, Cache cache, Object key, Object value) {
                log.error("从redis推送数据异常,redis可能挂了");
            }

            @Override
            public void handleCacheGetError(RuntimeException exception, Cache cache, Object key) {
                log.error("从redis获取数据异常,redis可能挂了");
            }

            @Override
            public void handleCacheEvictError(RuntimeException exception, Cache cache, Object key) {

            }

            @Override
            public void handleCacheClearError(RuntimeException exception, Cache cache) {

            }
        };
        return cacheErrorHandler;
    }

说明:如果Redis宕机了,会直接抛出异常,并不会像想象中的那样,Reds宕机就走数据库拿数据,因此我们需要重写errorHandler()方法,进行自定义,只要自定义了,即使Redis宕机也会转从数据库捞数据,不至于影响主业务流程。这里有个时间需要设置一下,spring.redis.timeout=1000,就是Redis连接超时时间,默认1分钟,也就是说会等待一分钟,确认连不上Redis才会走数据库,时间太长设置1s就i行,这里什么都不做就打个日志。停掉Redis访问,控制台日志如下:
控制台日志

3、缓存不生效

场景:同一个类中A、B方法都加缓存注解,但是在A中去调用B方法,A成功写入缓存,但是B却没有
原因:这个其实和Spring的声明式事务一样的情况,是因为他们都是基于AOP的,AOP使用的是代理类,也就是说AOP的最小粒度是Class,在A方法中调用B相当于this.B(),B方法并不是通过代理类进行调用的,因此不会经过切面,所有跟AOP相关的都是一样的。
解决:要么在类中注入自身,再去调用,或者从Spring容器的上下文中获取该类之后再去调用B方法。

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值