好,到目前为止,我们的 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. HttpClientUtils

    import java.io.IOException;import java.io.UnsupportedEncodingException;import java.util.List; import ...

  2. 多行SQL语句拼成一条数据

    将多条数据组合到一行 ) set @sql='select ''roleinfo:''' ) declare cursor1 cursor for select id from Userrole wh ...

  3. 在Android中自定义捕获Application全局异常,可以替换掉系统的强制退出对话框(很有参考价值与实用价值)

    转载自: http://blog.csdn.net/jdsjlzx/article/details/7606423

  4. Winform 中DataGridView控件添加行标题

    有很多种方法. 1.可以在DataGridView控件中的RowStateChanged事件改变行标题单元格的值(Row.HeaderCell.Value) /// <summary> / ...

  5. C#中获得机器的字符编码webName信息

    在一次解决编码的问题过程中,发现Encoding.GetEncoding("GB2312") == Encoding.GetEncoding("GBK"),到底 ...

  6. Linux 驱动学习笔记05--字符驱动实例,实现一个共享内存设备的驱动

    断断续续学驱动,好不容易有空,做了段字符驱动的例子.主要还是跟书上学习在此记录下来,以后说不定能回过头来温故知新. 首先上驱动源码 gmem.c: /************************* ...

  7. Python全栈开发day7

    一.Python生成器/迭代器 1 2 3 4 5 6 7 8 9 10 11 12 #!/bin/env python # -*- coding:utf-8 -*- def shaw(n):     ...

  8. U-Mail反垃圾邮件网关过滤Locky勒索邮件

    近期,不少朋友圈有朋友发布相关的邮件提醒,说有关于Locky病毒勒索邮件的.看来这个病毒影响不小啊!下面就说说怎么来防止Locky勒索病毒的侵扰. 什么是Locky勒索病毒 Locky勒索病毒主要以邮 ...

  9. log4j相对路径找不到,处理方法

    http://blog.csdn.net/u012345283/article/details/40821833?utm_source=tuicool&utm_medium=referral

  10. 全面的Seo面试题

      一.选择题(每题2分,2分×10=20分) 1.白帽SEO诞生日是: A .1997年9月15日 :B .2004年12月13日:C.2005年3月26日:D.2009年6月1日 2.一个做女性服 ...