缓存就是数据交换的缓冲区(称作Cache),当某一硬件要读取数据时,会首先从缓存中查找需要的数据,如果找到了则直接执行,找不到的话则从内存中找。由于缓存的运行速度比内存快得多,故缓存的作用就是帮助硬件更快地运行。
spring从3.1开始支持缓存功能。spring 自带的缓存机制它只在方法上起作用,对于你使用其他持久化层的框架来讲,是没有影响的。
Spring 缓存的特点
- 通过少量的配置 annotation 注释即可使得既有代码支持缓存。
- 支持开箱即用 Out-Of-The-Box,即不用安装和部署额外第三方组件即可使用缓存。
- 支持 Spring Express Language,能使用对象的任何属性或者方法来定义缓存的 key 和 condition。
- 支持 AspectJ,并通过其实现任何方法的缓存支持。
- 支持自定义 key 和自定义缓存管理者,具有相当的灵活性和扩展性。
Spring 缓存的基本原理
和 spring 的事务管理类似,spring cache 的关键原理就是 spring AOP,通过 spring AOP,其实现了在方法调用前、调用后获取方法的入参和返回值,进而实现了缓存的逻辑。我们来看一下下面这个图:
上图显示,当客户端“Calling code”调用一个普通类 Plain Object 的 foo() 方法的时候,是直接作用在 pojo 类自身对象上的,客户端拥有的是被调用者的直接的引用。
而 Spring cache 利用了 Spring AOP 的动态代理技术,即当客户端尝试调用 pojo 的 foo()方法的时候,给他的不是 pojo 自身的引用,而是一个动态生成的代理类。
如上图所示,这个时候,实际客户端拥有的是一个代理的引用,那么在调用 foo() 方法的时候,会首先调用 proxy 的 foo() 方法,这个时候 proxy 可以整体控制实际的 pojo.foo() 方法的入参和返回值,比如缓存结果,比如直接略过执行实际的 foo() 方法等,都是可以轻松做到的。
Spring 缓存的注解
@Cacheable
主要针对方法配置,能够根据方法的请求参数对其结果进行缓存
value
缓存的名称,在 spring 配置文件中定义,必须指定至少一个
1
2
3 1@Cacheable(value=”mycache”)
2@Cacheable(value={”cache1”,”cache2”}
3key
因为缓存都是key-value形式的,value就是返回值,而key可以有各种各样的,所以必须有一个key的生成策略,如果不指定key,Spring会帮我们生成;默认生成策略如下:
- 如果方法没有参数,则使用SimpleKey.EMPTY作为key。
- 如果只有一个参数的话则使用该参数作为key。
- 如果参数多于一个的话则使用所有参数的hashCode作为key。
可以通过Spring的EL表达式来指定我们的key。这里的EL表达式可以使用方法参数及它们对应的属性。使用方法参数时我们可以直接使用“#参数名”或者“#p参数index”。
1
2
3
4
5 1// 将isbn作为key
2@Cacheable(cacheNames="books", key="#isbn")
3//或者 @Cacheable(cacheNames="books", key="#p0")
4public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)
5condition
并不一定每次都需要缓存,可以根据参数来确定是否缓存,Spring提供了condition参数,可以为空,使用 SpEL 编写。通过判断true或者false来决定是否执行方法。
1
2
3 1Cacheable(cacheNames="book", condition="#name.length() < 32", unless="#result.hardback")
2public Book findBook(String name)
3@CachePut(参数同上)
主要针对方法配置,能够根据方法的请求参数对其结果进行缓存,和 @Cacheable 不同的是,它每次都会触发真实方法的调用
@CacheEvict (参数同上)
主要针对方法配置,能够根据一定的条件对缓存进行清空
allEntries
是否清空所有缓存内容,缺省为 false,如果指定为 true,则方法调用后将立即清空所有缓存。
1
2 1@CachEvict(value=”testcache”,allEntries=true)
2beforeInvocation
是否在方法执行前就清空,缺省为 false,如果指定为 true,则在方法还没有执行的时候就清空缓存,缺省情况下,如果方法执行抛出异常,则不会清空缓存。
1
2 1@CachEvict(value=”testcache”,beforeInvocation=true)
2@Caching
当一个类型有多个注解的时候使用
1
2
3 1@Caching(evict = { @CacheEvict("primary"), @CacheEvict(cacheNames="secondary", key="#p0") })
2public Book importBooks(String deposit, Date date)
3第一个@CacheEvict(“primary”)使用默认值清除名称为primary的缓存,第二个@CacheEvict(cacheNames=”secondary”, key=”#p0”)表示importBooks(String deposit, Date date)的第一个参数作为key清除掉名称为secondary的缓存
@CacheConfig
每次写的时候都需要指定cacheNames,如果都使用同一个cacheNames,可以使用@CacheConfig,将其放在类上
1
2
3
4
5
6 1@CacheConfig("books")
2public class BookRepositoryImpl implements BookRepository {
3 @Cacheable
4 public Book findBook(ISBN isbn) {...}
5}
6
Spring 缓存的配置
开启注解
1
2 1<cache:annotation-driven />
2
CacheManager和Cache分别用Bean配置
1
2
3
4
5
6
7
8
9
10
11
12 1<!-- simple cache manager -->
2<bean id="cacheManager" class="org.springframework.cache.support.SimpleCacheManager">
3 <property name="caches">
4 <set>
5 <!-- 默认的缓存 -->
6 <bean class="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean" p:name="default"/>
7 <!-- 例子中一直使用的"books",也就是那个cacheNames="books" -->
8 <bean class="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean" p:name="books"/>
9 </set>
10 </property>
11</bean>
12
Ehcache
Ehcache简介
EhCache 是一个纯Java的进程内缓存框架,具有快速、精干等特点,是hibernate中默认的CacheProvider。
Ehcache是一种广泛使用的开源Java分布式缓存。主要面向通用缓存,Java EE和轻量级容器。它具有内存和磁盘存储,缓存加载器,缓存扩展,缓存异常处理程序,一个gzip缓存servlet过滤器,支持REST和SOAP api等特点。
Ehcache最初是由Greg Luck于2003年开始开发。2009年,该项目被Terracotta购买。软件仍然是开源,但一些新的主要功能(例如,快速可重启性之间的一致性的)只能在商业产品中使用,例如Enterprise EHCache and BigMemory。维基媒体Foundationannounced目前使用的就是Ehcache技术。
总之Ehcache还是一个不错的缓存技术,我们来看看spring搭配Ehcache是如何实现的。
下载地址:http://www.ehcache.org/generated/2.10.1/html/ehc-all/ehcache.xml配置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 1<?xml version="1.0" encoding="UTF-8"?>
2<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd">
3 <diskStore path="java.io.tmpdir/ehcache"/>
4 <!-- 默认缓存 -->
5 <defaultCache
6 maxElementsInMemory="1000"
7 eternal="false"
8 timeToIdleSeconds="120"
9 timeToLiveSeconds="120"
10 overflowToDisk="false"/>
11 <!-- 菜单缓存 -->
12 <cache name="menuCache"
13 maxElementsInMemory="1000"
14 eternal="false"
15 timeToIdleSeconds="120"
16 timeToLiveSeconds="120"
17 overflowToDisk="false"
18 memoryStoreEvictionPolicy="LRU"/>
19</ehcache>
20ehcache参数说明
- < diskStore> : 当内存缓存中对象数量超过maxElementsInMemory时,将缓存对象写到磁盘缓存中(需对象实现序列化接口)。
- < diskStore path=”“> : 用来配置磁盘缓存使用的物理路径,Ehcache磁盘缓存使用的文件后缀名是*.data和*.index。
- name : 缓存名称,cache的唯一标识(ehcache会把这个cache放到HashMap里)。
- maxElementsOnDisk : 磁盘缓存中最多可以存放的元素数量,0表示无穷大。
- maxElementsInMemory : 内存缓存中最多可以存放的元素数量,若放入Cache中的元素超过这个数值,则有以下两种情况。
1)若overflowToDisk=true,则会将Cache中多出的元素放入磁盘文件中。
2)若overflowToDisk=false,则根据memoryStoreEvictionPolicy策略替换Cache中原有的元素。
- Eternal : 缓存中对象是否永久有效,即是否永驻内存,true时将忽略timeToIdleSeconds和timeToLiveSeconds。
- timeToIdleSeconds : 缓存数据在失效前的允许闲置时间(单位:秒),仅当eternal=false时使用,默认值是0表示可闲置时间无穷大,此为可选属性即访问这个cache中元素的最大间隔时间,若超过这个时间没有访问此Cache中的某个元素,那么此元素将被从Cache中清除。
- timeToLiveSeconds : 缓存数据在失效前的允许存活时间(单位:秒),仅当eternal=false时使用,默认值是0表示可存活时间无穷大,即Cache中的某元素从创建到清楚的生存时间,也就是说从创建开始计时,当超过这个时间时,此元素将从Cache中清除。
- overflowToDisk : 内存不足时,是否启用磁盘缓存(即内存中对象数量达到maxElementsInMemory时,Ehcache会将对象写到磁盘中),会根据标签中path值查找对应的属性值,写入磁盘的文件会放在path文件夹下,文件的名称是cache的名称,后缀名是data。
- diskPersistent : 是否持久化磁盘缓存,当这个属性的值为true时,系统在初始化时会在磁盘中查找文件名为cache名称,后缀名为index的文件,这个文件中存放了已经持久化在磁盘中的cache的index,找到后会把cache加载到内存,要想把cache真正持久化到磁盘,写程序时注意执行net.sf.ehcache.Cache.put(Element element)后要调用flush()方法。
- diskExpiryThreadIntervalSeconds : 磁盘缓存的清理线程运行间隔,默认是120秒。
- diskSpoolBufferSizeMB : 设置DiskStore(磁盘缓存)的缓存区大小,默认是30MB。
- memoryStoreEvictionPolicy : 内存存储与释放策略,即达到maxElementsInMemory限制时,Ehcache会根据指定策略清理内存,共有三种策略,分别为LRU(最近最少使用)、LFU(最常用的)、FIFO(先进先出)。
EhCache的使用
以Java配置的方式设置EhCacheManager
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26 1import org.springframework.cache.annotation.EnableCaching;
2import org.springframework.cache.ehcache.EhCacheCacheManager;
3import org.springframework.cache.ehcache.EhCacheManagerFactoryBean;
4import org.springframework.context.annotation.Bean;
5import org.springframework.context.annotation.Configuration;
6import org.springframework.core.io.ClassPathResource;
7import net.sf.ehcache.CacheManager;
8@Configuration
9@EnableCaching//启用缓存
10public class CachingConfig {
11 /**
12 * @param cacheManager 是 net.sf.ehcache.CacheManager的一个实例
13 * 配置EhCacheCacheManager缓存管理器
14 */
15 @Bean
16 public EhCacheCacheManager cacheManager(CacheManager cacheManager) {
17 return new EhCacheCacheManager(cacheManager);
18 }
19 @Bean
20 public EhCacheManagerFactoryBean ehcache() {
21 EhCacheManagerFactoryBean ehCacheManagerFactoryBean = new EhCacheManagerFactoryBean();
22 ehCacheManagerFactoryBean.setConfigLocation(new ClassPathResource("**/**/ehcache.xml"));
23 return ehCacheManagerFactoryBean;
24 }
25}
26以XML配置的方式设置EhCacheManager
1
2
3
4
5
6
7
8
9
10
11 1<!-- 启用缓存 -->
2 <cache:annotation-driven cache-manager ="cacheManager" />
3 <!--声明一个缓存管理器(EhCacheCacheManager) 这里的实现代码是通过传入EhCache的CacheManager实例实现的 -->
4 <bean id="cacheManager" class="org.springframework.cache.ehcache.EhCacheCacheManager" p:cache-manager-ref="ehcache"/>
5 <!--这里并不是EhCacheManagerFactoryBean的实例,而是EhCache中CacheManager的一个实例 -->
6 <!--因为Spring和EhCache都定义了CacheManager类型 -->
7 <bean id="ehcache" class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean">
8 <property name="configLocation" value="classpath:/ehcache.xml" />
9 <property name="shared" value="true"/>
10</bean>
11
Spring 整合 Ehcache
1.需要的jar包
- slf4j-api-1.6.1.jar
- ehcache-core-2.1.0.jar
- ehcache-spring-annotations-1.1.2.jar
- slf4j-log4j12-1.6.1.jar
- spring-context-support-4.0.6.RELEASE.jar
2.ehcache.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 1<?xml version="1.0" encoding="UTF-8"?>
2<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd">
3 <diskStore path="java.io.tmpdir/ehcache"/>
4 <!-- 默认缓存 -->
5 <defaultCache
6 maxElementsInMemory="1000"
7 eternal="false"
8 timeToIdleSeconds="120"
9 timeToLiveSeconds="120"
10 overflowToDisk="false"/>
11 <!-- 菜单缓存 -->
12 <cache name="menuCache"
13 maxElementsInMemory="1000"
14 eternal="false"
15 timeToIdleSeconds="120"
16 timeToLiveSeconds="120"
17 overflowToDisk="false"
18 memoryStoreEvictionPolicy="LRU"/>
19</ehcache>
203.application_spring_cache.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 1<?xml version="1.0" encoding="UTF-8"?>
2<beans xmlns="http://www.springframework.org/schema/beans"
3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4 xmlns:context="http://www.springframework.org/schema/context"
5 xmlns:cache="http://www.springframework.org/schema/cache"
6 xsi:schemaLocation="http://www.springframework.org/schema/beans
7 http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
8 http://www.springframework.org/schema/context
9 http://www.springframework.org/schema/context/spring-context-3.0.xsd
10 http://www.springframework.org/schema/cache
11 http://www.springframework.org/schema/cache/spring-cache-3.2.xsd">
12 <cache:annotation-driven cache-manager="cacheManager"/>
13 <bean id="cacheManagerFactory" class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean">
14 <property name="configLocation" value="classpath:application/ehcache.xml" />
15 </bean>
16 <bean id="cacheManager" class="org.springframework.cache.ehcache.EhCacheCacheManager">
17 <property name="cacheManager" ref="cacheManagerFactory"/>
18 </bean>
19</beans>
204.使用
1
2
3
4
5
6
7
8
9 1@Cacheable(value="menuCache",key="'UserMenuKey'+#userid")
2 public List<MenuBean> queryMenuByUserId(String userid) {
3 ……
4 }
5@CacheEvict(value="menuCache",key="'UserMenuKey'+#userRoleBean.getUserId()")
6 public boolean delUserRole(UserRoleBean userRoleBean) {
7 ……
8 }
9
缓存 的适应场景
数据不会被第三方修改
一般情况下,会被hibernate以外修改的数据最好不要配置二级缓存,以免引起不一致的数据。但是如果此数据因为性能的原因需要被缓存,同时又有可能被第3方比如SQL修改,也可以为其配置二级缓存。只是此时需要在sql执行修改后手动调用cache的清除方法。以保证数据的一致性 。
数据大小在可接收范围之内
如果数据表数据量特别巨大,此时不适合于二级缓存。原因是缓存的数据量过大可能会引起内存资源紧张,反而降低性能。
如果数据表数据量特别巨大,但是经常使用的往往只是较新的那部分数据。此时,也可为其配置二级缓存。但是必须单独配置其持久化类的缓存策略,比如最大缓存数、缓存过期时间等,将这些参数降低至一个合理的范围(太高会引起内存资源紧张,太低了缓存的意义不大)。数据更新频率低
对于数据更新频率过高的数据,频繁同步缓存中数据的代价可能和 查询缓存中的数据从中获得的好处相当,坏处益处相抵消。此时缓存的意义也不大。
非关键数据
财务数据等是非常重要的数据,绝对不允许出现或使用无效的数据,所以此时为了安全起见最好不要使用二级缓存。