Spring cache入门教程

在这里插入图片描述

GitHub地址

https://github.com/erlieStar/spring-boot-cache-demo

介绍

spring3.1引入了基于注解的缓存技术,即spring cache模块,它不是一个具体的缓存实现方案,而是一个对缓存使用的抽象。你可以类别为JDBC,定义了一系列缓存操作的接口,由具体的缓存来实现,如Ehcache,Redis等。

演示一下我们一般是怎么操作缓存的

实体类

@Data
@NoArgsConstructor
public class Account {
  private int id;
  private String name;
  private String password;

  public Account(String name) {
    this.name = name;
  }
}

缓存类,这个简单用ConcurrentHashMap来演示一下

public class Cache<T> {

  private Map<String, T> cache = new ConcurrentHashMap<String,T>();

  public T getValue(Object key) {
    return cache.get(key);
  }

  public void addOrUpdateCache(String key,T value) {
    cache.put(key, value);
  }

  // 根据 key 来删除缓存中的一条记录
  public void evictCache(String key) {
    if(cache.containsKey(key)) {
      cache.remove(key);
    }
  }

  // 清空缓存中的所有记录
  public void evictCache() {
    cache.clear();
  }
}

服务类

public class AccountService {

  private Cache<Account> cache = new Cache<>();

  public Account getAccountByName(String name) {
    Account result = cache.getValue(name);
    // 如果在缓存中,则直接返回缓存的结果
    if (result != null) {
      System.out.println("get from cache " + name);
      return result;
    }
    result = getFromDB(name);
    // 将数据库查询的结果更新到缓存中
    if (result != null) {
      cache.addOrUpdateCache(name, result);
    }
    return result;
  }

  private Account getFromDB(String name) {
    System.out.println("get from db " + name);
    return new Account(name);
  }
}

测试类

public class Main {

  public static void main(String[] args) {

    /**
     * get from db aaa
     * get from cache aaa
     */
    AccountService s = new AccountService();
    s.getAccountByName("aaa");
    s.getAccountByName("aaa");
  }
}

结果和我们预想的一样,第一次从数据库中拿,第二次就从缓存中拿

这样写有什么问题呢?

1.缓存代码和业务代码耦合度太高
2.目前缓存存储这块写的比较死,不能灵活的切换为第三方模块,当然你可以再抽象一层。

我们用Spring Cache改造一下上面的代码

在Spring Boot中使用Spring Chache

1.在pom文件中加入依赖

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-cache</artifactId>
</dependency>

2.在配置类中加@EnableCaching注解

@SpringBootApplication
@EnableCaching
public class CacheDemoApplication {
	public static void main(String[] args) {
		SpringApplication.run(CacheDemoApplication.class, args);
	}
}

以@Enable开头的这种类型的注解在Spring Boot和Spring Cloud项目中还是经常出现的,这种类型的注解是一个开关注解,对于调试还是非常方便的。比如调试中你不想用缓存,你就可以把这个注解注释掉。这样缓存相关的注解都不能使用了。

服务类改成如下形式

@Service
public class AccountService {

  @Cacheable(value = "cache", key = "#name")
  public Account getAccountByName(String name) {
    return getFromDB(name);
  }

  @Cacheable(value = "cache1", key = "#name")
  public Account getAccountByNameCache1(String name) {
    return getFromDB(name);
  }

  @Cacheable(value = "cache2", key = "#name")
  public Account getAccountByNameCache2(String name) {
    return getFromDB(name);
  }

  private Account getFromDB(String name) {
    System.out.println("get from db " + name);
    return new Account(name);
  }
}

现在你只看getAccountByName方法

@Test
public void test0() {

	/**
	 * get from db 1
	 * get from db 2
	 */
	accountService.getAccountByName("1");
	accountService.getAccountByName("1");
	accountService.getAccountByName("2");
	accountService.getAccountByName("2");
}

对于不同的key,第一次走数据库,第二次走缓存。完全符合我们的预期,而我们只用了一个注解。

spring cache的整体设计就类似下面这个map

Map<String, Map<String, String>> cacheManager;

最外层的map是缓存的名字(因为有可能有多个缓存),里面的map是缓存的key和缓存的value。

最外层的map对应spring cache的CacheManager接口,实现类有EhCacheCacheManager和
ConcurrentMapCacheManager等

里面的map对应spring cache的Cache接口,实现类有EhCacheCache和ConcurrentMapCache等

Spring cache默认使用的是ConcurrentMapCacheManager,即把缓存数据放在ConcurrentHashMap中。所以如果你想使用第三方缓存只要注入对应的CacheManager实现类和Cache实现类就行

接着来说上面用到的注解

@Cacheable(value = "cache", key = "#name")

@Cacheable的value值为cache的名字,key为缓存的key(使用SpEL表达式),缓存的key有多种指定方式,我这里只按照name进行了缓存。实际的key有多个组合方式,还能设定key的缓存条件。更多使用方式看最后的参考资料。

所以现在你明白@Cacheable的作用了把,如果能从缓存中取到值,就从缓存中取,否则就从数据库中取,如果取到值,再把值放到缓存中。背后的原理就是Spring Aop我就不深入解释了。

当初我为了验证我的想法,对getAccountByName写了好几个,就是@Cacheable中的value不同。

用来测试@Cacheable的value值是指定缓存的名字

@Test
public void test1() {
	/**
	 * get from db 1
	 * get from db 1
	 */
	accountService.getAccountByNameCache1("1");
	accountService.getAccountByNameCache2("1");
}

打印所有缓存的名字

@Autowired
CacheManager cacheManager;

@Test
public void test3() {

	/**
	 * get from db 1
	 * get from db 1
	 * cache2
	 * cache1
	 */
	accountService.getAccountByNameCache1("1");
	accountService.getAccountByNameCache2("1");
	Collection<String> collection = cacheManager.getCacheNames();
	collection.forEach(item ->{
		System.out.println(item);
	});
}

我再演示一下其他注解的使用

@Service
@CacheConfig(cacheNames = "cache")
public class AccountService1 {

  @Cacheable(key = "#name")
  public Account getAccountByName(String name) {
    return getFromDB(name);
  }

  @CachePut(key = "#account.getName()")
  public Account updateAccount(Account account) {
    return account;
  }

  @CacheEvict(key = "#name")
  public void deleteAccount(String name) {

  }

  private Account getFromDB(String name) {
    System.out.println("get from db " + name);
    return new Account(name);
  }
}

在前面的示例中,我们用到了@Cacheable注解,每次都得指明value属性,即缓存的名字。如果不想每次指定缓存的名字,就可以用@CacheConfig注解在类上统一指定一个缓存的名字。

@CachePut能够根据方法的请求参数对其结果进行缓存,和 @Cacheable 不同的是,它每次都会触发真实方法的调用,所以这个注解经常用在更新操作上

@CacheEvict根据条件对缓存清空,所以一般用在删除方法上

@Test
public void test6() {
	/**
	 * get from db aaa
	 * get from db aaa
	 */
	accountService1.getAccountByName("aaa");
	accountService1.deleteAccount("aaa");
	accountService1.getAccountByName("aaa");
}

参考博客

好文
[1]https://www.ibm.com/developerworks/cn/opensource/os-cn-spring-cache/
[2]https://blog.csdn.net/maoyeqiu/article/details/50017157
结合Spring Boot
[3]https://my.oschina.net/u/3545445/blog/1503579

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Java识堂

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值