Ehcache学习
1. 介绍
ehcache可以作为Hibernate和mybatis的二级缓存,并且可以很好的与spring结合。
Hibernate的缓存有两级
- 一级缓存:Session级别的缓存,每个session都有自己的cache,当前操作对象都会被保留在cache中,事务提交或回滚了,这个session也就关闭,cache就没有了,所以一级缓存生命周期非常短暂。
- 二级缓存:SessionFactory范围的缓存,可以被来自同一个*SessionFactory中的Session所共享*,这种缓存使用的范围更广,有不同的实现,比如Ehcache,OsCache。
缓存的方式有四种:
- CacheConcurrencyStrategy.READ_ONLY ,只读模式,在此模式下,如果对数据进行更新操作,会有异常;
- CacheConcurrencyStrategy.READ_WRITE ,读写模式在更新缓存的时候会把缓存里面的数据换成一个锁,其它事务如果去取相应的缓存数据,发现被锁了,直接就去数据库查询;
- CacheConcurrencyStrategy.NONSTRICT_READ_WRITE ,不严格的读写模式则不会的缓存数据加锁;
- CacheConcurrencyStrategy.TRANSACTIONAL ,事务模式指缓存支持事务,当事务回滚时,缓存也能回滚,只支持 JTA 环境。
缓存的注释写法如下,加在 Entity 的 java 类上:
@Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
2.配置
ehcache.xml
<?xml version="1.0" encoding="UTF-8"?>
<ehcache>
<diskStore path="java.io.tmpdir" />
<defaultCache maxElementsInMemory="1000" eternal="false"
timeToIdleSeconds="1200" timeToLiveSeconds="1200"
overflowToDisk="true" clearOnFlush="true"
memoryStoreEvictionPolicy="LFU">
</defaultCache>
<cache name="SimplePageFragmentCachingFilter" maxElementsInMemory="10"
maxElementsOnDisk="10" eternal="false" overflowToDisk="true"
diskSpoolBufferSizeMB="20" timeToIdleSeconds="120"
timeToLiveSeconds="120" memoryStoreEvictionPolicy="LFU" />
<!-- 单独对某个entity的缓存策略设置
<cache name="com.shop.domain.User"
maxElementsInMemory="100" eternal="false" timeToIdleSeconds="1200"
timeToLiveSeconds="1200" overflowToDisk="true" clearOnFlush="true">
</cache>
-->
</ehcache>
persistence.xml
<persistence xmlns="http://java.sun.com/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence
http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd"
version="1.0">
<persistence-unit name="fmcgshop" transaction-type="RESOURCE_LOCAL">
<provider>org.hibernate.ejb.HibernatePersistence</provider>
<class>com.iskyshop.foundation.domain.ItemSort</class>
<properties>
<property name="hibernate.show_sql" value="false" />
<property name="hibernate.hbm2ddl.auto" value="update" />
<!--Ehcache二级缓存配置-->
<property name="hibernate.cache.provider_class"
value="org.hibernate.cache.EhCacheProvider" />
<property name="hibernate.cache.provider_configuration"
value="/ehcache.xml" />
<property name="hibernate.cache.use_second_level_cache"
value="true" />
<property name="hibernate.cache.use_query_cache"
value="true" />
<property name="hibernate.connection.url" value="jdbc:mysql://localhost:3306/shop"/>
<property name="hibernate.connection.driver_class" value="com.mysql.jdbc.Driver"/>
</properties>
</persistence-unit>
</persistence>
entityManagerFactory
<bean name="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="persistenceXmlLocation" value="classpath:persistence.xml" />
<property name="dataSource" ref="dataSource" />
<property name="jpaVendorAdapter">
<bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
<property name="database" value="MYSQL" />
<property name="showSql" value="false" />
<property name="generateDdl" value="false" />
</bean>
</property>
<property name="jpaProperties">
<props>
<!-- 自定义方言 -->
<prop key="hibernate.dialect">
com.iskyshop.core.dialect.SystemMySQL5Dialect
</prop>
<!-- 二级缓存配置 -->
<prop key="hibernate.cache.provider_class">
net.sf.ehcache.hibernate.SingletonEhCacheProvider
</prop>
<prop key="hibernate.cache.use_query_cache">true</prop>
<prop key="hibernate.cache.use_second_level_cache">
true
</prop>
<prop key="hibernate.use_sql_comments">false</prop>
<prop key="hibernate.format_sql">false</prop>
<prop key="hibernate.generate_statistics">true</prop>
</props>
</property>
</bean>
配置页面缓存
页面缓存主要用Filter过滤器对请求的url进行过滤,如果该url在缓存中出现。那么页面数据就从缓存对象中获取,并以gzip压缩后返回。其速度是没有压缩缓存时速度的3-5倍,效率相当之高!其中页面缓存的过滤器有CachingFilter,一般要扩展filter或是自定义Filter都继承该CachingFilter。
CachingFilter功能可以对HTTP响应的内容进行缓存。这种方式缓存数据的粒度比较粗,例如缓存整张页面。它的优点是使用简单、效率高,缺点是不够灵活,可重用程度不高。
EHCache使用SimplePageCachingFilter类实现Filter缓存。该类继承自CachingFilter,有默认产生cache key的calculateKey()方法,该方法使用HTTP请求的URI和查询条件来组成key。也可以自己实现一个Filter,同样继承CachingFilter类,然后覆写calculateKey()方法,生成自定义的key。
CachingFilter输出的数据会根据浏览器发送的Accept-Encoding头信息进行Gzip压缩。
/**
* 页面缓存,主要用filter过滤器对请求的url进行过滤,如果该url在缓存中出现,那么页面数据就从缓存中取,并以GZIP压缩后返回
* Page caching, primarily using the filter filter to filter the requested url,
* if the url appears in the cache,the page data is fetched from the cache and returned with GZIP compression
*/
public class PageCacheFiler extends SimplePageFragmentCachingFilter {
private static final String FILTER_URL_PATTERNS = "patterns";
private static String[] cacheURLs;
private void init() throws CacheException {
String patterns = this.filterConfig.getInitParameter("patterns");
patterns = "/head.htm,/floor.htm,/advert_invoke.htm,public.css,index.css,goods.css,integral.css,user.css,window.css,jquery-1.6.2.js,jquery.lazyload.js,jquery-ui-1.8.21.js,jquery.shop.common.js,jquery.validate.min.js,jquery.jqzoom-core.js,jquery.metadata.js,jquery.rating.pack.js";
PageCacheFiler.cacheURLs = StringUtils.split(patterns, ",");
}
protected void doFilter(final HttpServletRequest request, final HttpServletResponse response, final FilterChain chain) throws Exception {
if (PageCacheFiler.cacheURLs == null) {
this.init();
}
final String url = request.getRequestURI();
final String include_url = CommUtil.null2String(request.getAttribute("javax.servlet.include.request_uri"));
boolean flag = false;
if (PageCacheFiler.cacheURLs != null && PageCacheFiler.cacheURLs.length > 0) {
String[] cacheURLs;
for (int length = (cacheURLs = PageCacheFiler.cacheURLs).length, i = 0; i < length; ++i) {
final String cacheURL = cacheURLs[i];
if (include_url.trim().equals("")) {
if (url.contains(cacheURL.trim())) {
//如果请求的url 在缓存pattern中
flag = true;
break;
}
} else if (include_url.contains(cacheURL.trim())) {
flag = true;
break;
}
}
}
// 如果包含我们要缓存的url 就缓存该页面,否则执行正常的页面转向
if (flag) {
super.doFilter(request, response, chain);
} else {
chain.doFilter(request, response);
}
}
private boolean headerContains(final HttpServletRequest request, final String header, final String value) {
this.logRequestHeaders(request);
final Enumeration accepted = request.getHeaders(header);
while (accepted.hasMoreElements()) {
final String headerValue = (String) accepted.nextElement();
if (headerValue.contains(value)) {
return true;
}
}
return false;
}
protected boolean acceptsGzipEncoding(final HttpServletRequest request) {
final boolean ie6 = this.headerContains(request, "User-Agent", "MSIE 6.0");
final boolean ie2 = this.headerContains(request, "User-Agent", "MSIE 7.0");
return this.acceptsEncoding(request, "gzip") || ie6 || ie2;
}
/**
* 这个方法非常重要,重写计算缓存key的方法,没有该方法include的url值会出错
*/
protected String calculateKey(final HttpServletRequest httpRequest) {
final String include_url = CommUtil.null2String(httpRequest.getAttribute("javax.servlet.include.request_uri"));
final StringBuilder stringBuffer = new StringBuilder();
if (include_url.equals("")) {
stringBuffer.append(httpRequest.getRequestURI()).append(httpRequest.getQueryString());
return stringBuffer.toString();
}
stringBuffer.append(CommUtil.null2String(httpRequest.getAttribute("javax.servlet.include.request_uri"))).append(CommUtil.null2String(httpRequest.getAttribute("javax.serlvet.include.query_string")));
return stringBuffer.toString();
}
}
web.xml
<!--页面缓存配置-->
<filter>
<filter-name>SimplePageFragmentCachingFilter</filter-name>
<filter-class>
com.fmcg.core.ehcache.PageCacheFiler
</filter-class>
<init-param>
<param-name>patterns</param-name>
<param-value>
</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>SimplePageFragmentCachingFilter</filter-name>
<url-pattern>/*</url-pattern>
<dispatcher>request</dispatcher>
<dispatcher>forward</dispatcher>
<dispatcher>include</dispatcher>
</filter-mapping>
3.清除缓存
当数据发生变化时,需要清除缓存
CacheManager manager = CacheManager.create();
String[] names = manager.getCacheNames();
for (String name : names) {
//清除所有对象缓存
if (!name.equalsIgnoreCase("SimplePageFragmentCachingFilter")) {
manager.clearAllStartingWith(name);
}
}
//清除页面缓存
Ehcache cache = manager.getEhcache("SimplePageCachingFilter");
manager.clearAllStartingWith("SimplePageFragmentCachingFilter");