为了提高性能,减少数据库的压力,使用缓存是非常好的手段之一。
Spring 定义 CacheManager 和 Cache 接口用来统一不同的缓存技术。例如 JCache、 EhCache、 Hazelcast、 Guava、 Redis 等。在使用 Spring 集成 Cache 的时候,我们需要注册实现的 CacheManager 的 Bean。
如果想更深入理解 Spring 的 Cache 机制,这边推荐两篇不错的文章。
Spring Boot 为我们自动配置了多个 CacheManager 的实现。
Spring Boot 为我们自动配置了 JcacheCacheConfiguration、 EhCacheCacheConfiguration、HazelcastCacheConfiguration、GuavaCacheConfiguration、RedisCacheConfiguration、SimpleCacheConfiguration 等。
Spring 从 Spring3.1 开始基于 java.util.concurrent.ConcurrentHashMap 实现的缓存管理器。所以, Spring Boot 默认使用 ConcurrentMapCacheManager 作为缓存技术。
以下是我们不引入其他缓存依赖情况下,控制台打印的日志信息。
Bean 'cacheManager' of type [class org.springframework.cache.concurrent.ConcurrentMapCacheManager]
在 Spring Boot中,通过@EnableCaching注解自动化配置合适的缓存管理器(CacheManager),Spring Boot根据下面的顺序去侦测缓存提供者:
关于 Spring Boot 的缓存机制:
高速缓存抽象不提供实际存储,并且依赖于由org.springframework.cache.Cache和org.springframework.cache.CacheManager接口实现的抽象。 Spring Boot根据实现自动配置合适的CacheManager,只要缓存支持通过@EnableCaching注释启用即可。
JAVA缓存实现方案有很多,最基本的自己使用Map去构建缓存,或者使用memcached或Redis,但是上述两种缓存框架都要搭建服务器,而Map自行构建的缓存可能没有很高的使用效率,那么我们可以尝试一下使用Ehcache缓存框架。
Ehcache主要基于内存缓存,磁盘缓存为辅的,使用起来方便。
整合步骤:
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-cacheartifactId>
dependency>
<dependency>
<groupId>net.sf.ehcachegroupId>
<artifactId>ehcacheartifactId>
<version>2.9.1version>
dependency>
默认自动加载resources目录下的ehcache.xml文件
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="ehcache.xsd">
<diskStore path="java.io.tmpdir" />
<defaultCache
maxElementsInMemory="10000"
eternal="false"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
maxElementsOnDisk="10000000"
diskExpiryThreadIntervalSeconds="120"
memoryStoreEvictionPolicy="LRU">
<persistence strategy="localTempSwap" />
defaultCache>
<cache name="GoodsType"
eternal="false"
timeToIdleSeconds="2400"
timeToLiveSeconds="2400"
maxEntriesLocalHeap="10000"
maxEntriesLocalDisk="10000000"
diskExpiryThreadIntervalSeconds="120"
overflowToDisk="false"
memoryStoreEvictionPolicy="LRU">
cache>
ehcache>
对于EhCache的配置文件也可以通过application.yml文件中使用spring.cache.ehcache.config属性来指定,比如:
spring:
cache:
ehcache:
config: classpath:ehcache.xml
ehcache.xml配置文件详解
FIFO,first in first out,先进先出。
LFU, Less Frequently Used,一直以来最少被使用的。如上面所讲,缓存的元素有一个hit属性,hit值最小的将会被清出缓存。
LRU,Least Recently Used,最近最少使用的,缓存的元素有一个时间戳,当缓存容量满了,而又需要腾出地方来缓存新的元素的时候,那么现有缓存元素中时间戳离当前时间最远的元素将被清出缓存。
package com.ahut.config;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Configuration;
/**
*
* @ClassName: EhCacheConfig
* @Description:配置缓存
* @author cheng
* @date 2017年10月11日 下午1:00:34
*/
@Configuration
@EnableCaching
public class EhCacheConfig {
}
一般情况下,我们在Sercive层进行对缓存的操作。
先介绍 Ehcache 在 Spring 中的注解:在支持 Spring Cache 的环境下。
@CacheConfig
String[] cacheNames() default {};
指定使用那个缓存,如下:
@CacheConfig(cacheNames = "GoodsType")
@CacheConfig(cacheNames = {"cache1","cache2"})
配置在类上面,指定使用哪个缓存
@Cacheable
@AliasFor("cacheNames")
String[] value() default {};
缓存的名称,在 spring 配置文件中定义,必须指定至少一个 例如:
@Cacheable(value = "mycache") 或者
@Cacheable(value = {"cache1","cache2"}
String key() default "";
缓存的 key,可以为空,如果指定要按照 SpEL 表达式编写,如果不指定,则缺省按照方法的所有参数进行组合 例如:
@Cacheable(value = "testcache",key = "#userName")
String condition() default "";
缓存的条件,可以为空,使用 SpEL 编写,返回 true 或者 false,只有为 true 才进行缓存 例如:
@Cacheable(value = "testcache",condition = "#userName.length()>2")
Spring在每次执行前都会检查Cache中是否存在相同key的缓存元素,如果存在就不再执行该方法,而是直接从缓存中获取结果进行返回,否则才会执行并将返回结果存入指定的缓存中。
@CachePut
和@Cacheable注解相类似
使用@CachePut标注的方法在执行前不会去检查缓存中是否存在之前执行过的结果,而是每次都会执行该方法,并将执行结果以键值对的形式存入指定的缓存中。
@CacheEvict
@AliasFor("cacheNames")
String[] value() default {};
缓存的名称,在 spring 配置文件中定义,必须指定至少一个 例如:
@CachEvict(value= "mycach") 或者
@CachEvict(value = {"cache1","cache2"}
String key() default "";
缓存的 key,可以为空,如果指定要按照 SpEL 表达式编写,如果不指定,则缺省按照方法的所有参数进行组合 例如:
@CachEvict(value=”testcache”,key=”#userName”)
String condition() default "";
缓存的条件,可以为空,使用 SpEL 编写,返回 true 或者 false,只有为 true 才清空缓存 例如:
@CachEvict(value=”testcache”,
condition=”#userName.length()>2”)
boolean allEntries() default false;
是否清空所有缓存内容,缺省为 false,如果指定为 true,则方法调用后将立即清空所有缓存 例如:
@CachEvict(value=”testcache”,allEntries=true)
boolean beforeInvocation() default false;
是否在方法执行前就清空,缺省为 false,如果指定为 true,则在方法还没有执行的时候就清空缓存,缺省情况下,如果方法执行抛出异常,则不会清空缓存 例如:
@CachEvict(value=”testcache”,beforeInvocation=true)
清除缓存
@Caching
分组注解,能够同时应用多个其他的缓存注解
注解说明:
package com.ahut.cache;
import org.springframework.cache.annotation.CacheConfig;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Repository;
/**
*
* @ClassName: GoodsTypeDaoImpl
* @Description: 模拟数据访问实现类
* @author cheng
* @date 2017年10月13日 上午10:54:32
*/
@Repository
@CacheConfig(cacheNames = "GoodsType")
public class GoodsTypeDaoImpl {
@Cacheable
public String save(String typeId) {
System.out.println("save()执行了=============");
return "模拟数据库保存";
}
@CachePut
public String update(String typeId) {
System.out.println("update()执行了=============");
return "模拟数据库更新";
}
@CacheEvict
public String delete(String typeId) {
System.out.println("delete()执行了=============");
return "模拟数据库删除";
}
@Cacheable
public String select(String typeId) {
System.out.println("select()执行了=============");
return "模拟数据库查询";
}
}
测试:
package com.ahut.cache;
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 GoodsTypeDaoImplTest {
@Autowired
private GoodsTypeDaoImpl typeDao;
@Test
public void testSave() {
String typeId = "type111";
// 模拟第一次保存
String returnStr1 = typeDao.save(typeId);
System.out.println(returnStr1);
// 模拟第二次保存
String returnStr2 = typeDao.save(typeId);
System.out.println(returnStr2);
}
@Test
public void testUpdate() {
String typeId = "type111";
// 模拟第一次查询
String returnStr1 = typeDao.select(typeId);
System.out.println(returnStr1);
// 模拟第二次查询
String returnStr2 = typeDao.select(typeId);
System.out.println(returnStr2);
// 模拟更新
String returnStr3 = typeDao.update(typeId);
System.out.println(returnStr3);
// 模拟查询
String returnStr4 = typeDao.select(typeId);
System.out.println(returnStr4);
}
@Test
public void testDelete() {
String typeId = "type111";
// 模拟第一次查询
String returnStr1 = typeDao.select(typeId);
System.out.println(returnStr1);
// 模拟第二次查询
String returnStr2 = typeDao.select(typeId);
System.out.println(returnStr2);
// 模拟删除
String returnStr3 = typeDao.delete(typeId);
System.out.println(returnStr3);
// 模拟查询
String returnStr4 = typeDao.select(typeId);
System.out.println(returnStr4);
}
@Test
public void testSelect() {
String typeId = "type111";
// 模拟第一次查询
String returnStr1 = typeDao.select(typeId);
System.out.println(returnStr1);
// 模拟第二次查询
String returnStr2 = typeDao.select(typeId);
System.out.println(returnStr2);
}
}
执行
@Test
public void testSave() {
String typeId = "type111";
// 模拟第一次保存
String returnStr1 = typeDao.save(typeId);
System.out.println(returnStr1);
// 模拟第二次保存
String returnStr2 = typeDao.save(typeId);
System.out.println(returnStr2);
}
结果
save()执行了=============
模拟数据库保存
模拟数据库保存
执行
@Test
public void testUpdate() {
String typeId = "type111";
// 模拟第一次查询
String returnStr1 = typeDao.select(typeId);
System.out.println(returnStr1);
// 模拟第二次查询
String returnStr2 = typeDao.select(typeId);
System.out.println(returnStr2);
// 模拟更新
String returnStr3 = typeDao.update(typeId);
System.out.println(returnStr3);
// 模拟查询
String returnStr4 = typeDao.select(typeId);
System.out.println(returnStr4);
}
结果
select()执行了=============
模拟数据库查询
模拟数据库查询
update()执行了=============
模拟数据库更新
模拟数据库更新
执行
@Test
public void testDelete() {
String typeId = "type111";
// 模拟第一次查询
String returnStr1 = typeDao.select(typeId);
System.out.println(returnStr1);
// 模拟第二次查询
String returnStr2 = typeDao.select(typeId);
System.out.println(returnStr2);
// 模拟删除
String returnStr3 = typeDao.delete(typeId);
System.out.println(returnStr3);
// 模拟查询
String returnStr4 = typeDao.select(typeId);
System.out.println(returnStr4);
}
结果
select()执行了=============
模拟数据库查询
模拟数据库查询
delete()执行了=============
模拟数据库删除
select()执行了=============
模拟数据库查询
执行
@Test
public void testSelect() {
String typeId = "type111";
// 模拟第一次查询
String returnStr1 = typeDao.select(typeId);
System.out.println(returnStr1);
// 模拟第二次查询
String returnStr2 = typeDao.select(typeId);
System.out.println(returnStr2);
}
结果
select()执行了=============
模拟数据库查询
模拟数据库查询