Ehcache 2.x缓存
Ehcache缓存在Java开发领域已是久负盛名,在Spring Boot中,只需要一个配置文件就可以将Ehcache集成到项目中。Ehcache 2.x的使用步骤如下。
1. 创建项目,添加缓存依赖,
创建Spring Boot项目,添加spring-boot-starter-cache依赖以及Ehcache依赖,代码如下:
<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>net.sf.ehcache</groupId>
<artifactId>ehcache</artifactId>
</dependency>
2. 添加缓存配置文件
如果Ehcache的依赖存在,并且在classpath下有一个名为ehcache.xml 的Ehcache配置文件,那么EhCacheCacheManager将会自动作为缓存的实现。因此,在resources目录下创建ehcache.xml文件作为Ehcache缓存的配置文件,代码如下:
<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd"
updateCheck="false">
<!--
diskStore:为缓存路径,ehcache分为内存和磁盘两级,此属性定义磁盘的缓存位置。参数解释如下:
user.home – 用户主目录
user.dir – 用户当前工作目录
java.io.tmpdir – 默认临时文件路径
-->
<diskStore path="java.io.tmpdir/Tmp_EhCache"/>
<!--
defaultCache:默认缓存策略,当ehcache找不到定义的缓存时,则使用这个缓存策略。只能定义一个。
-->
<!--
name:缓存名称。
maxElementsInMemory:缓存最大数目
maxElementsOnDisk:硬盘最大缓存个数。
eternal:对象是否永久有效,一但设置了,timeout将不起作用。
overflowToDisk:是否保存到磁盘,当系统当机时
timeToIdleSeconds:设置对象在失效前的允许闲置时间(单位:秒)。仅当eternal=false对象不是永久有效时使用,可选属性,默认值是0,也就是可闲置时间无穷大。
timeToLiveSeconds:设置对象在失效前允许存活时间(单位:秒)。最大时间介于创建时间和失效时间之间。仅当eternal=false对象不是永久有效时使用,默认是0.,也就是对象存活时间无穷大。
diskPersistent:是否缓存虚拟机重启期数据 Whether the disk store persists between restarts of the Virtual Machine. The default value is false.
diskSpoolBufferSizeMB:这个参数设置DiskStore(磁盘缓存)的缓存区大小。默认是30MB。每个Cache都应该有自己的一个缓冲区。
diskExpiryThreadIntervalSeconds:磁盘失效线程运行时间间隔,默认是120秒。
memoryStoreEvictionPolicy:当达到maxElementsInMemory限制时,Ehcache将会根据指定的策略去清理内存。默认策略是LRU(最近最少使用)。你可以设置为FIFO(先进先出)或是LFU(较少使用)。
clearOnFlush:内存数量最大时是否清除。
memoryStoreEvictionPolicy:可选策略有:LRU(最近最少使用,默认策略)、FIFO(先进先出)、LFU(最少访问次数)。
FIFO,first in first out,这个是大家最熟的,先进先出。
LFU, Less Frequently Used,就是上面例子中使用的策略,直白一点就是讲一直以来最少被使用的。如上面所讲,缓存的元素有一个hit属性,hit值最小的将会被清出缓存。
LRU,Least Recently Used,最近最少使用的,缓存的元素有一个时间戳,当缓存容量满了,而又需要腾出地方来缓存新的元素的时候,那么现有缓存元素中时间戳离当前时间最远的元素将被清出缓存。
-->
<defaultCache
eternal="false"
maxElementsInMemory="10000"
overflowToDisk="false"
diskPersistent="false"
timeToIdleSeconds="1800"
timeToLiveSeconds="259200"
memoryStoreEvictionPolicy="LRU"/>
<cache
name="book_cache"
eternal="false"
maxElementsInMemory="5000"
overflowToDisk="false"
diskPersistent="false"
timeToIdleSeconds="1800"
timeToLiveSeconds="1800"
memoryStoreEvictionPolicy="LRU"/>
</ehcache>
这是一个常规的Ehcache 配置文件,提供了两个缓存策略,一个是默认的,另一个名为book_cache。 其中,name表示缓存名称; maxElementsInMemory 表示缓存最大个数; eternal 表示缓存对象是否永久有效,一旦设置了 永久有效,timeout 将不起作用; timeToIdleSeconds 表示缓存对象在失效前的允许闲置时间(单位:秒),当eternal=false对象不是永久有效时,该属性才生效;timeToLiveSeconds表示缓存对象在失效前允许存活的时间(单位:秒),当eternal=false对象不是永久有效时,该属性才生效; overflowToDisk 表示当内存中的对象数量达到maxElementsInMemory时,Ehcache 是否将对象写到磁盘中; diskExpiryThreadIntervalSeconds 表示磁盘失效线程运行时间间隔。还有其他更为详细的Ehcache配置,这里就不一-介绍了。另外,如果开发者想自定义Ehcache。配置文件的名称和位置,可以在application.properties中添加如下配置:
spring.cache.ehcache.config=classpath:config/anothor-config.xml
3. 开启缓存
在项目的入口类上添加@EnableCaching注解开启缓存,代码如下:
@SpringBootApplication
@EnableCaching
public class SpringbootApplication {
public static void main(String[] args) {
SpringApplication.run(TestspringbootApplication.class, args);
}
}
4. 创建BookDao
创建Book实体类和BookService,代码如下:
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Book implements Serializable {
private Integer id;
private String name;
private String author;
}
@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.setAuthor("罗贯中");
book.setName("三国演义");
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");
}
}
代码解释:
- 在BookDao. 上添加@CacheConfig 注解指明使用的缓存的名字,这个配置可选,若不使用@CacheConfig注解,则直接在@Cacheable注解中指明缓存名字。
- 第4 行在getBookById方法.上添加@Cacheable注解表示对该方法进行缓存,默认情况下,缓存的key是方法的参数,缓存的value是方法的返回值。当开发者在其他类中调用该方法时,首先会根据调用参数查看缓存中是否有相关数据,若有,则直接使用缓存数据,该方法不会执行,否则执行该方法,执行成功后将返回值缓存起来,但若是在当前类中调用该方法,则缓存不会生效。
- @Cacheable注解中还有一个属性condition用来描述缓存的执行时机,例如.@Cacheable(condition = “#id%2==0”)表示当 id对2取模为0时才进行缓存,否则不缓存。
- 如果开发者不想使用默认的key, 也可以像第13行和第19行一样自定义key, 第13行表示缓存的key为参数book对象中id的值,第19行表示缓存的key为参数id。除了这种使用参数定义key的方式之外,Spring 还提供了一个root对象用来生成key,如表所示。
使用root对象生成key
属性名称 | 属性描述 | 用法示例 |
---|---|---|
methodName | 当前方法名 | #root.methodName |
method | 当前方法对象 | #root.method.name |
caches | 当前方法使用的缓存 | #root.cache[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 target, Method method, Object... params) {
return Arrays.toString(params);
}
}
@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.setAuthor("罗贯中");
book.setName("三国演义");
return book;
}
}
- 自定义MyKeyGenerator实现KeyGenerator接口,然后实现该接口中的generate方法,该方法的三个参数分别是当前对象、当前请求的方法以及方法的参数,开发者可根据这些信息组成一个新的key 返回,返回值就是缓存的key。 第5行在@Cacheable注解中引用MyKeyGenerator实例即可。
- 第13行@CachePut注解一般用于数据更新方法上,与@Cacheable注解不同,添加了@CachePut注解的方法每次在执行时都不去检查缓存中是否有数据,而是直接执行方法,然后将方法的执行结果缓存起来,如果该key对应的数据已经被缓存起来了,就会覆盖之前的数据,这样可以避免再次加载数据时获取到脏数据。同时,@CachePut 具有和@Cacheable类似的属性,这里不再赘述。
- 第19行@CacheEvict注解一般用于删除方法上,表示移除一个key对应的缓存。@CacheEvict注解有两个特殊的属性: allEntries 和beforeInvocation,其中allEntries 表示是否将所有的缓存数据都移除,默认为false, beforeInvocation 表示是否在方法执行之前移除缓存中的数据,默认为false,即在方法执行之后移除缓存中的数据。
5. 创建测试类
创建测试类,对Service中的方法进行测试,代码如下:
@RunWith(SpringRunner.class)
@SpringBootTest(classes = TestspringbootApplication.class)
public class CachesApplicationTests {
@Resource
BookDao bookDao;
@Test
public void contextLoads() {
bookDao.getBookById(1);
bookDao.getBookById(1);
bookDao.deleteBookById(1);
Book bookById = bookDao.getBookById(1);
System.out.println(bookById);
Book book = new Book();
book.setName("张三日记");
book.setAuthor("罗翔");
book.setId(1);
bookDao.updateBookById(book);
Book book1 = bookDao.getBookById(1);
System.out.println(book1);
}
}
执行该方法,控制台打印日志如图所示。
getBookById
deleteBookById
getBookById
Book(id=1, name=三国演义, author=罗贯中)
updateBookById
Book(id=1, name=三国演义2, author=罗翔)
一开始执行了两个查询,但是查询方法只打印了一次,因为第二次使用了缓存。接下来执行了删除方法,删除方法执行完之后再次执行查询,查询方法又被执行了,因为在删除方法中缓存已经被删除了。再接下来执行更新方法,更新方法中不仅更新数据,也更新了缓存,所以在最后的查询方法中,查询方法日志没打印,说明该方法没执行,而是使用了缓存中的数据,而缓存中的数据已经被更新了。