Spring数据缓存之注解@Cacheable、@CachePut、@CacheEvict入门篇

目录

 

前言

通过XML启用注解驱动的缓存

缓存管理器

基于SimpleCacheManager的XML配置示例1

为方法添加注解以支持缓存

填充缓存

自定义缓存key

条件化缓存

移除缓存条目

使用XML声明缓存


前言

如果想让应用程序避免一遍遍地为同一个问题推导、计算或查询答案的话,缓存是一种很棒的方式。当以一组参数第一次调用某个方法时,返回值会被保存在缓存中,如果这个方法再次以相同的参数进行调用时,这个返回值会从缓存中查询获取。在很多场景中,从缓存查找值会比其他的方式(比如,执行数据库查询)成本更低。因此,缓存会对应用程序的性能带来正面的影响。

通过XML启用注解驱动的缓存

使用XML的方式配置时,需要使用Spring cache命名空间中的<cache:annotation-driven>元素来启用注解驱动的缓存。从本质上来讲,其工作方式它是会创建一个切面(aspect)并触发Spring缓存注解的切点(pointcut)。根据所使用的注解以及缓存的状态,这个切面会从缓存中获取数据,将数据添加到缓存之中或者从缓存中移除某个值。

<cache:annotation-driven>

缓存管理器

缓存管理器是Spring缓存抽象的核心,它能够与多个流行的缓存实现进行集成。Spring常见管理器如下表所示:

缓存管理器名称

引入Spring版本

SimpleCacheManager

3.1

NoOpCacheManager

3.1

ConcurrentMapCacheManager

3.1

CompositeCacheManager

3.1

EhCacheCacheManager

3.1

RedisCacheManager

3.2

GemfireCacheManager

3.2

对于上表中ConcurrentMapCacheManager,这是一个简单的缓存管理器使用java.util.concurrent.ConcurrentHashMap作为其缓存存储。它非常简单,因此对于开发、测试或基础的应用来讲,这是一个很不错的选择。但它的缓存存储是基于内存的,所以它的生命周期是与应用关联的,对于生产级别的大型企业级应用程序,这可能并不是理想的选择。

基于SimpleCacheManager的XML配置示例1

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:cache="http://www.springframework.org/schema/cache" xmlns:p="http://www.springframework.org/schema/p"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/mvc
        http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache.xsd">

    <cache:annotation-driven/>
    <bean id="cacheManager" class="org.springframework.cache.support.SimpleCacheManager">
        <property name="caches">
            <set>
                <bean class="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean" p:name="users"/>
            </set>
        </property>
    </bean>

</beans

为方法添加注解以支持缓存

如前文所述,Spring的缓存抽象在很大程度上是围绕切面构建的。在Spring中启用缓存时,会创建一个切面,它触发一个或更多的Spring的缓存注解。下表列出了Spring所提供的缓存注解。

注解

描述

@Cacheable

表明Spring在调用方法之前,首先应该在缓存中查找方法的返回值。如果这个值能够找到,就会返回缓存的值。否则的话,这个方法就会被调用,返回值会放到缓存之中

@CachePut

表明Spring应该将方法的返回值放到缓存中。在方法的调用前并不会检查缓存,方法始终都会被调用

@CacheEvict

表明Spring应该在缓存中清除一个或多个条目

@Caching

这是一个分组的注解,能够同时应用多个其他的缓存注解

填充缓存

可以看到,@Cacheable和@CachePut注解都可以填充缓存,但是它们的工作方式略有差异。

@Cacheable首先在缓存中查找条目,如果找到了匹配的条目,那么就不会对方法进行调用了。如果没有找到匹配的条目,方法会被调用并且返回值要放到缓存之中。而@CachePut并不会在缓存中检查匹配的值,目标方法总是会被调用,并将返回值添加到缓存之中。@Cacheable和@CachePut有一些属性是共有的,如下表所示:

属性

类型

描述

value

String[]

要使用的缓存名称

condition

String

SpEL表达式,如果得到的值是false的话,不会将缓存应用到方法调用上

key

String

SpEL表达式,用来计算自定义的缓存key

unless

String

SpEL表达式,如果得到的值是true的话,返回值不会放到缓存之中

在最简单的情况下,在@Cacheable和@CachePut的这些属性中,只需使用value属性指定一个或多个缓存即可。例如,考虑UserDao的findById(Integer id)方法。在初始保存之后,User数据表就不会再发生变化了。如果有的用户会被频繁请求,反复地在数据库中进行获取是对时间和资源的浪费。通过在findById(Integer id)方法上添加@Cacheable注解,如下面的程序清单所示,能够确保将User对象保存在缓存users中,从而避免对数据库的不必要访问。

@Cacheable(value="users")
public User findUserById(int id) {
    String sql="select * from t_user where id=?";
    return jdbcTemplate.queryForObject(sql, new UserRowMapper(), id);
}

class UserRowMapper implements RowMapper<User> {
    //rs为返回结果集,以每行为单位封装着
    @Override
    public User mapRow(ResultSet rs, int rowNum) throws SQLException {
        User user = new User();
        user.setID(rs.getInt("id"));
        user.setUserName(rs.getString("name"));
        user.setUserPwd(rs.getString("pwd"));
        return user;
    }
}

当findUserById(int id)被调用时,缓存切面会拦截调用并在缓存中查找之前以名users存储的返回值。缓存的key是传递到findUserById(int id)方法中的id参数。如果按照这个key能够找到值的话,就会返回找到的值,方法不会再被调用。如果没有找到值的话,那么就会调用这个方法,并将返回值放到缓存之中,为下一次调用findUserById(int id)方法做好准备。

当@Cacheable为接口方法添加注解后,所有实现类都会应用相同的缓存规则。

当一个全新的User对象通过addUser(User user)方法保存之后,很可能马上就会请求这条记录。所以,当save()方法调用后,立即将user塞到缓存之中是很有意义的,这样当其他人通过findUserById(int id)对其进行查找时,它就已经准备就绪了。为了实现这一点,可以在addUser(User user)方法上添加@CachePut注解,如下所示:

@CachePut(value="users")
public void addUser(User user) {
    String sql = "insert into t_user values(?,?,?)";
    jdbcTemplate.update(sql, null,user.getUserName(),user.getUserPwd());
    return user;
}

 当addUser(User user)方法被调用时,它首先会做所有必要的事情来保存user对象,然后返回的user会被放到users缓存中。在这里只有一个问题:缓存的key。如前文所述,默认的缓存key要基于方法的参数来确定。因为addUser(User user)方法的唯一参数就是user,所以它会用作缓存的key。将users放在缓存中,而它的缓存key恰好是同一个user,这是不是有一点诡异呢?显然,在这个场景中,默认的缓存key并不是我们想要的。我们需要的缓存key是新保存user的ID,而不是user本身。所以,在这里需要指定一个key而不是使用默认的key。让我们看一下怎样自定义缓存key。

自定义缓存key

@Cacheable和@CachePut都有一个名为key属性,这个属性能够替换默认的key,它是通过一个SpEL表达式计算得到的。任意的SpEL表达式都是可行的,但是更常见的场景是所定义的表达式与存储在缓存中的值有关,据此计算得到key。

具体到我们这个场景,我们需要将key设置为所保存user的ID。以参数形式传递给addUser(User user)的user还没有保存,因此并没有ID。我们只能通过addUser(User user)返回的user得到id属性。

幸好,在为缓存编写SpEL表达式的时候,Spring暴露了一些很有用的元数据。下表列出了SpEL中可用的缓存元数据。

表达式

描述

#root.args

传递给缓存方法的参数,形式为数组

#root.caches

该方法执行时所对应的缓存,形式为数组

#root.target

目标对象

#root.targetClass

目标对象的类,是 #root.target.class的简写形式

#root.method

缓存方法

#root.methodName

缓存方法的名字,是 #root.method.name的简写形式

#result

方法调用的返回值(不能用在 @Cacheable注解上)

#Argument

任意的方法参数名(如 #argName)或参数索引(如#a0或#p0)

对于addUser(User user)方法来说,我们需要的键是所返回user对象的id属性。表达式#result能够得到返回的user。借助这个对象,我们可以通过将key属性设置为#result.id来引用id属性:

@CachePut(value="users",key="#result.id")
  public void addUser(User user) {
        String sql = "insert into t_user values(?,?,?)";
        jdbcTemplate.update(sql, null,user.getUserName(),user.getUserPwd());
        return user;
    }

 按照这种方式配置@CachePut,缓存不会去干涉addUser(User user)方法的执行,但是返回的user将会保存在缓存中,并且缓存的key与user的id属性相同。

条件化缓存

通过为方法添加Spring的缓存注解,Spring就会围绕着这个方法创建一个缓存切面。但是,在有些场景下我们可能希望将缓存功能关闭。

@Cacheable和@CachePut提供了两个属性用以实现条件化缓存:unless和condition,这两个属性都接受一个SpEL表达式。如果unless属性的SpEL表达式计算结果为true,那么缓存方法返回的数据就不会放到缓存中。与之类似,如果condition属性的SpEL表达式计算结果为false,那么对于这个方法缓存就会被禁用掉。

表面上来看,unless和condition属性做的是相同的事情。但是,这里有一点细微的差别。unless属性只能阻止将对象放进缓存,但是在这个方法调用的时候,依然会去缓存中进行查找,如果找到了匹配的值,就会返回找到的值。与之不同,如果condition的表达式计算结果为false,那么在这个方法调用的过程中,缓存是被禁用的。就是说,不会去缓存进行查找,同时返回值也不会放进缓存中。

移除缓存条目

@CacheEvict并不会往缓存中添加任何东西。相反,如果带有@CacheEvict注解的方法被调用的话,那么会有一个或更多的条目会在缓存中移除。

那么在什么场景下需要从缓存中移除内容呢?当缓存值不再合法时,我们应该确保将其从缓存中移除,这样的话,后续的缓存命中就不会返回旧的或者已经不存在的值,其中一个这样的场景就是数据被删除掉了。这样的话,UserDao的deleteUser(int id)方法就是使用@CacheEvict的绝佳选择:

@CacheEvict(value="users")
public void deleteUser(int id) {
    String sql = "delete from t_user where id = ?";
    jdbcTemplate.update(sql, id);
}

 注意:与@Cacheable和@CachePut不同,@CacheEvict能够应用在返回值为void的方法上,而@Cacheable和@CachePut需要非void的返回值,它将会作为放在缓存中的条目。因为@CacheEvict只是将条目从缓存中移除,因此它可以放在任意的方法上,甚至void方法。

从上述代码可以看到,当deleteUser()调用时,会从缓存中删除一个条目。被删除条目的key与传递进来的id参数的值相等。

@CacheEvict有多个属性,如下表所示,这些属性会影响到该注解的行为,使其不同于默认的做法。可以看到,@CacheEvict的一些属性与@Cacheable和@CachePut是相同的,另外还有几个新的属性。与@Cacheable和@CachePut不同,@CacheEvict并没有提供unless属性。

属性

类型

描述

value

String []

要使用的缓存名称

key

String

SpEL表达式,用来计算自定义的缓存key

condition

String

SpEL表达式,如果得到的值是false的话,缓存不会应用到方法调用上

allEntries

boolean

如果为true的话,特定缓存的所有条目都会被移除掉

beforeInvocation

boolean

如果为true的话,在方法调用之前移除条目。如果为 false(默认值)的话,在方法成功调用之后再移除条目

使用XML声明缓存

Spring的cache命名空间提供了使用XML声明缓存规则的方法,可以作为面向注解缓存的替代方案。因为缓存是一种面向切面的行为,所以cache命名空间会与Spring的aop命名空间结合起来使用,用来声明缓存所应用的切点在哪里。

要开始配置XML声明的缓存,首先需要创建Spring配置文件,这个文件中要包含cache和aop命名空间。cache命名空间定义了在Spring XML配置文件中声明缓存的配置元素。如下表所示:

元素

描述

<cache:annotation-driven>

启用注解驱动的缓存。等同于Java配置中的 @EnableCaching

<cache:advice>

定义缓存通知(advice)。结合 <aop:advisor>,将通知应用到切点上

<cache:caching>

在缓存通知中,定义一组特定的缓存规则

<cache:cacheable>

指明某个方法要进行缓存。等同于 @Cacheable注解

<cache:cache-put>

指明某个方法要填充缓存,但不会考虑缓存中是否已有匹配的值。等同于 @CachePut注解

<cache:cache-evict>

指明某个方法要从缓存中移除一个或多个条目,等同于 @CacheEvict注解

<!--将缓存通知绑定到切点上-->
<aop:config>
    <aop:advisor advice-ref="cacheAdvice" pointcut="execution(* com.boss.spring.learning.dao.*(..))"/>
</aop:config>

<cache:advice id="cacheAdvice">
    <cache:caching>
        <cache:cacheable cache="users" method="addUser"></cache:cacheable>
        <cache:cache-put cache="users" method="addUser" key="#result.id"></cache:cache-put>
        <cache:cache-evict cache="users" method="deleteUser"></cache:cache-evict>
    </cache:caching>
</cache:advice>

<bean id="cacheManager" class="org.springframework.cache.support.SimpleCacheManager">
    <property name="caches">
        <set>
            <bean class="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean" p:name="users"/>
        </set>
    </property>
</bean>

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值