黑马程序员技术交流社区

标题: 【石家庄校区】简单介绍Spring Cache结合redis的使用 [打印本页]

作者: 张荫    时间: 2017-11-7 21:00
标题: 【石家庄校区】简单介绍Spring Cache结合redis的使用
本帖最后由 张荫 于 2017-11-7 22:54 编辑

      缓存在我们的项目中运用的比较广泛,一般来说,缓存一般应用于对于数据一致性要求不高,频繁访问,存储信息量不是很大的情形中。redis是目前比较常用的nosql数据库,我们可以使用redis作为我们的缓存服务。
在我们开发的项目中一般来说使用jedis操作redis需要在我们的代码中我们要先查询缓存中的内容如果缓存中有则直接返回如果缓存中没有则需要从数据库中查询在把查询的结果放入缓存中去。并返回结果 代码如下:

[Java] 纯文本查看 复制代码
@Override
        public List<TbContent> getContentListByCid(long cid) {
                //先查询缓存
                try {
                        String json = jedisClient.hget(CONTENT_KEY, cid+"");
                        //判断是否命中缓存
                        if(StringUtils.isNotBlank(json)){
                                //吧json转换成list
                                List<TbContent> list = JsonUtils.jsonToList(json, TbContent.class);
                                return list;
                        }
                } catch (Exception e) {
                        e.printStackTrace();
                }
                // 根据分类id查询内容列表
                TbContentExample example =new TbContentExample();
                //设置查询条件
                Criteria createCriteria = example.createCriteria();
                createCriteria.andCategoryIdEqualTo(cid);
                //执行查询
                List<TbContent> list = contentMapper.selectByExample(example);
                //像缓存中保存结果
                try {
                        jedisClient.hset(CONTENT_KEY, cid+"", JsonUtils.objectToJson(list));
                } catch (Exception e) {
                        e.printStackTrace();
                }
                return list;
        }

这个代码有一个问题就是我们查询数据库和查询缓存的耦合度很高,每次都需要去手工实现缓存中是否有数据的判断,那么有没有更简单的办法实现缓存的功能呢?
我们可以使用spring cache来实现 。
首先我们看下刚刚的代码如果用spring cache如何实现:


[Java] 纯文本查看 复制代码
        @Cacheable(value="getContentListByCid",key="#root.caches[0].name.concat(':').concat(#cid)")
        @Override
        public List<TbContent> getContentListByCid(long cid) {
        
                // 根据分类id查询内容列表
                TbContentExample example =new TbContentExample();
                //设置查询条件
                Criteria createCriteria = example.createCriteria();
                createCriteria.andCategoryIdEqualTo(cid);
                //执行查询
                List<TbContent> list = contentMapper.selectByExample(example);
               
                return list;
        }


用spring cache 减小了和我们业务逻辑代码的耦合,我们只需要关注业务逻辑加上注解就可以实现缓存。
那么如何把spring cache加入到我们的项目中去呢?
首先需要引入依赖


[XML] 纯文本查看 复制代码
<dependency>
    <groupId>org.springframework.data</groupId>
    <artifactId>spring-data-redis</artifactId>
    <version>1.7.2.RELEASE</version>
</dependency>


配置spring cache配置文件

[XML] 纯文本查看 复制代码

        <cache:annotation-driven />
        <!-- Jedis线程 -->
        <bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig">
                <property name="maxIdle" value="${redis.maxIdle}" />
                <property name="minIdle" value="${redis.minIdle}" />
                <property name="maxTotal" value="${redis.maxTotal}" />
                <property name="testOnBorrow" value="true" />
        </bean>

        <!-- 单机版配置 begin -->
         <bean id="jedisShardInfo" class="redis.clients.jedis.JedisShardInfo">
                <constructor-arg index="0" value="${redis.host}" />
                <constructor-arg index="1" value="${redis.port}" type="int" />
        </bean>
        <bean id="jedisConnectionFactory"
                class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
                <property name="shardInfo" ref="jedisShardInfo" />
                <property name="poolConfig" ref="jedisPoolConfig" />
        </bean>
        <!-- 单机版配置 end -->


        <!-- 缓存序列化方式 -->
        <bean id="keySerializer"
                class="org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer" />
        <bean id="valueSerializer"
                class="org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer" />
        <!-- 缓存 -->
        <bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate">
                <property name="connectionFactory" ref="jedisConnectionFactory" />
                <property name="keySerializer" ref="keySerializer" />
                <property name="valueSerializer" ref="valueSerializer" />
                <property name="hashKeySerializer" ref="keySerializer" />
                <property name="hashValueSerializer" ref="valueSerializer" />
        </bean>
        <bean id="cacheManager" class="org.springframework.data.redis.cache.RedisCacheManager">
                <constructor-arg index="0" ref="redisTemplate" />
                <property name="defaultExpiration" value="${redis.expiration}" />
        </bean>

properties配置文件:
[Java] 纯文本查看 复制代码
# Redis settings
redis.host=192.168.25.128
redis.port=6379
redis.pass=


redis.maxIdle=30
redis.minIdle=2
redis.maxActive=60
redis.maxWait=1000
redis.maxTotal=100
redis.testOnBorrow=true
redis.expiration=36000


之后我们就可以使用 spring cache 的相关注解了
那么下面就说下 常用的几个注解:

@Cacheable
@Cacheable可以标记在一个方法上,也可以标记在一个类上。当标记在一个方法上时表示该方法是支持缓存的,当标记在一个类上时则表示该类所有的方法都是支持缓存的。对于一个支持缓存的方法,Spring会在其被调用后将其返回值缓存起来,以保证下次利用同样的参数来执行该方法时可以直接从缓存中获取结果,而不需要再次执行该方法。Spring在缓存方法的返回值时是以键值对进行缓存的,值就是方法的返回结果,至于键的话,Spring又支持两种策略,默认策略和自定义策略。@Cacheable可以指定三个属性,value、key和condition。
value属性是必须指定的,其表示当前方法的返回值是会被缓存在哪个Cache上的,对应Cache的名称。
其可以是一个Cache也可以是多个Cache,当需要指定多个Cache时其是一个数组。
[Java] 纯文本查看 复制代码
  @Cacheable("cache1")//Cache是发生在cache1上的
   public User find(Integer id) {
      returnnull;
   }


   @Cacheable({"cache1", "cache2"})//Cache是发生在cache1和cache2上的
   public User find(Integer id) {
      returnnull;
   }


  key属性是用来指定Spring缓存方法的返回结果时对应的key的。
该属性支持SpringEL表达式。当我们没有指定该属性时,Spring将使用默认策略生成key。自定义策略是指我们可以通过Spring的EL表达式来指定我们的key。这里的EL表达式可以使用方法参数及它们对应的属性。使用方法参数时我们可以直接使用“#参数名”或者“#p参数index”。下面是几个使用参数作为key的示例。



[Java] 纯文本查看 复制代码
@Cacheable(value="users", key="#id")
   public User find(Integer id) {
      returnnull;
   }


   @Cacheable(value="users", key="#p0")
   public User find(Integer id) {
      returnnull;
   }


   @Cacheable(value="users", key="#user.id")
   public User find(User user) {
      returnnull;
   }


   @Cacheable(value="users", key="#p0.id")
   public User find(User user) {
      returnnull;
   }



除了上述使用方法参数作为key之外,Spring还为我们提供了一个root对象可以用来生成key。通过该root对象我们可以获取到以下信息。

属性名称
描述
示例
methodName
当前方法名
#root.methodName
method
当前方法
#root.method.name
target
当前被调用的对象
#root.target
targetClass
当前被调用的对象的class
#root.targetClass
args
当前方法参数组成的数组
#root.args[0]
caches
当前被调用的方法使用的Cache
#root.caches[0].name



condition属性指定发生的条件
       有的时候我们可能并不希望缓存一个方法所有的返回结果。通过condition属性可以实现这一功能。condition属性默认为空,表示将缓存所有的调用情形。其值是通过SpringEL表达式来指定的,当为true时表示进行缓存处理;当为false时表示不进行缓存处理,即每次调用该方法时该方法都会执行一次。如下示例表示只有当user的id为偶数时才会进行缓存。
[Java] 纯文本查看 复制代码
@Cacheable(value={"users"}, key="#user.id", condition="#user.id%2==0")
public User find(User user) {
System.out.println("find user by user " + user);
return user;
}

@CachePut
@CachePut标注的方法在执行前不会去检查缓存中是否存在之前执行过的结果,而是每次都会执行该方法,并将执行结果以键值对的形式存入指定的缓存中。@CachePut也可以标注在类上和方法上。使用@CachePut时我们可以指定的属性跟@Cacheable是一样的。
[Java] 纯文本查看 复制代码
@CachePut("users")//每次都会执行方法,并将结果存入指定的缓存中
public User find(Integer id) {
returnnull;
}


@CacheEvict
@CacheEvict是用来标注在需要清除缓存元素的方法或类上的。当标记在一个类上时表示其中所有的方法的执行都会触发缓存的清除操作。@CacheEvict可以指定的属性有value、key、condition、allEntries和beforeInvocation。其中value、key和condition的语义与@Cacheable对应的属性类似。即value表示清除操作是发生在哪些Cache上的(对应Cache的名称);key表示需要清除的是哪个key,如未指定则会使用默认策略生成的key;condition表示清除操作发生的条件。下面我们来介绍一下新出现的两个属性allEntries。
allEntries属性 allEntries是boolean类型,表示是否需要清除缓存中的所有元素。默认为false,表示不需要。当指定了allEntries为true时,Spring Cache将忽略指定的key。有的时候我们需要Cache一下清除所有的元素,这比一个一个清除元素更有效率。
[Java] 纯文本查看 复制代码
 @CacheEvict(value="users", allEntries=true)
public void delete(Integer id) {
System.out.println("delete user by id: " + id);
}







作者: Yin灬Yan    时间: 2017-11-8 18:45
我来占层楼啊  
作者: 小石姐姐    时间: 2017-11-10 14:44
棒棒哒,纯干货
作者: 张荫    时间: 2017-11-11 21:37
小石姐姐 发表于 2017-11-10 14:44
棒棒哒,纯干货

谢谢支持




欢迎光临 黑马程序员技术交流社区 (http://bbs.itheima.com/) 黑马程序员IT技术论坛 X3.2