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>

1,首先我们在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>

2.我们来写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";
            }
        });
    }
}

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

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

4.运行结果

这个就是我们存的key

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

---------------------------------------------------------key的生成策略-----------------------------------------------------------

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

默认策略:

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

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

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

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

自定义策略:

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

以上就是本博主研究了半天成果,研究过程中很快乐!,如果本博客写的不是很好,希望小伙伴们指出来,博主会进行更改,也欢迎小伙伴进行讨论,共同进步!!!

未经本人同意不得转载

  • 3
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值