好,到目前为止,我们的 spring cache 缓存程序已经运行成功了,但是还不完美,因为还缺少一个重要的缓存管理逻辑:清空缓存.

当账号数据发生变更,那么必须要清空某个缓存,另外还需要定期的清空所有缓存,以保证缓存数据的可靠性。

为了加入清空缓存的逻辑,我们只要对 AccountService2.java 进行修改,从业务逻辑的角度上看,它有两个需要清空缓存的地方

  • 当外部调用更新了账号,则我们需要更新此账号对应的缓存

  • 当外部调用说明重新加载,则我们需要清空所有缓存

我们在AccountService2的基础上进行修改,修改为AccountService3,代码如下:

import com.google.common.base.Optional;

import com.rollenholt.spring.cache.example1.Account;

import org.slf4j.Logger;

import org.slf4j.LoggerFactory;

import org.springframework.cache.annotation.CacheEvict;

import org.springframework.cache.annotation.Cacheable;

import org.springframework.stereotype.Service;

/**

* @author wenchao.ren

*         2015/1/5.

*/

@Service

public class AccountService3 {

private final Logger logger = LoggerFactory.getLogger(AccountService3.class);

// 使用了一个缓存名叫 accountCache

@Cacheable(value="accountCache")

public Account getAccountByName(String accountName) {

// 方法内部实现不考虑缓存逻辑,直接实现业务

logger.info("real querying account... {}", accountName);

Optional<Account> accountOptional = getFromDB(accountName);

if (!accountOptional.isPresent()) {

throw new IllegalStateException(String.format("can not find account by account name : [%s]", accountName));

}

return accountOptional.get();

}

@CacheEvict(value="accountCache",key="#account.getName()")

public void updateAccount(Account account) {

updateDB(account);

}

@CacheEvict(value="accountCache",allEntries=true)

public void reload() {

}

private void updateDB(Account account) {

logger.info("real update db...{}", account.getName());

}

private Optional<Account> getFromDB(String accountName) {

logger.info("real querying db... {}", accountName);

//Todo query data from database

return Optional.fromNullable(new Account(accountName));

}

}

我们的测试代码如下:

import com.rollenholt.spring.cache.example1.Account;

import org.junit.Before;

import org.junit.Test;

import org.slf4j.Logger;

import org.slf4j.LoggerFactory;

import org.springframework.context.support.ClassPathXmlApplicationContext;

public class AccountService3Test {

private AccountService3 accountService3;

private final Logger logger = LoggerFactory.getLogger(AccountService3Test.class);

@Before

public void setUp() throws Exception {

ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext2.xml");

accountService3 = context.getBean("accountService3", AccountService3.class);

}

@Test

public void testGetAccountByName() throws Exception {

logger.info("first query.....");

accountService3.getAccountByName("accountName");

logger.info("second query....");

accountService3.getAccountByName("accountName");

}

@Test

public void testUpdateAccount() throws Exception {

Account account1 = accountService3.getAccountByName("accountName1");

logger.info(account1.toString());

Account account2 = accountService3.getAccountByName("accountName2");

logger.info(account2.toString());

account2.setId(121212);

accountService3.updateAccount(account2);

// account1会走缓存

account1 = accountService3.getAccountByName("accountName1");

logger.info(account1.toString());

// account2会查询db

account2 = accountService3.getAccountByName("accountName2");

logger.info(account2.toString());

}

@Test

public void testReload() throws Exception {

accountService3.reload();

// 这2行查询数据库

accountService3.getAccountByName("somebody1");

accountService3.getAccountByName("somebody2");

// 这两行走缓存

accountService3.getAccountByName("somebody1");

accountService3.getAccountByName("somebody2");

}

}

在这个测试代码中我们重点关注testUpdateAccount()方法,在测试代码中我们已经注释了在update完account2以后,再次查询的时候,account1会走缓存,而account2不会走缓存,而去查询db,观察程序运行日志,运行日志为:

01:37:34.549 [main] INFO  c.r.s.cache.example3.AccountService3 - real querying account... accountName1

01:37:34.551 [main] INFO  c.r.s.cache.example3.AccountService3 - real querying db... accountName1

01:37:34.552 [main] INFO  c.r.s.c.example3.AccountService3Test - Account{id=0, name='accountName1'}

01:37:34.553 [main] INFO  c.r.s.cache.example3.AccountService3 - real querying account... accountName2

01:37:34.553 [main] INFO  c.r.s.cache.example3.AccountService3 - real querying db... accountName2

01:37:34.555 [main] INFO  c.r.s.c.example3.AccountService3Test - Account{id=0, name='accountName2'}

01:37:34.555 [main] INFO  c.r.s.cache.example3.AccountService3 - real update db...accountName2

01:37:34.595 [main] INFO  c.r.s.c.example3.AccountService3Test - Account{id=0, name='accountName1'}

01:37:34.596 [main] INFO  c.r.s.cache.example3.AccountService3 - real querying account... accountName2

01:37:34.596 [main] INFO  c.r.s.cache.example3.AccountService3 - real querying db... accountName2

01:37:34.596 [main] INFO  c.r.s.c.example3.AccountService3Test - Account{id=0, name='accountName2'}

我们会发现实际运行情况和我们预估的结果是一致的。

如何按照条件操作缓存

前面介绍的缓存方法,没有任何条件,即所有对 accountService 对象的 getAccountByName 方法的调用都会起动缓存效果,不管参数是什么值。

如果有一个需求,就是只有账号名称的长度小于等于 4 的情况下,才做缓存,大于 4 的不使用缓存

虽然这个需求比较坑爹,但是抛开需求的合理性,我们怎么实现这个功能呢?

通过查看CacheEvict注解的定义,我们会发现:

/**

* Annotation indicating that a method (or all methods on a class) trigger(s)

* a cache invalidate operation.

*

* @author Costin Leau

* @author Stephane Nicoll

* @since 3.1

* @see CacheConfig

*/

@Target({ElementType.METHOD, ElementType.TYPE})

@Retention(RetentionPolicy.RUNTIME)

@Inherited

@Documented

public @interface CacheEvict {

/**

* Qualifier value for the specified cached operation.

* <p>May be used to determine the target cache (or caches), matching the qualifier

* value (or the bean name(s)) of (a) specific bean definition.

*/

String[] value() default {};

/**

* Spring Expression Language (SpEL) attribute for computing the key dynamically.

* <p>Default is "", meaning all method parameters are considered as a key, unless

* a custom {@link #keyGenerator()} has been set.

*/

String key() default "";

/**

* The bean name of the custom {@link org.springframework.cache.interceptor.KeyGenerator} to use.

* <p>Mutually exclusive with the {@link #key()} attribute.

*/

String keyGenerator() default "";

/**

* The bean name of the custom {@link org.springframework.cache.CacheManager} to use to

* create a default {@link org.springframework.cache.interceptor.CacheResolver} if none

* is set already.

* <p>Mutually exclusive with the {@link #cacheResolver()}  attribute.

* @see org.springframework.cache.interceptor.SimpleCacheResolver

*/

String cacheManager() default "";

/**

* The bean name of the custom {@link org.springframework.cache.interceptor.CacheResolver} to use.

*/

String cacheResolver() default "";

/**

* Spring Expression Language (SpEL) attribute used for conditioning the method caching.

* <p>Default is "", meaning the method is always cached.

*/

String condition() default "";

/**

* Whether or not all the entries inside the cache(s) are removed or not. By

* default, only the value under the associated key is removed.

* <p>Note that setting this parameter to {@code true} and specifying a {@link #key()}

* is not allowed.

*/

boolean allEntries() default false;

/**

* Whether the eviction should occur after the method is successfully invoked (default)

* or before. The latter causes the eviction to occur irrespective of the method outcome (whether

* it threw an exception or not) while the former does not.

*/

boolean beforeInvocation() default false;

}

定义中有一个condition描述:

Spring Expression Language (SpEL) attribute used for conditioning the method caching.Default is “”, meaning the method is always cached.

我们可以利用这个方法来完成这个功能,下面只给出示例代码:

@Cacheable(value="accountCache",condition="#accountName.length() <= 4")// 缓存名叫 accountCache

public Account getAccountByName(String accountName) {

// 方法内部实现不考虑缓存逻辑,直接实现业务

return getFromDB(accountName);

}

注意其中的 condition=”#accountName.length() <=4”,这里使用了 SpEL 表达式访问了参数 accountName 对象的 length() 方法,条件表达式返回一个布尔值,true/false,当条件为 true,则进行缓存操作,否则直接调用方法执行的返回结果。

如果有多个参数,如何进行 key 的组合

我们看看CacheEvict注解的key()方法的描述:

Spring Expression Language (SpEL) attribute for computing the key dynamically. Default is “”, meaning all method parameters are considered as a key, unless a custom {@link #keyGenerator()} has been set.

假设我们希望根据对象相关属性的组合来进行缓存,比如有这么一个场景:

要求根据账号名、密码和是否发送日志查询账号信息

很明显,这里我们需要根据账号名、密码对账号对象进行缓存,而第三个参数“是否发送日志”对缓存没有任何影响。所以,我们可以利用 SpEL 表达式对缓存 key 进行设计

我们为Account类增加一个password 属性, 然后修改AccountService代码:

@Cacheable(value="accountCache",key="#accountName.concat(#password)")

public Account getAccount(String accountName,String password,boolean sendLog) {

// 方法内部实现不考虑缓存逻辑,直接实现业务

return getFromDB(accountName,password);

}

注意上面的 key 属性,其中引用了方法的两个参数 accountName 和 password,而 sendLog 属性没有考虑,因为其对缓存没有影响。

accountService.getAccount("accountName", "123456", true);// 查询数据库

accountService.getAccount("accountName", "123456", true);// 走缓存

accountService.getAccount("accountName", "123456", false);// 走缓存

accountService.getAccount("accountName", "654321", true);// 查询数据库

accountService.getAccount("accountName", "654321", true);// 走缓存

如何做到:既要保证方法被调用,又希望结果被缓存

根据前面的例子,我们知道,如果使用了 @Cacheable 注释,则当重复使用相同参数调用方法的时候,方法本身不会被调用执行,即方法本身被略过了,取而代之的是方法的结果直接从缓存中找到并返回了。

现实中并不总是如此,有些情况下我们希望方法一定会被调用,因为其除了返回一个结果,还做了其他事情,例如记录日志,调用接口等,这个时候,我们可以用 @CachePut 注释,这个注释可以确保方法被执行,同时方法的返回值也被记录到缓存中。

@Cacheable(value="accountCache")

public Account getAccountByName(String accountName) {

// 方法内部实现不考虑缓存逻辑,直接实现业务

return getFromDB(accountName);

}

// 更新 accountCache 缓存

@CachePut(value="accountCache",key="#account.getName()")

public Account updateAccount(Account account) {

return updateDB(account);

}

private Account updateDB(Account account) {

logger.info("real updating db..."+account.getName());

return account;

}

我们的测试代码如下

Account account = accountService.getAccountByName("someone");

account.setPassword("123");

accountService.updateAccount(account);

account.setPassword("321");

accountService.updateAccount(account);

account = accountService.getAccountByName("someone");

logger.info(account.getPassword());

如上面的代码所示,我们首先用 getAccountByName 方法查询一个人 someone 的账号,这个时候会查询数据库一次,但是也记录到缓存中了。然后我们修改了密码,调用了 updateAccount 方法,这个时候会执行数据库的更新操作且记录到缓存,我们再次修改密码并调用 updateAccount 方法,然后通过 getAccountByName 方法查询,这个时候,由于缓存中已经有数据,所以不会查询数据库,而是直接返回最新的数据,所以打印的密码应该是“321”

@Cacheable、@CachePut、@CacheEvict 注释介绍

  • @Cacheable 主要针对方法配置,能够根据方法的请求参数对其结果进行缓存

  • @CachePut 主要针对方法配置,能够根据方法的请求参数对其结果进行缓存,和 @Cacheable 不同的是,它每次都会触发真实方法的调用

-@CachEvict 主要针对方法配置,能够根据一定的条件对缓存进行清空

强大的Spring缓存技术(中)的更多相关文章

  1. 强大的Spring缓存技术(上)

    缓存是实际工作中非常常用的一种提高性能的方法, 我们会在许多场景下来使用缓存. 本文通过一个简单的例子进行展开,通过对比我们原来的自定义缓存和 spring 的基于注释的 cache 配置方法,展现了 ...

  2. 强大的Spring缓存技术(下)

    基本原理 一句话介绍就是Spring AOP的动态代理技术. 如果读者对Spring AOP不熟悉的话,可以去看看官方文档 扩展性 直到现在,我们已经学会了如何使用开箱即用的 spring cache ...

  3. .Net环境下的缓存技术介绍 (转)

    .Net环境下的缓存技术介绍 (转) 摘要:介绍缓存的基本概念和常用的缓存技术,给出了各种技术的实现机制的简单介绍和适用范围说明,以及设计缓存方案应该考虑的问题(共17页) 1         概念 ...

  4. .Net环境下的缓存技术介绍

    .Net环境下的缓存技术介绍 摘要: 介绍缓存的基本概念和常用的缓存技术,给出了各种技术的实现机制的简单介绍和适用范围说明,以及设计缓存方案应该考虑的问题(共17页) 1         概念 1.1 ...

  5. .net环境下的缓存技术-转载!

    摘要: 介绍缓存的基本概念和常用的缓存技术,给出了各种技术的实现机制的简单介绍和适用范围说明,以及设计缓存方案应该考虑的问题(共17页) 1         概念 1.1   缓存能解决的问题 · 性 ...

  6. 详细讲解PHP中缓存技术的应用

    PHP,一门最近几年兴起的web设计脚本语言,由于它的强大和可伸缩性,近几年来得到长足的发展,php相比传统的asp网站,在速度上有绝对的优势,想mssql转6万条数据php如需要40秒,asp不下2 ...

  7. 利用Spring.Net技术打造可切换的分布式缓存读写类

    利用Spring.Net技术打造可切换的Memcached分布式缓存读写类 Memcached是一个高性能的分布式内存对象缓存系统,因为工作在内存,读写速率比数据库高的不是一般的多,和Radis一样具 ...

  8. 在Spring、Hibernate中使用Ehcache缓存(2)

    这里将介绍在Hibernate中使用查询缓存.一级缓存.二级缓存,整合Spring在HibernateTemplate中使用查询缓存.,这里是hibernate3,使用hibernate4类似,不过不 ...

  9. Spring Cache缓存技术的介绍

    缓存用于提升系统的性能,特别适用于一些对资源需求比较高的操作.本文介绍如何基于spring boot cache技术,使用caffeine作为具体的缓存实现,对操作的结果进行缓存. demo场景 本d ...

随机推荐

  1. java静态和动态代理原理

    一.代理概念 为某个对象提供一个代理,以控制对这个对象的访问. 代理类和委托类有共同的父类或父接口,这样在任何使用委托类对象的地方都可以用代理对象替代.代理类负责请求的预处理.过滤.将请求分派给委托类 ...

  2. java上传xls文件

    using System; using System.Collections.Generic; using System.Web; using System.Web.UI; using System. ...

  3. 如果客户端禁用cookie,session还能使用吗?

    记得在以前找工作的时候,可多次被问到如果客户端被禁用cookie,session还能使用吗? 今天终于找到了相关的答案:我们来看一下: session是在服务器段保持会话数据的一种方法,对应的cook ...

  4. 应用EF访问SQLite数据

    创建项目,应用EF访问SQLite 1.创建项目 项目结构初始结构如下图所示,Netage.Data.SQLite 类库项目用于定义访问数据的接口和方法,Netage.SQLiteTest.UI 控制 ...

  5. java中 sleep 与 wait 的区别

    1.所属类不同 sleep是Thread类的方法: wait是Object类的方法: 2.功能不同 sleep是线程用来控制自身流程的,在调用sleep()方法的过程中,线程不会释放对象锁: wait ...

  6. 搭载在webstorm上的go语言开发插件安装

    1. 2.搜索框内搜索go,单击“Browse repositories... ”没有匹配结果(因本人已安装好插件,所以go已经显示在上面了) 3.单击"Manage repositorie ...

  7. {Latex}{Tabular}文本超出表格自动换行

    用p或者m可以控制每列的宽度(需载入array宏包). 要整个表格相对于页面居中,用chngpage宏包的adjustwidth,如下: \documentclass[oneside]{article ...

  8. CSS实现DIV水平自适应居中

    DIV水平自适应居中 <!DOCTYPE html> <html lang="cn"> <head> <meta charset=&quo ...

  9. Qt中让Qwidget置顶的方法

    一般来是说窗体置顶和取消只要        setWindowFlags(Qt::WindowStaysOnTopHint);        setWindowFlags(Qt::Widget); 要 ...

  10. sharepoint webpart

    SharePoint开发中,不仅仅是WebPart,我们都经常会使用的几个关键位置,如下: GAC: C:\Windows\assembly,也就是部署的位置: ISAPI位置,SharePoint ...