Ehcache 缓存
Ehcache缓存在Java开发领域已时久负盛名,在SpringBoot中,只需一个配置文件就可以将Ehcache集成到项目中。具体的使用步骤如下:
1. 创建项目,添加缓存依赖
创建SpringBoot项目,添加spring-boot-starter-cache依赖以及Ehcache依赖,代码如下:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
<groupId>org.ehcache</groupId>
<artifactId>ehcache</artifactId>
<version>3.8.1</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
2.添加缓存配置文件
如果Ehcache的依赖存在,并且在classpath下有个名为ehcache.xml的Ehcache配置文件,那么EhCacheCacheManager将自动作为缓存的实现。因此,在resources目录下创建ehcache.xml文件作为Ehcache缓存的配置文件,代码如下:
<config
xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'
xmlns='http://www.ehcache.org/v3'
xsi:schemaLocation="http://www.ehcache.org/v3 http://www.ehcache.org/schema/ehcache-core.xsd">
<!-- 声明的Cache别名foo-->
<cache alias="foo">
<!-- 声明缓存的键和值的type为String,如果未指定,则默认值为java.lang.Object-->
<key-type>java.lang.String</key-type>
<value-type>java.lang.String</value-type>
<expiry>
<ttl unit="minutes">2</ttl>
</expiry>
<!-- 分配资源大小-->
<resources>
<!-- 缓存在堆上最多容纳2000条-->
<heap unit="entries">2000</heap>
<!-- 以及开始驱逐之前最多500MB的堆外内存-->
<offheap unit="MB">500</offheap>
</resources>
</cache>
<!-- <cache-template>元素使您可以创建抽象配置,<cache>然后进一步的配置可以“扩展”-->
<cache-template name="myDefaults">
<key-type>java.lang.Long</key-type>
<value-type>java.lang.String</value-type>
<heap unit="entries">200</heap>
</cache-template>
<!-- 声明Cache的别名为bar,bar使用<cache-template>的名称为myDefaults并将其覆盖key-type为Number类型。 -->
<cache alias="bar" uses-template="myDefaults">
<key-type>java.lang.Number</key-type>
</cache>
<!-- 声明Cache的别名为simpleCache,它仅使用myDefaults配置CacheConfiguration -->
<cache alias="simpleCache" uses-template="myDefaults" />
<cache-template name="bookTemplate">
<key-type>java.lang.String</key-type>
<value-type>com.demo.chapter091.entity.Book</value-type>
<heap unit="entries">200</heap>
</cache-template>
<cache alias="book_cache" uses-template="bookTemplate">
<expiry>
<ttl unit="minutes">1</ttl>
</expiry>
</cache>
</config>
具有107扩展名的XML:
<ehcache:config
xmlns:ehcache="http://www.ehcache.org/v3"
xmlns:jcache="http://www.ehcache.org/v3/jsr107">
<!--
OPTIONAL
由CacheManager管理和生命周期化的服务
-->
<ehcache:service>
<!--
另一个命名空间中的一个元素,这里以我们的JSR-107扩展为例
-->
<jcache:defaults>
<jcache:cache name="invoices" template="myDefaultTemplate"/>
</jcache:defaults>
</ehcache:service>
<!--
OPTIONAL
<cache>元素定义由强制'alias'属性标识的缓存,由CacheManager管理
-->
<ehcache:cache alias="productCache">
<!--
OPTIONAL, defaults to java.lang.Object
The FQCN of the type of keys K we'll use with the Cache<K, V>
-->
<ehcache:key-type copier="org.ehcache.impl.copy.SerializingCopier">java.lang.Long</ehcache:key-type>
<!--
OPTIONAL, defaults to java.lang.Object
The FQCN of the type of values V we'll use with the Cache<K, V>
-->
<ehcache:value-type copier="org.ehcache.impl.copy.SerializingCopier">com.pany.domain.Product</ehcache:value-type>
<!--
OPTIONAL, defaults to no expiry
可以使缓存条目在给定时间后过期
-->
<ehcache:expiry>
<!--
time to idle<tti>空闲时间,使条目保持不变的最大时间可以使缓存条目在给定时间后到期,其他选项包括:
<ttl>,生存时间;
<class>,用于自定义Expiry实现;
<无>,无有效期
-->
<ehcache:tti unit="minutes">2</ehcache:tti>
</ehcache:expiry>
<!--
OPTIONAL, defaults to no advice
驱逐顾问,使您可以控制哪些条目仅应作为org.ehcache.config.EvictionAdvisor实现的最后手段FQCN驱逐
-->
<ehcache:eviction-advisor>com.pany.ehcache.MyEvictionAdvisor</ehcache:eviction-advisor>
<!--
OPTIONAL,
Let's you configure your cache as a "cache-through",
i.e. a Cache that uses a CacheLoaderWriter to load on misses, and write on mutative operations.
让我们将缓存配置为“直通式缓存”,即使用CacheLoaderWriter的缓存加载未命中的内容,并写入变异操作。
-->
<ehcache:loader-writer>
<!--
The FQCN implementing org.ehcache.spi.loaderwriter.CacheLoaderWriter
-->
<ehcache:class>com.pany.ehcache.integration.ProductCacheLoaderWriter</ehcache:class>
<!-- Any further elements in another namespace -->
</ehcache:loader-writer>
<!--
逐出之前,要保留在缓存中的最大条目数
-->
<ehcache:heap unit="entries">200</ehcache:heap>
<!--
OPTIONAL
Any further elements in another namespace
-->
</ehcache:cache>
<!--
OPTIONAL
A <cache-template> defines a named template that can be used be <cache> definitions in this same file
They have all the same property as the <cache> elements above
-->
<ehcache:cache-template name="myDefaultTemplate">
<ehcache:expiry>
<ehcache:none/>
</ehcache:expiry>
<!--
OPTIONAL
Any further elements in another namespace
-->
</ehcache:cache-template>
<!--
通过在uses-template属性中引用高速缓存模板的名称来使用上述模板的<cache>:
-->
<ehcache:cache alias="customerCache" uses-template="myDefaultTemplate">
<!--
Adds the key and value type configuration
-->
<ehcache:key-type>java.lang.Long</ehcache:key-type>
<ehcache:value-type>com.pany.domain.Customer</ehcache:value-type>
<!--
Overwrites the capacity limit set by the template to a new value
-->
<ehcache:heap unit="entries">200</ehcache:heap>
</ehcache:cache>
</ehcache:config>
另外,如果开发者想自定义Ehcache配置文件的位置,可在application.yml中添加如下配置:
spring:
cache:
jcache:
config: ehcache.xml
3. 开启缓存
在项目的入口类上添加@EnableCaching注解开启缓存,代码如下:
@SpringBootApplication
@EnableCaching
public class Chapter091Application {
public static void main(String[] args) {
SpringApplication.run(Chapter091Application.class, args);
}
}
4. 创建BookDao
创建Book实体类和BookDao,代码如下
@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");
}
}
@Data
public class Book implements Serializable {
private Integer id;
private String name;
private String author;
}
-
在BookDao上添加@CacheConfig注解指明使用缓存的名字,这个配置可选,若不使用@CacheConfig注解,则直接在@Cacheable注解中指明缓存的名字。
-
在getBookById方法上添加@Cacheable注解表示对该方法进行缓存,默认情况下缓存的key是方法的参数,缓存的值是方法的返回值。当开发者在其他类中调用该方法时,首先会根据参数查看缓存中是否有相关的数据,若有则直接使用缓存的数据,该方法不会执行,否者执行该方法,执行成功后将返回值缓存起来,但若在当前类中调用该方法,则缓存不会生效。
-
@Cacheable注解中还有一个属性condition用来藐视缓存执行的时机,例如@Cacheable(conditioin="#id%2==0")表示当id对2取模为0时才进行缓存,否则不执行缓存。
-
如果开发者不想使用默认的key,第15行表示缓存的key为book参数中id的值,第22行表示缓存的key为参数id。错了这种用参数定义key之外,Spring还提供了一个root对象用来生成key,如下图所示:
属性名称 属性描述 用法实例 methodName 当前方法名 #root.methodName method 当前方法对象 #root.method.name caches 当前方法使用的缓存 #root.caches[0].name target 当前被调用的对象 #root.target targetClass 当前被调用对象的Class #root.targetClass args 当前方法参数数组 #root.args[0] -
如果这些key不能够瞒住开发需求,开发者也可以自定义缓存key的生成器KeyGenerator,代码如下:
@Component public class MyKeyGenerator implements KeyGenerator { @Override public Object generate(Object o, Method method, Object... objects) { return Arrays.toString(objects); } } @Service @CacheConfig(cacheNames = "book_cache") public class BookService { @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; } }
-
自定义MyKeyGenerator实现KeyGenerator接口,然后实现generate方法,该方法的三个参数分别是当前对象,当前请求方法以及方法的参数,开发者可以根据这些信息组成一个新的key返回,返回值就是缓存的key在第19行@Cacheable中引用MyKeyGenerator实例即可;
-
@CachePut注解一般用于跟新方法上,与@Cacheable不同,添加了@CachePut注解每次在执行时都不回去检查缓存中是否有数据,而是直接执行方法,然后将方法执行的结果缓存起来,如果key对应数据已经被缓存起来了,就会覆盖之前的数据,这样就可以避免再次加载数据时获取到脏数据。同时@CachePut具有和@Cacheable类似的属性这里不再赘述。
-
@CacheEvict一般用于删除方法上,表示移除一个key对应的缓存数据,@CacheEvict注解有两个特殊性的属性:allEntries和beforeInvocation表示是否在方法执行前移除缓存中的数据,默认为false,即在方法执行后移除缓存中数据。
5. 创建测试类
创建测试类,代码如下:
@SpringBootTest
class BookDaoTest {
@Autowired
BookDao bookDao;
@Test
public void contextLoads(){
bookDao.getBookById(1);
bookDao.getBookById(1);
bookDao.deleteBookById(1);
Book book3 = bookDao.getBookById(1);
System.out.println("book3 " + book3);
Book book = new Book();
book.setName("三国演义");
book.setAuthor("罗贯中");
book.setId(1);
bookDao.updateBookById(book);
Book book4 = bookDao.getBookById(1);
System.out.println("boo4 " + book4);
}
}
执行测试方法控制台打印数据如下:
备注:更多相关配置请参考Ehcache 3.8文档