前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >用注解解决业务逻辑和缓存逻辑的深度耦合

用注解解决业务逻辑和缓存逻辑的深度耦合

作者头像
Java识堂
发布2019-09-19 18:33:29
7290
发布2019-09-19 18:33:29
举报
文章被收录于专栏:Java识堂Java识堂

介绍

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接口(定义缓存的具体操作,如put和get等),实现类有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");
}

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-09-18,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Java识堂 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 介绍
  • 在Spring Boot中使用Spring Chache
相关产品与服务
数据库
云数据库为企业提供了完善的关系型数据库、非关系型数据库、分析型数据库和数据库生态工具。您可以通过产品选择和组合搭建,轻松实现高可靠、高可用性、高性能等数据库需求。云数据库服务也可大幅减少您的运维工作量,更专注于业务发展,让企业一站式享受数据上云及分布式架构的技术红利!
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档