Hibernate缓存的使用
使用缓存能使Hibernate的查询性能获得有效地提升,可以避免数据重复查询带来的性能开销,通过缓存可以实现数据的快速读取,但是并不意味着缓存一定能提升性能,相反,如果处理不当也可能造成致命伤害。
在项目开发之中有两种常见的缓存组件:OSCache、EHCache,其中在Hibernate之中主要使用EHCache,之所以使用此组件主要是考虑到简洁问题,在缓存之中至少需要考虑以下几种情况:缓存的个数、缓存数据如何保存、缓存数据如何同步。
在Hibernate之中主要支持两类缓存:
- Session级缓存(一级缓存、first_level-cache),在使用get()或load()方法的时候,对象实际就一直在持久态的状态,这实际上就是一级缓存的配置,但是如果Session关闭了,那么一级缓存的内容将消失,值得注意的是,一级缓存永远存在。
- SessionFactory级缓存(二级缓存、second_level-cache) ,指的是跨越Session的存在,可以在多个Session对象之中共享数据,二级缓存是需要额外配置的。
如果Hibernate项目之中需要使用缓存,那么必须在项目创建的时候准备好第三方组件包。
一级缓存
一级缓存是Session级缓存,永远都会存在,也就是说只要用户执行了save()或get()等方法,那么这个对象在Session关闭前会一直被保留下来。
一级缓存的控制操作都在Session接口里定义了,定义有如下几个方法:
方法 | 描述 |
---|---|
public void clear() | 清空所有缓存数据 |
public void flush() throws HibernateException | 清空刷新缓冲区 |
public void evict(Object object) | 清除一个缓存对象 |
【范例:观察如何清除一个对象】
import com.gub.dbc.HibernateSessionFactory;
import com.gub.vo.Dept;
public class SessionTest {
public static void main(String[] args) {
//查询操作
Dept temp = (Dept)HibernateSessionFactory.getSession().get(Dept.class,4);
//将之前查询出来的对象(保存在缓存中)进行缓存的清除
HibernateSessionFactory.getSession().evict(temp);
//此时没有关闭Session,再一次发出查询操作,此时缓存没有数据
System.out.println(HibernateSessionFactory.getSession().get(Dept.class,4));
}
}
运行后发现发出了两条查询指令,原因在于,第一个对象查询出来之后进行了删除缓存对象的操作,所以在第二次查询同样主键的时候,如果发现数据在缓存里面不存在,那么就将重新发出查询指令。
【范例:批量数据增加】
import com.gub.dbc.HibernateSessionFactory;
import com.gub.vo.Dept;
import java.util.Date;
public class SessionTest {
public static void main(String[] args) {
Dept dept = null;
for(int x=10;x<100;x++){
dept = new Dept();
dept.setDeptno(x);
dept.setDname("部门-"+x);
dept.setCredate(new Date());
dept.setAddress("****");
dept.setTel("0471-48451256");
HibernateSessionFactory.getSession().save(dept);
if(x%10==0){//每10条记录进行一次缓存的刷新
HibernateSessionFactory.getSession().flush();
HibernateSessionFactory.getSession().clear();
HibernateSessionFactory.getSession().beginTransaction().commit();
}
}
HibernateSessionFactory.closeSession();
}
}
Hibernate中的一级缓存会一直存在直到Session的关闭,如果缓存内容过多,程序可能会出现问题,可以使用Session中的flush()与clear()两个方法进行强制性的缓存清空,保证缓存的容量不会占用过多。
二级缓存
在Hibernate默认状态下,每一个Session缓存的数据是不能够进行共享的。如果希望在不同的Session上实现数据缓存的共享,那么就需要使用二级缓存,与一级缓存不同,二级缓存必须由用户单独配置(需要先导入一系列开发包)
- 定义缓存配置文件:ehcache.xml文件
<ehcache>
<!--指的是缓存的临时保存目录-->
<diskStore path="java.io.tmpdir"/>
<!--此处进行默认的缓存配置-->
<defaultCache
maxElementsInMemory="10000"
eternal="false"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
overflowToDisk="true"
/>
</ehcache>
通过defaultCache的属性配置缓存的默认信息
属性名 | 描述 |
---|---|
maxElementsInMemory | 可以缓存的最大的POJO对象的个数 |
eternal | 是否允许缓存自动失效 |
timeToIdleSeconds | 最小的失效时间 |
timeToLiveSeconds | 最大的保存时间 |
overflowToDisk | 如果保存过多则自动利用磁盘存储缓存 |
- 在hibernate.cfg.xml文件里面配置使用的缓存类型
<property name="hibernate.cache.region.factory_class">
org.hibernate.cache.ehcache.EhCacheRegionFactory
</property>
-
并不是所有的POJO类都需要使用到二级缓存,所以应该在支持缓存的位置上进行启用,有以下三种启用的操作形式。
- 如果项目是基于*.hbm.xml文件配置的,则可以在*.hbm.xml的class节点内增加以下配置:
<cache usege="read-only" />
- 如果项目使用的是Annotation,则可以利用以下注解在POJO类中配置:
@Cache(usage = CacheConcurrencyStrategy.READ_ONLY)
- 考虑到项目可能会修改,如果配置在每一个文件上会比较麻烦,那么可以直接在hibernate.cfg.xml文件里面进行综合的配置(优势:只需要维护一个文件):
<class-cache class="com.gub.vo.Dept" usage="read-only" />
【范例:观察二级缓存】
import com.gub.dbc.HibernateSessionFactory;
import com.gub.vo.Dept;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
public class SecondCacheTest {
public static void main(String[] args) {
SessionFactory sessionFactory = HibernateSessionFactory.getSessionFactory();
Session sessionA = sessionFactory.openSession();
//由于二级缓存的存在,此时的查询结果会自动写入二级缓存之中
System.out.println(sessionA.get(Dept.class,1));
Session sessionB = sessionFactory.openSession();
//此时默认先寻找二级缓存中的数据是否存在,如果存在则直接读取
System.out.println(sessionB.get(Dept.class,1));
}
}
此时发现两个不同的Session按相同的索引执行查询操作时,只执行了一条查询语句,说明二级缓存使用成功。
二级缓存操作的常用模式有以下几种:
模式 | 说明 |
---|---|
READ_ONLY(只读缓存) | 将数据查询结果保存在缓存之中,以供其他的Session使用 |
READ_WRITE(读写缓存) | 当数据库里的数据发生变化之后,那么可是进行及时的数据追踪,缓存性能最差 |
NONSTRICT_READ_WRITE(不严格的读写操作) | 在数据库发生变化之后不会立即进行缓存的更新,而是等待一段时间之后在进行数据的更新操作 |
TRANSACTIONAL(事物的处理缓存) | 如果数据库中的数据发生了回滚操作,内存中的数据也要一起发生回滚操作 |
缓存交互
项目中如果配置了二级缓存,只要是通过Session查询的数据都会默认保存在二级缓存之中以供其他Session使用,但这些都是默认的配置形式,用户也可以根据自己的需要来配置是否需要二级缓存,配置的方法主要是在Session接口中:
方法名 | 描述 |
---|---|
public void setCacheMode(CacheMode cacheMode) | 设置缓存模式 |
CacheMode 属于枚举类型,其定义了如下几种缓存模式:
名称 | 说明 |
---|---|
public static final CacheMode GET | 从缓存读取数据,但是不保存 |
public static final CacheMode PUT | 向缓存保存数据,但是不能取出 |
public static final CacheMode NORMAL | 正常进行缓存数据的读或写 |
public static final CacheMode REFRESH | 针对于缓存中的重复数据进行刷新操作 |
【范例:观察GET模式】
import com.gub.dbc.HibernateSessionFactory;
import com.gub.vo.Dept;
import org.hibernate.CacheMode;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
public class CacheInteraction {
public static void main(String[] args) {
SessionFactory sessionFactory = HibernateSessionFactory.getSessionFactory();
Session sessionA = sessionFactory.openSession();
sessionA.setCacheMode(CacheMode.GET);//此时不向二级缓存写入数据,值进行读取
System.out.println(sessionA.get(Dept.class,1));
Session sessionB = sessionFactory.openSession();
//此时默认先寻找二级缓存中的数据是否存在,如果存在则直接读取
System.out.println(sessionB.get(Dept.class,1));
}
}
执行后发现数据库进行了两次查询操作,因为缓存模式为GET,所以内容不会像二级缓存中保存。
查询缓存
以上的一些列缓存操作都是基于Session完成的,但是在实际开发中,数据的查询一般都通过Query接口来完成,但是如果利用Query查询,配置的二级缓存无效,也就是说此时没有使用到缓存的配置,因为Query接口默认情况下并没有启动缓存的支持,在Query接口中定义了如下方法:
方法名 | 描述 |
---|---|
public Query setCacheable(boolean cacheable) | 配置是否支持缓存 |
public Query setCacheMode(CacheMode cacheMode) | 配置缓存模式 |
默认情况下Hibernate没有启动查询缓存的支持,所以如果想要使用查询缓存,还要再hibernate.cfg.xml文件中添加以下属性:
<property name="cache.use_query_cache">true</property>
【范例:Query接口使用二级缓存】
import com.gub.dbc.HibernateSessionFactory;
import com.gub.vo.Dept;
import org.hibernate.CacheMode;
import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
public class QueryCache {
public static void main(String[] args) {
String hql = "FROM Dept AS d WHERE d.deptno=?";
SessionFactory sessionFactory = HibernateSessionFactory.getSessionFactory();
//sessionA查询数据
Session sessionA = sessionFactory.openSession();
Query queryA = sessionA.createQuery(hql);
queryA.setCacheable(true);
queryA.setCacheMode(CacheMode.PUT);
queryA.setParameter(0,1);
Dept deptA = (Dept)queryA.uniqueResult();
System.out.println("deptA = " + deptA);
//session查询数据
Session sessionB = sessionFactory.openSession();
Query queryB = sessionB.createQuery(hql);
queryB.setCacheable(true);
queryA.setCacheMode(CacheMode.GET);
queryB.setParameter(0,1);
Dept deptB = (Dept)queryB.uniqueResult();
System.out.println("deptB = " + deptB);
}
}
此时发现数据库只执行了一条查询语句,缓存使用成功。
对于Criteria查询也有相应的方法支持,与Query查询的使用方法相同。