java基于Spring注解的缓存 解决方案

今天分享 java基于Spring注解的缓存 解决方案;

上篇以代码的方式分享缓存解决方案;今天分享以注解的方式解决缓存问题:

Spring 3.1 起,提供了基于注解的对 Cache 的支持。
一、使用 Spring Cache 的好处:
 1、 基于注解,代码清爽简洁;
 2、基于注解也可以实现复杂的逻辑;
 3、可以对缓存进行回滚;
Spring Cache 并非具体的缓存技术,而是基于各种缓存产品(如 Guava、EhCache、 Redis 等)共性进行的一层封装,结合 SpringBoot 的开箱即用的特性用起来会非常方
便,因为 Spring Cache 通过注解隔离了具体的缓存产品,让用户更加专注于应用层面。 具体的底层缓存技术究竟采用了 Guava、EhCache 还是 Redis,只需要简单的配置就 可以实现方便的切换。
二、设计理念
正如 Spring 框架的其它服务一样,Spring cache 首先是提供了一层抽象,核心抽象 主要体现在两个接口上
org.springframework.cache.Cache:代表缓存本身
org.springframework.cache.CacheManager:代表对缓存的处理和管理
抽象的意义在于屏蔽实现细节的差异和提供扩展性,Cache 的抽象解耦了缓存的使用和 缓存的后端存储,方便后续更换存储。
 
三、使用 Spring Cache
分三步:  声明缓存 、  开启 Spring 的 cache 功能  、 配置后端的存储
1 声明缓存
@Cacheable("book")
public Book findBook() {...}
用法很简单,在方法上添加@cacheable 等注解,表示缓存该方法的结果。 当方法有被调用时,先检查 cache 中有没有针对该方法相同参数的调用发生过,如果有,
从 cache 中查询并返回结果。如果没有,则执行具体的方法逻辑,并把结果缓存到 cache 中。当然这一系列逻辑对于调用者来说都是透明的。
 
其它的缓存操作的注解包含如下(详细说明可参见官方文档):
@Cacheable triggers cache population
@CacheEvict triggers cache eviction
@CachePut updates the cache without interfering with the method execution
@Caching regroups multiple cache operations to be applied on a method
@CacheConfig shares some common cache-related settings at class-level
 
 
2、 开启 Spring Cache 的支持
<cache:annotation-driven />
或者使用注解@EnableCaching 的方式
@EnableCaching
public class CacheConfig extends CachingConfigurerSupport {

//相关配置暂时省略,后面会讲解
}

3 、配置缓存后端存储

SpringCache 包本身提供了一个 manager 实现,用法如下:
@Bean public CacheManager cacheManager() { 
//jdk 里,内存管理器 
SimpleCacheManager cacheManager = new SimpleCacheManager(); 

cacheManager.setCaches(Collections.singletonList(new ConcurrentMapCache("province"))); 

return cacheManager; 

}

更常用的,RedisCacheManager(来自于 Spring Data Redis 项目),用法如下:

 
@Bean public CacheManager cacheManager(RedisConnectionFactory connectionFactory) { 
return RedisCacheManager .builder(connectionFactory) 
   .cacheDefaults( RedisCacheConfiguration.defaultCacheConfig()     
   .entryTtl(Duration.ofSeconds(20))) //缓存时间绝对过期时间 20s 
   .transactionAware() .build(); 
 }

4、 缓存 key 的生成

我们都知道缓存的存储方式一般是 key value 的方式,那么在 Spring cache 里,key 是如何被设置的呢,在这里要引入 KeyGenerator,它负责 key 的生成策略,只需要按
自己的业务,为 params 设计一套生成 key 的规则即可(简单地连接各个参数值):
 
//key 的生成,springcache 的内容,跟具体实现缓存器无关 
@Bean 
public KeyGenerator keyGenerator() { 
return new KeyGenerator() { 
    @Override 
    public Object generate(Object target, Method method, Object... params) { 
    StringBuilder sb = new StringBuilder(); 
    sb.append(target.getClass().getSimpleName()); 
    sb.append(method.getName());
    for (Object obj : params) {
       sb.append(obj.toString()); 
    }
    return sb.toString(); 
    } 
  }; 
}
三、 注解风格说明

1 、@CachePut 注解说明

public @interface CachePut {
String[] value();
//缓存的名字,可以把数据写到多个缓存
String key() default "";
// 缓 存 key , 如 果 不 指 定 将 使 用 默 认 的
KeyGenerator 生成,后边介绍
String condition() default ""; //满足缓存条件的数据才会放入缓存,condition在调用方法之前和之后都会判断
String unless() default "";
//用于否决缓存更新的,不像 condition,该表达只在方法执行之后判断,此时可以拿到返回值 result 进行判断了
}
 
@CachePut 注解使用 应用到写数据的方法上,如新增/修改方法,调用方法时会自动把相应的数据放入缓存。 比如下面调用该方法时,会把 user.id 作为 key,返回值作为 value 放入缓存
 
2、 @CacheEvict 注解说明
public @interface CacheEvict {
String[] value();
//请参考@CachePut
String key() default "";
//请参考@CachePut
String condition() default "";
//请参考@CachePut
boolean allEntries() default false;
//是否移除所有数据享学课堂
boolean beforeInvocation() default false;//是调用方法之前移除/还是调用之后移除
}
@CacheEvict 注解使用 应用到移除数据的方法上,如删除方法,调用方法时会从缓存中移除相应的数据
3 、@Cacheable 注解说明
 
public @interface Cacheable {
String[] value();
//请参考@CachePut
String key() default "";
//请参考@CachePut
String condition() default "";//请参考@CachePut
String unless() default "";
//请参考@CachePut
}

@Cacheable 注解使用应用到读取数据的方法上,即可缓存的方法,如查找方法:先从缓存中读取,如果没有 再调用方法获取数据,然后把数据添加到缓存中

四、实战使用:

1、缓存配置:

@Configuration
@EnableCaching
public class CacheConfig extends CachingConfigurerSupport {


    //key的生成,springcache的内容,跟具体实现缓存器无关
    //自定义本项目内的key的方式
    @Bean
    public KeyGenerator keyGenerator() {
        return new KeyGenerator() {
            @Override
            public Object generate(Object target, Method method, Object... params) {

                StringBuilder sb = new StringBuilder();
                sb.append(target.getClass().getSimpleName());
                sb.append(method.getName());
                for (Object obj : params) {
                    sb.append(obj.toString());
                }
                return sb.toString();
            }
        };
    }

    //不支持过期时间
//    @Bean
//    public CacheManager cacheManager() {
//        //jdk里,内存管理器
//        SimpleCacheManager cacheManager = new SimpleCacheManager();
//        cacheManager.setCaches(Collections.singletonList(new ConcurrentMapCache("province")));
//        return cacheManager;
//    }

    @Bean
    public CacheManager cacheManager(RedisConnectionFactory connectionFactory) {
        return RedisCacheManager
                .builder(connectionFactory)
                .cacheDefaults(
                        RedisCacheConfiguration.defaultCacheConfig()
                        .entryTtl(Duration.ofSeconds(20))) //缓存时间绝对过期时间20s
                .transactionAware()
                .build();
    }

    /**
     * 序列化object对象为json字符串
     */
  /*  @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(factory);

        //使用Jackson2JsonRedisSerializer来序列化和反序列化redis的value值
        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;
    }*/

    /**
     * JdkSerializationRedisSerializer: 序列化java对象(被序列化的对象必须实现Serializable接口),无法转义成对象
     */
    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(factory);

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

2、业务代码:

/**
 * springcache优雅实现缓存
 */
@Service("TestProvincesService")
@CacheConfig(cacheNames="province") //通用配置
public class ProvincesServiceImpl2 extends ProvincesServiceImpl implements ProvincesService{
//    @Cacheable(value = "province",
//            key = "#root.targetClass.simpleName+':'+#root.methodName+':'+#provinceid")
    @Cacheable// value指定当前接口,要使用哪一个缓存器 --- 如果该缓存器不存在,则创建一个
    public Provinces detail(String provinceid) {//一个接口方法,对应一个缓存器
        return super.detail(provinceid);
    }

    //这个AOP,是先删缓存,先改数据库?先删库,在删缓存
    @CachePut(key = "#entity.provinceid")
    public Provinces update(Provinces entity) {
        return super.update(entity);
    }

    @CacheEvict//先删库,在删缓存
    public void delete(String provinceid) {
        super.delete(provinceid);
    }

    //组合配置
    @Caching(put = {
            @CachePut(key = "#entity.provinceid"),
            @CachePut(key = "#entity.provinceid")}
    )
    public Provinces add(Provinces entity) {
        return super.add(entity);
    }

这就是注解的缓存解决方案,使用起来很方便,当然凡是都有两面性;相比逻辑代码的实现方案,可读性 差,灵活性小,因此缓存解决方法,一定要根据具体业务场景选择使用。

 
 
 
 
 
 
 
 
 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

寅灯

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值