Hibernate中,缓存机制将对象保存在内存中而非每次都访问数据库无疑大大提高了程序的运行性能。因此此篇博文旨在总结Hibernate的缓存机制,希望在项目中或者面试提问中能够有所帮助。
一、 缓存作用
Hibernate是一个持久层框架,经常访问物理数据库,为了降低应用程序对物理数据源访问的频次,从而提高应用程序的运行性能。缓存内的数据是对物理数据源中的数据的复制,应用程序在运行时从缓存读写数据,在特定的时刻或事件会同步缓存和物理数据源的数据。
如何应用缓存:首先,无论通过何种方式查询对象,都会存入一级缓存中(hql语句或者get,load)。当Hibernate根据ID访问数据对象的时候,首先从Session一级缓存中查;查不到,如果配置了二级缓存,那么从二级缓存中查;如果都查不到,再查询数据库,查询后将结果按照ID放入到缓存,删除、更新、增加数据的时候,同时更新缓存。Hibernate管理缓存实例无论何时,当你给save()、update()或saveOrUpdate()方法传递一个对象时,或使用load()、 get()、list()、iterate() 或scroll()方法获得一个对象时, 该对象都将被加入到Session的内部缓存中。当随后flush()方法被调用时,对象的状态会和数据库取得同步。如果你不希望此同步操作发生,或者你正处理大量对象、需要对有效管理内存时,你可以调用evict() 方法,从一级缓存中去掉这些对象及其集合。
二、 一级缓存
又称session缓存。它是内置的,作用范围在一个session范围内。即在同一个session内,查询的对象会被存入缓存,如果再根据ID查询到这个对象,会先到缓存中找。如果找到了,则不需要去数据库查询,直接取出来即可,如果没找到,再到数据库查询这个对象。session关闭后,缓存清空。
实例:
public class FirstCacheTest {
@Test
public void test(){
Session session = HibernateSessionFactory.getSession();
Transaction tr = session.beginTransaction();
String hql = "select a from Admin a";
Query q = session.createQuery(hql);
List<Admin> list = q.list();
Admin admin = (Admin)session.get(Admin.class, 1);
tr.commit();
}
}
运行结果
[INFO] starting query cache at region: org.hibernate.cache.StandardQueryCache
Hibernate: select admin0_.id as id0_, admin0_.name as name0_, admin0_.pswd as pswd0_, admin0_.email as email0_, admin0_.status as status0_, admin0_.create_time as create6_0_, admin0_.flag as flag0_ from t_admin admin0_
[INFO] Shutting down 'proxool' pool immediately [Shutdown Hook]
[INFO] Stopping Prototyper thread
[INFO] Stopping HouseKeeper thread
只有一次查询操作。
三、 二级缓存
二级缓存就是针对不同session提出的另一种缓存机制。二级缓存又称为“SessionFactory的缓存”,由于SessionFactory对象的生命周期和应用程序的整个过程对应,因此Hibernate二级缓存是进程范围或者集群范围的缓存,有可能出现并发问题,因此需要采用适当的并发访问策略,
要使用Hibernate二级缓存时,需要进行额外的配置。而在日常使用中,Ehcache使用最多,因此以下以Ehcache为例配置使用二级缓存。
1、 添加ehcache相应的依赖包。
2、 在hibernate.cfg.xml中配置二级缓存属性。
针对Hibernate4:
<!-- 开启二级缓存 -->
<property name="hibernate.cache.use_second_level_cache">true</property>
<!-- 二级缓存的提供类 在hibernate4.0版本以后我们都是配置这个属性来指定二级缓存的提供类-->
<property name="hibernate.cache.region.factory_class">org.hibernate.cache.ehcache.EhCacheRegionFactory</property>
<!-- 二级缓存配置文件的位置 -->
<property name="hibernate.cache.provider_configuration_file_resource_path">ehcache.xml</property>
Hibernate3中有一处不同:
<property name="cache.provider_class">org.hibernate.cache.EhCacheProvider</property>
3、 配置ehcache.xml:
<?xml version="1.0" encoding="UTF-8"?>
<ehcache>
<diskStore path="d:/test/ehcache" />
<!-- maxElementsInMemory:内存中缓存对象最大数量 eternal:是否永远不变 OverflowToDisk:内存满了是否放入硬盘
timeToIdleSeconds:可以操作对象的时间 timeToLiveSeconds: 缓存中对象的生命周期 -->
<defaultCache maxElementsInMemory="200" eternal="false"
overflowToDisk="true" timeToIdleSeconds="120" timeToLiveSeconds="120"
diskPersistent="false" diskExpiryThreadIntervalSeconds="120" />
<cache name="org.hibernate.cache.StandardQueryCache"
maxElementsInMemory="10000" eternal="false" timeToIdleSeconds="300"
timeToLiveSeconds="4200" overflowToDisk="true" />
<cache name="org.hibernate.cache.UpdateTimestampsCache"
maxElementsInMemory="5000" eternal="true" timeToIdleSeconds="0"
timeToLiveSeconds="0" overflowToDisk="false" />
<!-- entity -->
<cache name="com.entity.Admin" maxElementsInMemory="200" eternal="false"
overflowToDisk="true" timeToIdleSeconds="120" timeToLiveSeconds="120"
diskPersistent="false" diskExpiryThreadIntervalSeconds="120" >
</cache>
</ehcache>
4、 开启二级缓存:
在entity.hbm.xml中配置
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="com.entity.Admin" table="t_admin">
<cache usage="read-only" />
<id name="id" type="java.lang.Integer">
<column name="id" />
<generator class="native" />
</id>
<property name="loginName" type="java.lang.String">
<column name="name" />
</property>
<property name="password" type="java.lang.String">
<column name="pswd" />
</property>
<property name="email" type="java.lang.String">
<column name="email" />
</property>
<property name="status" type="java.lang.Integer">
<column name="status" />
</property>
<property name="time" type="java.sql.Timestamp">
<column name="create_time" />
</property>
<property name="flag" type="java.lang.Integer">
<column name="flag" />
</property>
</class>
</hibernate-mapping>
注意:我们通常使用二级缓存都是将其配置成 read-only ,即我们应当在那些不需要进行修改的实体类上使用二级缓存,否则如果对缓存进行读写的话,性能会变差,这样设置缓存就失去了意义。同时设置为read-only时,会存在读到脏数据的可能性。因此请衡量使用二级缓存的数据能否接受这种情况。
实例:
@Test
public void test(){
Session session = HibernateSessionFactory.getSession();
Transaction tr = session.beginTransaction();
Admin admin = (Admin)session.get(Admin.class, 1);
tr.commit();
Session session2 = HibernateSessionFactory.getSession();
Transaction tr2 = session2.beginTransaction();
Admin admin2 = (Admin)session2.get(Admin.class, 1);
tr2.commit();
}
运行结果:
[INFO] starting query cache at region: org.hibernate.cache.StandardQueryCache
Hibernate: select admin0_.id as id0_0_, admin0_.name as name0_0_, admin0_.pswd as pswd0_0_, admin0_.email as email0_0_, admin0_.status as status0_0_, admin0_.create_time as create6_0_0_, admin0_.flag as flag0_0_ from t_admin admin0_ where admin0_.id=?
[INFO] Shutting down 'proxool' pool immediately [Shutdown Hook]
四、 HQL语句缓存
当使用HQL语句查询时,会因为不直接使用ID查询的方式而无法使用缓存的数据,但是当对HQL语句缓存后,下一次HQL查询也是SessionFactory范围的缓存。但是必须注意,只有在HQL语句完全一致,且参数也完全一致的情况下,才会命中缓存。
配置:
<property name="hibernate.cache.use_query_cache">true</property>
实例:
@Test
public void test2(){
Session session = HibernateSessionFactory.getSession();
Transaction tr = session.beginTransaction();
String hql = "from Admin where id =:id";
Query q = session.createQuery(hql);
q.setInteger("id", 1);
q.setCacheable(true);
Admin admin = (Admin)q.uniqueResult();
tr.commit();
Session session2 = HibernateSessionFactory.getSession();
Transaction tr2 = session2.beginTransaction();
String hql2 = "from Admin where id =:id";
Query q2 = session2.createQuery(hql);
q2.setInteger("id", 1);
q2.setCacheable(true);
Admin admin2 = (Admin)q2.uniqueResult();
tr2.commit();
}
运行结果:
[INFO] starting query cache at region: org.hibernate.cache.StandardQueryCache
Hibernate: select admin0_.id as id0_, admin0_.name as name0_, admin0_.pswd as pswd0_, admin0_.email as email0_, admin0_.status as status0_, admin0_.create_time as create6_0_, admin0_.flag as flag0_ from t_admin admin0_ where admin0_.id=?
[INFO] Shutting down 'proxool' pool immediately [Shutdown Hook]
参考文章:
http://www.cnblogs.com/xiaoluo501395377/p/3377604.html
http://www.cnblogs.com/bkchengzheng/articles/5267585.html