返回 导航

SpringBoot / Cloud

hangge.com

SpringBoot - 缓存的使用详解1(使用Ehcache 2.x缓存)

作者:hangge | 2019-12-26 08:10
    Spring 3.1 中开始对缓存提供支持,核心思路是对方法的缓存,当我们调用一个方法时,将方法的参数和返回值作为 key/value 缓存起来,当再次调用该方法时,如果缓存中有数据,就直接从缓存中获取,否则再去执行该方法。
    Spring 并未提供缓存的实现,我们可以自由选择缓存的实现,目前 Spring Boot 支持的缓存有 JCacheEhCache 2.xRedis....。不过无论使用那种缓存实现,不同的只是缓存配置,开发者使用的缓存注解都是一样的。

一、使用 Ehchche 2.x 缓存

1,添加依赖

首先编辑项目的 pom.xml 文件,添加 spring-boot-starter-cache 依赖以及 Ehcache 依赖:
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
    <groupId>net.sf.ehcache</groupId>
    <artifactId>ehcache</artifactId>
</dependency>

2,添加缓存配置文件

resources 目录下创建 ehcache.xml 文件作为 Ehcache 缓存的配置文件,内容如下:
(1)如果 Ehcache 的依赖存在,并且在 classpath 下又一个名为 encache.xmlEhcache 配置文件,那么 EhCacheManager 将会自动作为缓存的实现。
(2)这是一个常规的 Ehcache 配置文件,提供了两个缓存策略,一个是默认的,另一个名为 book_cache。具体参数作用如下:
  • name:缓存名称
  • maxElementsInMemory:缓存最大个数
  • eternal:缓存对象是否永久有效,一但设置了永久有效,timeout 将不起作用。
  • timeToIdleSeconds:缓存对象在失效前的允许闲置时间(单位:秒)。仅当 eternal=false 对象不是永久有效时,该属性才生效。默认值是 0,也就是可闲置时间无穷大。
  • timeToLiveSeconds:缓存对象在失效前允许存活时间(单位:秒)。仅当 eternal=false 对象不是永久有效时,该属性才生效。默认值是 0,也就是存活时间无穷大。
  • overflowToDisk:表示内存中的数量达到 maxElementsInMemory 时,Ehcache 是否将对象写到磁盘中。
  • diskPersistent:是否缓存虚拟机重启期数据。
  • diskExpiryThreadIntervalSeconds:磁盘失效线程运行时间间隔,默认是 120 秒。
(3)如果我们想要自定 Ehcache 配置文件的名称和位置,可以在 application.properties 中添加如下配置:
  • spring.cache.ehcache.config=classpath:config/another-config.xml
<ehcache>
    <diskStore path="java.io.tmpdir/cache"/>
    <defaultCache
            maxElementsInMemory="10000"
            eternal="false"
            timeToIdleSeconds="120"
            timeToLiveSeconds="120"
            overflowToDisk="false"
            diskPersistent="false"
            diskExpiryThreadIntervalSeconds="120"
    />
    <cache name="book_cache"
           maxElementsInMemory="10000"
           eternal="true"
           timeToIdleSeconds="120"
           timeToLiveSeconds="120"
           overflowToDisk="true"
           diskPersistent="true"
           diskExpiryThreadIntervalSeconds="10"/>
</ehcache>

3,开启缓存

在项目的入口类上添加 @EnableCaching 注解开启缓存,代码如下:
@SpringBootApplication
@EnableCaching
public class DemoApplication {
    public static void main(String[] args) {
        ConfigurableApplicationContext context
                = SpringApplication.run(DemoApplication.class, args);
    }
}

4,开始测试

(1)首先我们创建一个 Book 实体类:
@Setter
@Getter
@NoArgsConstructor
public class Book implements Serializable {
    private Integer id;
    private String name;
    private String author;
}

(2)接着创建一个 BookDao 并添加相关的缓存注解:
(1)在 BookDao 上添加 @CacheConfig 注解指定使用的缓存的名字,这个配置可选。若不使用 @CacheConfig 注解,则直接在 @Cacheable 注解中指明缓存名字。
(2)在方法上添加 @Cacheable 注解表示对该方法进行缓存:
  • 默认情况下,缓存的 key 是方法的参数,缓存的 value 是方法的返回值。
  • 当开发者在其他类中调用该方法时,首先会根据调用参数查看缓存中是否有相关数据,若有则直接使用缓存数据,该方法不会执行。
  • 否则执行该方法,执行成功后将返回值缓存起来。
  • 但若是在当前类中调用该方法,则缓存不会生效。
(3)@Cacheable 注解中还有一个属性 condition 用来描述缓存的执行时机,例如:
  • @Cacheable(condition="#id%2-0") 表示当 id 2 取模为 0 时才进行缓存,否则不缓存。
(4)如果开发者不想使用默认的 key,也可以像第 15 行和第 22 行一样自定义 key
  • 15 行表示缓存的 key 为参数 book 对象中 id 的值。
  • 22 行表示缓存的 key 为参数 id
(5)除了上面这种使用参数定义 key 的方式之外,Spring 还提供了一个 root 对象用来生成 key。下面是一些用法实例:
  • #root.methodName:当前方法名
  • #root.method.name:当前方法对象
  • #root.caches[0].name:当前方法使用的缓存
  • #root.target:当前被调用的对象
  • #root.targetClass:当前被调用的对象的 class
  • #root.args[0]:当前方法参数数组
(6)@Cacheput 注解一般用于数据更新方法上:
  • @Cacheable 注解不同,添加了 @Cacheput 注解的方法每次在执行时都不去检查缓存中是否有数据,而是直接执行方法,然后将方法的执行结果缓存起来。
  • 如果该 key 对应的数据已经被缓存起来了,就会覆盖之前的数据,这样可以避免再次加载数据时获取到脏数据。
  • 同时,@Cacheput 具有和 @Cacheable 类似的属性,这里不再赘述。
(7)@CacheEvict 注解一般用于删除方法上,表示移除一个 key 对应的缓存。@CacheEvict 注解有两个特殊的属性 allEntries beforelnvocation
  • allEntries 表示是否将所有的缓存数据都移除,默认为 false
  • beforelnvocation 表示是否在方法执行之前移除缓存中的数据,默认为 false,即在方法执行之后移除缓存中的数据。
@Repository
@CacheConfig(cacheNames = "book_cache")
public class BookDao {

    @Cacheable
    public Book getBookById(Integer id) {
        System.out.println("getBookById");
        Book book = new Book();
        book.setId(id);
        book.setName("三国演义");
        book.setAuthor("罗贯中");
        return book;
    }

    @CachePut(key = "#book.id")
    public Book updateBookById(Book book) {
        System.out.println("updateBookById");
        book.setName("三国演义2");
        return book;
    }

    @CacheEvict(key = "#id")
    public void deleteBookById(Integer id) {
        System.out.println("deleteBookById");
    }
}

(3)创建一个测试 Controller BookDao 中的方法进行测试:
@RestController
public class HelloController {

    @Autowired
    BookDao bookDao;

    @GetMapping("/test")
    public void test() {
        // 连续获取两次数据
        bookDao.getBookById(1);
        bookDao.getBookById(1);

        // 删除后再次获取数据
        bookDao.deleteBookById(1);
        Book b3 = bookDao.getBookById(1);
        System.out.println("b3:"+b3);

        // 更新后再次获取数据
        Book b = new Book();
        b.setName("平凡的世界");
        b.setAuthor("路遥");
        b.setId(1);
        bookDao.updateBookById(b);
        Book b4 = bookDao.getBookById(1);
        System.out.println("b4:"+b4);
    }
}

(4)访问这个接口,可以看到控制台打印日志如下:

附:自定义缓存 key 的生成器 KeyGenerator

    根据前面介绍可知,默认情况下缓存的 key 是方法的参数。如果不想使用默认的 key,可以使用自定义 key(使用参数定义 key 的方式、或者使用 root 对象来生成 key
    如果这些 key 不能满足开发需求,我们也可以自定义缓存 key 的生成器 KeyGenerator

1,创建自定义 KeyGenerator

自定义的 MyKeyGenerator 实现 KeyGenerator 接口,然后实现该接口中的 generate 方法。
    generate 方法的三个参数分别是:当前对象、当前请求的方法以及方法的参数,开发者可根据这些信息纽成一个新的 key 返回,返回值就是缓存的 key
@Component
public class MyKeyGenerator implements KeyGenerator {
    @Override
    public Object generate(Object target, Method method, Object... params) {
        return Arrays.toString(params);
    }
}

2,使用自定义的 KeyGenerator

使用时我们只需要在 @Cacheable 注解中引用 MyKeyGenermor 实例即可。
@Repository
@CacheConfig(cacheNames = "book_cache")
public class BookDao {

    @Autowired
    MyKeyGenerator myKeyGenerator;

    @Cacheable(keyGenerator = "myKeyGenerator")
    public Book getBookById(Integer id) {
        System.out.println("getBookById");
        Book book = new Book();
        book.setId(id);
        book.setName("三国演义");
        book.setAuthor("罗贯中");
        return book;
    }
}
评论

全部评论(0)

回到顶部