SpringBoot 学习笔记_缓存 —— Ehcache 2.x
声明:
本次学习参考 《SpringBoot + Vue 开发实战》 · 王松(著) 一书。
本文的目的是记录我学习的过程和遇到的一些问题以及解决办法,其内容主要来源于原书。
如有侵权,请联系我删除
Spring 3.1 开始对缓存提供支持,核心思路对方法的缓存,当开发者调用一个方法时,将方法中的参数/返回值作为key/value缓存起来,当再次调用该方法时,如果缓存中有数据,就直接从缓存获取,否则再去执行方法。但是,Spring 中并未提供缓存的实现,而是提供一套缓存 API。
SpringBoot 缓存 —— Ehcache 2.x
-
创建 SpringBoot 项目,添加依赖
<!-- 添加 Ehcache 2.x 缓存依赖 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-cache</artifactId> </dependency> <dependency> <groupId>net.sf.ehcache</groupId> <artifactId>ehcache</artifactId> </dependency>
-
添加缓存配置文件(
ehcache.xml
)如果 Ehcache 的依赖存在,并且在 classpath 下有一个名为
ehcache.xml
的 Ehcache 配置文件,那么 EhCacheCacheManager 将会自动作为缓存的实现。因此,在 resource 目录下创建ehcache.xml
作文 Ehcache 缓存的配置文件。如果想自定义 Ehcache 配置文件位置和名称,可以在
application.properties
添加配置spring.cache.ehcache.config=classpath:config/ehcache-config.xml
ehcache.xml
中配置如下:<!-- 常规 Ehcache 配置文件 --> <ehcache> <!-- 提供两个缓存策略 --> <diskStore path="java.io.tmpdir/cache"/> <!-- 1、默认缓存配置 --> <defaultCache maxElementsInMemory="1000" eternal="false" timeToIdleSeconds="120" timeToLiveSeconds="120" overflowToDisk="false" diskPersistent="false" diskExpiryThreadIntervalSeconds="120" /> <!-- 2、自定义缓存配置 --> <!-- name: 缓存名称 --> <!-- maxElementsInMemory: 缓存最大个数 --> <!-- eternal: 缓存对象是否永久有效。为 true 时 timeout 不生效 --> <!-- timeToIdleSeconds: 缓存对象在失效前的允许闲置时间(单位:秒)。eternal 为 false 时才生效 --> <!-- timeToLiveSeconds: 缓存对象在失效前的允许存活时间(单位:秒)。eternal 为 false 时才生效 --> <!-- overflowToDisk: 内存中的对象数量达到 maxElementsInMemory 时,Ehcache 是否将对象写到磁盘中 --> <!-- diskExpiryThreadIntervalSecond: 磁盘失效线程运行时间间隔 --> <cache name="book_cache" maxElementsInMemory="10000" eternal="true" timeToIdleSeconds="120" timeToLiveSeconds="120" overflowToDisk="true" diskPersistent="true" diskExpiryThreadIntervalSeconds="600" /> </ehcache>
-
开启缓存
在项目入口类添加
@EnableCaching
注解开启缓存@SpringBootApplication @EnableCaching public class Chapter041Application { public static void main(String[] args) { SpringApplication.run(Chapter041Application.class, args); } }
-
创建实体类
public class Book { private Integer id; private String name; private String author; /* Getter & Setter */ }
-
创建 Dao
@Repository // @CacheConfig 注解指定使用的缓存名称(配置在 ehcache.xml 中) // 也可以在 @Cacheable 注解中指明缓存名称 @CacheConfig(cacheNames = "book_cache") public class BookDao { // 方法上添加 @Cacheable 注解,表示对该方法进行缓存。 // 默认情况下,缓存的 key 是方法的参数,缓存的 value 是方法的返回值。 // 当在其他类中调用该方法时,首先会根据调用参数查看缓存中是否有相关数据,若有,直接使用缓存数据,该方法不会执行。 // 否则,执行该方法,执行成功后将返回值缓存起来 // 但是,若在当前类中调用该方法,则缓存不会生效 // 该注解还有一个 condition 属性用来表明缓存的执行时机,如 @Cacheable(condition= "#id%2==0") 表示 id 为偶数时才进行缓存 @Cacheable public Book getBookById(Integer id) { System.out.println("getBookId"); Book book = new Book(); book.setId(1); book.setName("三国演义"); book.setAuthor("罗贯中"); return book; } // 如果不想使用默认的 key,也可以自定义 // 表示缓存的 key 为参数 book 对象中 id 的值 // CachePut 一般用于数据更新方法上。其属性与 Cacheable 类似 // 与 Cacheable 不同的是: // 添加 CachePut 注解的方法每次执行时都不去检查缓存中是否有数据,而是直接执行方法,然后将结果缓存, // 如果该 key 对应的数据已经被缓存,则会覆盖之前的数据,避免获取到脏数据 @CachePut(key = "#book.id") public Book updateBookById(Book book) { System.out.println("updateBookById"); book.setName("新三国演义"); return book; } // 如果不想使用默认的 key,也可以自定义 // 表示缓存的 key 为参数 id // CacheEvict 一般用于删除方法上,表示移除一个 key 对应的缓存。其有两个特殊属性: // allEntries:表示是否将所有的缓存数据都移除。默认 false // beforeInvocation:表示是否在方法执行之前移除缓存中的数据。默认 false,即在方法执行之后移除缓存中的数据。 @CacheEvict(key = "#id") public void deleteBookById(Integer id){ System.out.println("deleteBookById"); } // 除了以上两种,Spring 还提供了 root 对象用来生成 key // #root.methodName 当前方法名 // #root.method.name 当前方法对象 // #root.caches[0].name 当前方法使用的缓存 // #root.target 当前被调用的对象 // #root.targetClass 当前被调用的对象的 class // #root.args[0] 当前方法参数数组 }
-
如果默认提供的 key 不满足要求,也可以自定义缓存 key 生成器
/** * 自定义缓存 key 的生成器 */ @Component public class MyKeyGenerator implements KeyGenerator { /** * * @param target 当前对象 * @param method 当前请求方法 * @param params 当前参数 * @return 生成的 key */ @Override public Object generate(Object target, Method method, Object... params) { return Arrays.toString(params); } }
-
BookDao 应该如下使用自定义的 key 生成器
@Service @CacheConfig(cacheNames = "book_cache") public class Book1Dao { @Autowired MyKeyGenerator myKeyGenerator; @Cacheable(keyGenerator = "myKeyGenerator") public Book getBookById(Integer id){ System.out.println("getBookById"); Book book = new Book(); book.setId(1); book.setName("三国演义"); book.setAuthor("罗贯中"); return book; } }
-
创建测试类
@RunWith(SpringRunner.class) @SpringBootTest class Chapter041ApplicationTests { @Autowired BookDao bookDao; @Test 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.setId(1); book.setName("三国演义"); book.setAuthor("罗贯中"); bookDao.updateBookById(book); // 这里不仅会更新数据,也会更新缓存 // 执行查询方法 Book book4 = bookDao.getBookById(1);// 没有执行方法,说明这里使用了缓存数据 System.out.println("book4: " + book4);// 但是输出数据,发现数据已经被更新。所以说明 update 的时候是同步更新了缓存数据的。 } }
-
输出结果
getBookId deleteBookById getBookId book3: Book{id=1, name='三国演义', author='罗贯中'} updateBookById book4: Book{id=1, name='新三国演义', author='罗贯中'}