spring的Cache注解和redis的区别说明

 更新时间:2021年12月31日 10:52:08   作者:吧唧小猪  
这篇文章主要介绍了spring的Cache注解和redis的区别说明,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教

spring Cache注解和redis区别

1.不支持TTL

即不能设置过期时间 expires time,SpringCache 认为这是各个Cache实现自己去完成的事情,有方案但是只能设置统一的过期时间,明显不够灵活。

2.内部调用

非 public 方法上使用注解,会导致缓存无效。内部调用方法的时候不会调用cache方法。

由于 SpringCache 是基于 Spring AOP 的动态代理实现,由于代理本身的问题,当同一个类中调用另一个方法,会导致另一个方法的缓存不能使用,这个在编码上需要注意,避免在同一个类中这样调用。如果非要这样做,可以通过再次代理调用,如 ((Category)AopContext.currentProxy()).get(category) 这样避免缓存无效。

3.key的问题

在清除缓存的时候,无法指定多个缓存块,同时清除多个缓存的key。

Spring Cache注解+redis整合及遇到的坑

背景:项目经理让我做缓存,我心想不是有redis吗?他说你把Spring Cache注解结合进来,我只好硬着头皮来做。

先介绍Spring Cache注解

从3.1开始,Spring引入了对Cache的支持。其使用方法和原理都类似于Spring对事务管理的支持。Spring Cache是作用在方法上的,其核心思想是这样的:当我们在调用一个缓存方法时会把该方法参数和返回结果作为一个键值对存放在缓存中,等到下次利用同样的参数来调用该方法时将不再执行该方法,而是直接从缓存中获取结果进行返回。所以在使用Spring Cache的时候我们要保证我们缓存的方法对于相同的方法参数要有相同的返回结果。

Spring为我们提供了几个注解来支持Spring Cache。其核心主要是@Cacheable和@CacheEvict。使用@Cacheable标记的方法在执行后Spring Cache将缓存其返回结果,而使用@CacheEvict标记的方法会在方法执行前或者执行后移除Spring Cache中的某些元素。下面我们将来详细介绍一下Spring基于注解对Cache的支持所提供的几个注解。

根据项目情况只用到@Cacheable,这里面一共有几个常用的参数,一个是value,这一定必须指定其表示当前方法的返回值是会被缓存在哪个Cache上的,对应Cache的名称。其可以是一个Cache也可以是多个Cache,当需要指定多个Cache时其是一个数组。

还有就是key,key属性是用来指定Spring缓存方法的返回结果时对应的key的。该属性支持SpringEL表达式。当我们没有指定该属性时,Spring将使用默认策略生成key。

我们这里先来看看自定义策略,至于默认策略会在后文单独介绍自定义策略是指我们可以通过Spring的EL表达式来指定我们的key。

这里的EL表达式可以使用方法参数及它们对应的属性。使用方法参数时我们可以直接使用“#参数名”或者“#p参数index”。最后一个就是condition,有的时候我们可能并不希望缓存一个方法所有的返回结果。通过condition属性可以实现这一功能。condition属性默认为空,表示将缓存所有的调用情形。

其值是通过SpringEL表达式来指定的,当为true时表示进行缓存处理;当为false时表示不进行缓存处理,即每次调用该方法时该方法都会执行一次。还有几个别的注解@CacheEvict,@CachePut,基本跟@Cacheable一样只是作用不一样换汤不换药。

当你看到这的时候我相信你,你已经对Spring缓存注解了解的很多了。下面就来配置Spring注解与Redis来整合。(本人不做配置Redis)

配置Spring注解与Redis整合

先导入jar包

<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>2.9.0</version>
</dependency>
<dependency>
    <groupId>org.springframework.data</groupId>
    <artifactId>spring-data-redis</artifactId>
    <version>1.6.0.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-lang3</artifactId>
    <version>3.3.2</version>
</dependency>

我们在Spring-config.xml中来配置

xmlns:cache="http://www.springframework.org/schema/cache"
http://www.springframework.org/schema/cache
http://www.springframework.org/schema/cache/spring-cache-4.2.xsd"
<cache:annotation-driven cache-manager="cacheManager" /> //这句话一定要加上,要不注解不管用
<bean id="cacheManager" class="org.springframework.cache.support.SimpleCacheManager">
    <property name="caches">
        <set>
            <bean class="com.zswy.mkedu.utils.RedisCache"> 
                <property name="redisTemplate" ref="redisTemplate" />
                <!--name一定要与你注解那个方法中value一样,否则找不到-->
                <property name="name" value="courseCache" />
            </bean>
        </set>
    </property>
</bean>

来写RedisCache这个类,让其实现Cache接口

public class RedisCache implements Cache{ 
    private RedisTemplate<String, Object> redisTemplate;
    private String name;
    public RedisTemplate<String, Object> getRedisTemplate() {
        return redisTemplate;
    }
 
    public void setRedisTemplate(RedisTemplate<String, Object> redisTemplate) {
        this.redisTemplate = redisTemplate;
    }
 
    public void setName(String name) {
        this.name = name;
    }
 
    public String getName() {
        return this.name;
    }
 
    public Object getNativeCache() {
        return this.redisTemplate;
    }
 
    public ValueWrapper get(Object key) {
        final String keyf = (String) key;
        Object object = null;
        object = redisTemplate.execute(new RedisCallback<Object>() {
            public Object doInRedis(RedisConnection connection)
                    throws DataAccessException {
 
                byte[] key = keyf.getBytes();
                byte[] t = name.getBytes();
                byte[] value = connection.hGet(t, key);
                if (value == null) {
                    return null;
                }
                return toObject(value);
            }
        });
        ValueWrapper obj=(object != null ? new SimpleValueWrapper(object) : null);
        return obj;
    }
 
    public <T> T get(Object o, Class<T> aClass) {
        return null;
    }
 
    public void put(Object key, Object value) {
        final String keyf = (String) key;
        final Object valuef = value;
        final long liveTime = 86400;
        redisTemplate.execute(new RedisCallback<Long>() {
            public Long doInRedis(RedisConnection connection)
                    throws DataAccessException {
                byte[] keyb = keyf.getBytes();
                byte[] valueb = toByteArray(valuef);
                byte[] t = name.getBytes();
                connection.hSet(t, keyb, valueb);
                if (liveTime > 0) {
                    connection.expire(keyb, liveTime);
                }
                return 1L;
            }
        });
    }
 
    private byte[] toByteArray(Object obj) {
        byte[] bytes = null;
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        try {
            ObjectOutputStream oos = new ObjectOutputStream(bos);
            oos.writeObject(obj);
            oos.flush();
            bytes = bos.toByteArray();
            oos.close();
            bos.close();
        } catch (IOException ex) {
            ex.printStackTrace();
        }
        return bytes;
    }
 
    private Object toObject(byte[] bytes) {
        Object obj = null;
        try {
            ByteArrayInputStream bis = new ByteArrayInputStream(bytes);
            ObjectInputStream ois = new ObjectInputStream(bis);
            obj = ois.readObject();
            ois.close();
            bis.close();
        } catch (IOException ex) {
            ex.printStackTrace();
        } catch (ClassNotFoundException ex) {
            ex.printStackTrace();
        }
        return obj;
    }
 
    public ValueWrapper putIfAbsent(Object o, Object o1) {
        return null;
    }
 
    public void evict(Object key) {
        final String keyf = (String) key;
        redisTemplate.execute(new RedisCallback<Long>() {
            public Long doInRedis(RedisConnection connection)
                    throws DataAccessException {
                return connection.hDel(name.getBytes(), keyf.getBytes());
            }
        });
    }
 
    public void clear() {
        // 清楚缓存,需要根据Cache的name属性,在redis中模糊查询相关key值的集合,并全部删除
        redisTemplate.execute(new RedisCallback<String>() {
            public String doInRedis(RedisConnection connection)
                    throws DataAccessException {
                byte[] t = name.getBytes();
                Set<byte[]> fields = connection.hKeys(t);
                for (byte[] bs : fields) {
                    connection.hDel(t, bs);
                }
                return "ok";
            }
        });
    }
}

开始使用注解,我是把其作用在倒dao层中

注意:这有一个坑:一般我们都是三层架构,service层调dao层,我一开始把注解加到Service实现层,运行的时候Cache机制不触发。我开始大量百度,查书,最后发现根本不会触发,因为Spring把实现类装载成为Bean的时候,会用代理包装一下,所以从Spring Bean的角度看,只有接口里面的方法是可见的,其它的都隐藏了,自然课看不到实现类里面的非接口方法,最后我把注解放到dao层上

这里的key我使用的方法名,一般会使用参数的属性,value就是上文在配置文件里配置的name。这样所有的配置都配好了

运行结果

这个就是我们存的key

第二个坑就是,你的实体类一定要序列化 实现Serializable,否则就报空指针异常。这个一定要记住

key的生成策略

键的生成策略有两种,一种是默认策略,一种是自定义策略。

默认策略:

默认的key是通过KeyGenerator生成的,其默认策略如下:

1.如果方法没有参数,则使用0作为key;

2.如果只有一个参数的话则使用该参数作为key;

3.如果参数多于一个则使用所有参数的hashcode作为key;

自定义策略:

自定义策略是指我们通过Spring的EL表达式来指定我们的key。这里的EL表达式可以使用参数以及它们对应的属性。使用方法参数时我们可以直接使用“#参数名”或者“#p参数index”。

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

相关文章

  • 关于BeanUtils.copyProperties(source, target)的使用

    关于BeanUtils.copyProperties(source, target)的使用

    这篇文章主要介绍了关于BeanUtils.copyProperties(source, target)的使用说明,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-06-06
  • Java实现在线聊天室(层层递进)

    Java实现在线聊天室(层层递进)

    这篇文章主要为大家详细介绍了Java实现在线聊天室,层层递进,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-09-09
  • Springboot 2使用外部Tomcat源码分析

    Springboot 2使用外部Tomcat源码分析

    这篇文章主要介绍了Springboot 2使用外部Tomcat源码分析,本文给大家介绍的非常详细,具有一定的参考借鉴价值,需要的朋友可以参考下
    2019-08-08
  • Spring AI借助全局参数实现智能数据库操作与个性化待办管理

    Spring AI借助全局参数实现智能数据库操作与个性化待办管理

    这篇文章主要介绍了Spring AI借助全局参数实现智能数据库操作与个性化待办管理,本文给大家介绍的非常详细,需要的朋友可以参考下
    2024-11-11
  • Mysql json类型字段Java+Mybatis数据字典功能的实践方式

    Mysql json类型字段Java+Mybatis数据字典功能的实践方式

    这篇文章主要介绍了Mysql json类型字段Java+Mybatis数据字典功能的实践方式,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-08-08
  • Spring Boot常见外部配置文件方式详析

    Spring Boot常见外部配置文件方式详析

    这篇文章主要给大家介绍了关于Spring Boot常见外部配置文件方式的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者使用Spring Boot具有一定的参考学习价值,需要的朋友们下面来一起学习学习吧
    2020-07-07
  • 图解Java经典算法归并排序的原理与实现

    图解Java经典算法归并排序的原理与实现

    归并排序是建立在归并操作上的一种有效的排序算法。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。本文将通过动图详解归并排序的原理及实现,需要的可以参考一下
    2022-09-09
  • idea运行vue项目设置自定义浏览器方式

    idea运行vue项目设置自定义浏览器方式

    这篇文章主要介绍了idea运行vue项目设置自定义浏览器方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2024-08-08
  • Java实战之实现文件资料上传并生成缩略图

    Java实战之实现文件资料上传并生成缩略图

    这篇文章主要介绍了通过Java实现文件资料的上传并生成一个缩略图,文中的示例代码讲解详细,对我们学习Java有一定的帮助,感兴趣的小伙伴可以了解一下
    2021-12-12
  • 解决maven没有打包xml文件的问题

    解决maven没有打包xml文件的问题

    这篇文章主要介绍了解决maven没有打包xml文件的问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-11-11

最新评论