Ehcache 缓存在 Java 开发领域己是久负盛吗名,在 Spring Boot 中,只需要一个配置文 就可以将 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>
- 添加缓存配置文件
<ehcache>
<diskStore path="java.io.tmpdir/cache"/>
<defaultCache
maxElementsInMemory="1000"
eternal="false"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
overflowToDisk="false"
diskPersistent="false"
diskExpiryThreadIntervalSeconds="600"
/>
<cache name="book cache"
maxElementsInMemory="1000"
eternal="true"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
overflowToDisk="true"
diskPersistent="true"
diskExpiryThreadIntervalSeconds="600"/>
</ehcache>
这是一个常规的 Ehcache 配置文件,提供了两个缓存策略, 一个是默认的,另一个名为 book_cache 。
- name 表示缓存名称;
- maxElementsInMemory 表示缓存最大个数;
- eternal 表示缓存对象是否永久有效,一旦设置了永久有效,timeout 不起作用
- timeToIdleSeconds 表示缓存对象在失效前的允许闲置时间(单位 :秒),当 eternal=false 对象不是永久有效时,该属性才生效;
- timeToLiveSeconds 表示缓存对象在失效前允许存活的时间(单位 :秒),当 eternal=false 对象不是永久有效时,该属性才生效;
- overflowToDisk 表示当内存中的对象数量达到 maxElementsInMemory 时, Ehcache 是否将对象写到磁盘中;
- diskExpiryThreadIntervalSeconds 表示磁盘失效线程运行时间间隔。
如果开发者想自定义 Ehcache 配置文件的名称和位置,可以在 application.properties 中添加如下配置:
spring.cache.ehcache.config=classpath:config/another_config.xml
- 开启缓存
@SpringBootApplication
@EnableCaching
public class EhcacheApplication {
public static void main(String[] args) {
SpringApplication.run(EhcacheApplication.class, args);
}
}
- 创建 实体类、BookDao
public class Book implements Serializable {
private Integer id;
private String name;
private String author;
//省略get/set方法
}
@Repository
//指明使用的缓存的名字 这个配直可选,若不使用 @CacheConfig 注解,则直接在 @Cacheable 注解中指明缓存名字
@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");
}
}
@Cacheable
:表示对该方法进行缓存,默认情况 ,缓存的 key 是方法的参数,缓存的 value 是方法的返回值当开发者在其他类中调用该方法时,首先会根据调用参数查看缓存中是否有相关数据,若有,则直接使用缓存数据,该方法不会执行,否则执行该方法,执行成功后将返回值缓存起来,但若是在当前类中调用该方法,则缓存不会生效。@Cacheable
注解中还有一个属性 condition 用来描述缓存的执行时机,例如
@Cacheable(condition ="#id%2=0")
表示当 id 对2 取模为 0 时才进行缓存,否则不缓存。- 如果开发者不想使用默认的 key ,也可以像
@CachePut(key = "#book.id")
、@CacheEvict(key = "#id")
一样自定义 key ,@CachePut(key = "#book.id")
表示存的 key 为参数 book 对象中 id 的值,@CacheEvict(key = "#id")
表示缓存 key 为参数 id。除了这种使用参数定义 key 的方式之外,Spring 还提供了 root 对象用来生成 key ,如表所示:
- 如果这些 key 不能够满足开发需求, 开发者也可以自定义缓存 key 的生成器 KeyGenerator。
@Component
public class MyKeyGenerator implements KeyGenerator {
@Override
public Object generate(Object o, Method method, Object... objects) {
return Arrays.toString(objects);
}
}
@Repository
//指明使用的缓存的名字 这个配直可选,若不使用 @CacheConfig 注解,则直接在 @Cacheable 注解中指明缓存名字
@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;
}
}
自定义 MyKeyGenerator 实现 KeyGenerator 接口,然后实现该接口中的 generate 方法,该方法的三个参数分别是当前对象、当前请求的方法以及方法的参数,开发者可根据这些信息组成一个新的 key 返回,返回值就是缓存的 key ,然后在 @Cacheable 注解中引用 MyKeyGenerator 实例即可。
@CachePut
注解一般用于数据更新方法上,与@Cacheable 解不同,添加了@CachePut 注解的方法每次在执行时都不去检查缓存中是否有数据,而是直接执行方法,然后将方法的执行结果缓存起来,如果该 key 对应的数据已经被缓存起来了,就会覆盖之前的数据,这样可以避免再次加载数据时获取到脏数据,同时,@CachePut 具有和@Cacheable 似的属性。@CacheEvict
注解一般用于删除方法上,表示移除一个key对应的缓存。@CacheEvict 注解有两个特殊的属性: allEntries 和 beforelnvocation ,其中 allEntries 表示是否将所有的缓存数据都移除, 默认为 false,beforelnvocation 表示是否在方法执行之前移除缓存中的数据,默认为 false ,即在方法执行之后移除缓存中的数据。
- 创建测试类
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
@RunWith(SpringRunner.class)
@SpringBootTest
public class EhcacheApplicationTest {
@Autowired
BookDao bookDao;
@Test
public void contextLoads() {
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);
}
}
一开始执行了两个查询,但是查询方法只打印了一次,因为第二次使用了缓存。接下来执行了删除方法,删除方法执行完之后再次执行查询, 询方法又被执行了,因为在删除方法中缓存己经被删除了 。再接下来执行更新方法,更新方法中不仅更新数据,也更新了缓存,所以在最后的查询方法中,查询方法的日志没打印,说明该方法没执行,而是使用了缓存中的数据,而缓存中的数据已经被更新了。