欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

hibernate二级缓存之ehcache初探

程序员文章站 2022-05-10 11:39:23
...
一、Ehcache简介
      EhCache是Hibernate的二级缓存技术之一,可以把查询出来的数据存储在内存或者磁盘,节省下次同样查询语句再次查询数据库,大幅减轻数据库压力;
      当用Hibernate对关系型数据库表进行更改时(DELETE/UPDATE),这时EhCache会自动把缓存中关于此表的所有缓存全部删除掉,以此来达到同步效果。基于这一点来说,ehcache不适合那种经常修改数据库表的情形。
      Ehcache适用场合:
  • 1)对数据库表很少修改;
  • 2)对并发要求不是很严格。

      对于工业传感器实时数据,程序对其保存后,利用二级缓存技术查看历史数据。我本人也是基于这种场景才用ehcache的
二、准备工作
[list]
  • ehcache-core.jar ehcache核心包,在maven项目中,类似于如下引入
  • <dependency>
    	<groupId>net.sf.ehcache</groupId>
    	<artifactId>ehcache-core</artifactId>
    	<version>2.4.3</version>
    </dependency>
    
  • ehcache-spring-annotations 基于spring注解ehcache,在maven项目中,类似于如下引入
  • <groupId>com.googlecode.ehcache-spring-annotations</groupId>
    	<artifactId>ehcache-spring-annotations</artifactId>
    	<version>1.1.2</version>
    	<type>jar</type>
    	<scope>compile</scope>
    </dependency>
    
  • 其他jar包(例如hibernate系列、spring web系列,commons系列)
  • [/list]
    三、Ehcache.xml配置
          Ehcache.xml文件时ehcache二级缓存最主要的我配置文件,它定义了对哪些实体对象进行缓存,以及缓存策略,直接上一个配置片段把
    <?xml version="1.0" encoding="UTF-8"?>
    <ehcache>
    	<cache name="entity.HistoryData"
    	       maxElementsInMemory="5000" eternal="false" timeToIdleSeconds="300"
    	       timeToLiveSeconds="7200" overflowToDisk="false" />
    </ehcache>


    name 要缓存的实体类的名称
    maxElementsInMemory 设置该缓存实体的最大个数
    eternal 对象是否永久有效
    timeToIdleSeconds 设置对象在失效前的允许闲置时间(单位:s)
    timeToLiveSeconds 设置对象在失效前允许存活时间(单位:s)
    overflowToDisk 当缓存中对象达到maxElementsInMemory时,存入磁盘
    diskSpoolBufferSizeMB 设置磁盘缓冲区大小
    maxElementsOnDisk 设置磁盘能缓存最大对象个数
    diskPersistent 是否缓存虚拟机重启期数
    diskExpiryThreadIntervalSeconds 磁盘失效线程运行时间间隔,默认是120秒。
    memoryStoreEvictionPolicy 当达到maxElementsInMemory限制时,Ehcache将会根据指定的策略去清理内存。默认策略是LRU(最近最少使用)。你可以设置为FIFO(先进先出)或是LFU(较少使用)。 
    clearOnFlush 内存数量最大时是否清除。

    两个问题:
    • 该文件放置在那里?该文件可以放置在工程的任意位置
    • 该文件如何引入?或者说ehcache.xml路径是如何确定的?

        我们可以通过查阅相关源码,可知
    在hibernate.cache.provider所指定的类net.sf.ehcache.hibernate.EhCacheRegionFactory类的抽象类AbstractEhcacheRegionFactory中,对NET_SF_EHCACHE_CONFIGURATION_RESOURCE_NAME中有明确的解释:
    /**
    The Hibernate system property specifying the location of the ehcache configuration file name.
        If not set, ehcache.xml will be looked for in the root of the classpath.
    If set to say ehcache-1.xml, ehcache-1.xml will be looked for in the root of the classpath.
    **/
    

    [/list]
    四、集成到spring
          这一个步骤分为两部分,一部分是对ehcache打开注解功能,另一部分是集成hibernate sesionFactory中
    [list]
  • 配置注解,新建spring-ehcache.xml,并在web.xml param中引入,如下:
    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
    	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    	xmlns:ehcache="http://ehcache-spring-annotations.googlecode.com/svn/schema/ehcache-spring"
    	xsi:schemaLocation="  
        	http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd  
        	http://ehcache-spring-annotations.googlecode.com/svn/schema/ehcache-spring http://ehcache-spring-annotations.googlecode.com/svn/schema/ehcache-spring/ehcache-spring-1.1.xsd">
    	<ehcache:annotation-driven />
    	<bean id="cacheManager" class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean">
    		<property name="configLocation" value="classpath:ehcache.xml" />
    	</bean>
    	<ehcache:config cache-manager="cacheManager"/>
    </beans>

    注:对于命名空间,前提是引入ehcache-spring-annotations.jar包,否则提示无法解析该命名空间。ConfigLocation指定ehcache.xml配置文件所在的路径。
  • hibernate sessionFactory配置,新建spring-sessionFactory.xml,并在web.xml中引入,如下
  • <?xml version="1.0" encoding="utf-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
    	xmlns:p="http://www.springframework.org/schema/p" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    	xmlns:util="http://www.springframework.org/schema/util" xmlns:jee="http://www.springframework.org/schema/jee"
    	xmlns:lang="http://www.springframework.org/schema/lang" xmlns:jms="http://www.springframework.org/schema/jms"
    	xmlns:tx="http://www.springframework.org/schema/tx" xmlns:aop="http://www.springframework.org/schema/aop"
    	xmlns:context="http://www.springframework.org/schema/context"
    	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
                               http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-3.0.xsd
                               http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-3.0.xsd
                               http://www.springframework.org/schema/lang http://www.springframework.org/schema/lang/spring-lang-3.0.xsd
                               http://www.springframework.org/schema/jms http://www.springframework.org/schema/jms/spring-jms-3.0.xsd
                               http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
                               http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
                               http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd">
      
       <context:property-placeholder location="classpath:/resource.properties" />
       
       <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">
       		<property name="driverClass" value="${jdbc.driverClassName}"/>
       		<property name="jdbcUrl" value="${jdbc.url}"/>
       		<property name="user" value="${jdbc.username}"/>
       		<property name="password" value="${jdbc.password}"/>
       		<property name="idleConnectionTestPeriod" value="120"/>
      	</bean>
      	<bean id="mySessionFactory" class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
      		<property name="dataSource" ref="dataSource"/>
      		<property name="schemaUpdate" value="${hibernate.schemaUpdate}"/>
      		<property name="packagesToScan" value="${hibernate.packageScan}"/>
    		<property name="hibernateProperties">
    			<value>
    				hibernate.dialect ${hibernate.dialect}
    				hibernate.show_sql ${hibernate.show_sql}
    				hibernate.cache.region.factory_class ${hibernate.cache.provider}
                    hibernate.cache.use_second_level_cache ${hiberante.second.cache}
    		[color=red]Hibernate.cache.use_query_cache true	[/color]	
    			</value>
    		</property>
    	</bean>
    </beans>

    其中,
    • hibernate.cache.provider 选择二级缓存实现的类
    • Hibernate.cache.use_second_level_cache:选择是否开启二级缓存
    • Hibernate.cache.use_query_cache 是否开启查询缓存(重要,后面讲到)

    [/list]
    五、测试
    新建一个实体类
    package entity;
    
    import java.io.Serializable;
    import java.util.Date;
    
    import javax.persistence.Column;
    import javax.persistence.Entity;
    import javax.persistence.GeneratedValue;
    import javax.persistence.Id;
    import javax.persistence.Table;
    
    import org.hibernate.annotations.Cache;
    import org.hibernate.annotations.CacheConcurrencyStrategy;
    
    @Entity
    @Table(name="history_data")
    @Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
    public class HistoryData implements Serializable{
    	//省略
    }

    把这个类映射到数据库表中。并且设置缓存方式

    缓存方式有四种
    参考资料 写道
    CacheConcurrencyStrategy.NONE
       CacheConcurrencyStrategy.READ_ONLY ,只读模式,在此模式下,如果对数据进行更新操作,会有异常;
       CacheConcurrencyStrategy.READ_WRITE ,读写模式在更新缓存的时候会把缓存里面的数据换成一个锁,其它事务如果去取相应的缓存数据,发现被锁了,直接就去数据库查询;
       CacheConcurrencyStrategy.NONSTRICT_READ_WRITE ,不严格的读写模式则不会的缓存数据加锁;
       CacheConcurrencyStrategy.TRANSACTIONAL ,事务模式指缓存支持事务,当事务回滚时,缓存也能回滚,只支持 JTA 环境。

    缓存的注释写法如下,加在 Entity 的 java 类上:
    @Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)

        如果我们要对hql查询设置缓存,可以通过设置this.getHibernateTemplate().setCacheQueries(boolean)来实现,前提是开启查询缓存。下面是一个例子:
    public List<HistoryData> list(String name,String location,Date startTime,
    			Date endTime, boolean alarm,int start,int limit){
    		String hql = "from HistoryData hd where 1=1 ";
    		if(StringUtils.isNotBlank(name)){
    			hql+= " and hd.name like '%"+name+"%' ";
    		}
    		if(StringUtils.isNotBlank(location)){
    			hql+= " and hd.location like '"+location+"' ";
    		}
    		this.getHibernateTemplate().setCacheQueries(true);
    		List<HistoryData> hds =  this.findByNamedParam(hql, null, null, start, limit);
    		this.getHibernateTemplate().setCacheQueries(false);
    		return hds;
    	}


        调用这个方法,针对同一sql来讲,只发送一条sql,下次查询直接从缓存中获取。
    注:this.getHibernateTemplate().setCacheQueries(true);后最好是要this.getHibernateTemplate().setCacheQueries(false),来关闭查询缓存,因为实际应用中,不可能所有的查询我们都得设置缓存。