Hibernate缓存机制浅谈
Hibernate缓存
对于一个成熟的项目而言,缓存是必不可少的数据存储策略。
在实际项目开发过程中,Hibernate是一个持久层框架,频繁访问物理数据库是一个高消耗、高延迟的操作。对服务器造成的压力比较大。所 以,需要缓存,来减轻数据库的压力,提高网站的访问速度和性能。比如,对于电商类的项目中商品、热销、推荐 等信息可以放在缓存中,提高访问速度、减轻服务器的压力。
1Hibernate的一级缓存(Session级缓存)
一级缓存是Session级别的缓存,它属于事务范围的缓存,该级缓存由hibernate管理,应用程序无需干预。
Hibernate的一级缓存由Session提供,只存在于Session的生命周期中,当应用程序调用Session接口的save(),update(),saveOrupDate(),get(),load()或者Query和Criteria实例的list(),iterate()等方法时,如果Session缓存中没有相应的对象,hibernate就会把对象加入到一级缓存中,当session关闭时,该Session所管理的一级缓存也会立即被清除;
1.get查询的测试
1>在同一个session里执行两次get查询,以出现的sql语句数量来判断是否有缓存效果。
如果sql语句出现两条,则没有出现缓存。如果只有一条sql语句则,Hibernate的一级缓存启用了。
public void testQuery(){ Configuration configuration = new Configuration(); Configuration configure = configuration.configure(); SessionFactory sessionFactory = configure.buildSessionFactory(); Session session = sessionFactory.openSession(); Transaction transaction = session.beginTransaction(); HotelBean hotel1 = session.get(HotelBean.class, 3); HotelBean hotel2 = session.get(HotelBean.class, 3); transaction.commit(); session.close(); }
在控制台查看到显示的sql结果如下:
Hibernate: select hotelbean0_.hotel_id as hotel_id1_0_0_, hotelbean0_.hotel_name as hotel_na2_0_0_, hotelbean0_.hotel_address as hotel_ad3_0_0_ from hotel hotelbean0_ where hotelbean0_.hotel_id=? Process finished with exit code 0
两次查询,第一次执行了get方法查询了数据库,产生了一条sql语句,第二次执行get方法时,由于在一级缓存中找到了该对象,因此不会查询数据库,不再发出sql语句。
session级缓存:默认缓存。
增加、修改、查询的时候,都有缓存的数据。
2Hibernate的二级缓存(SessionFactory级缓存)
二级缓存是SessionFactory级别的缓存,该级缓存可以进行配置和更改,并且可以动态加载和卸载,hibernate还为查询结果提供了一个查询缓存,它依赖于二级缓存;
二级缓存是一个可插拔的缓存插件,它是由SessionFactory负责管理的;
由于SessionFactory对象的生命周期与应用程序的整个过程对应,通常一个应用程序对应一个SessionFactory,因此,二级缓存是进程范围或者集群范围的缓存;
与一级缓存一样,二级缓存也是根据对象的id来加载与缓存,当执行某个查询获得结果集为实体对象集时,hibernate就会把它们按照对象id加载到二级缓存中,在访问指定的id的对象时,首先从一级缓存中查找,找到就直接使用,找不到则转到二级缓存中查找(必须配置且启用二级缓存),如果二级缓存中找到,则直接使用,否则会查询数据库,并将查询结果根据对象的id放到缓存中;
1,常用的二级缓存插件
Hibernate的二级缓存功能是通过配置二级缓存插件来实现的,常用的二级缓存插件包括EHCache,OSCache,SwarmCache和JBossCache。其中EHCache缓存插件是理想的进程范围的缓存实现,此处以使用EHCache缓存插件为例,来介绍如何使用hibernate的二级缓存;
2,Hibernate中使用EHCache的配置
1>引入EHCache相关的jar包;
lib\optional\ehcache下的三个jar包;
2>创建EHCache的配置文件ehcache.xml
- <span style="font-size:18px;"><strong><ehcache>
- <diskStore path="java.io.tmpdir"/>
- <defaultCache
- maxElementsInMemory="10000"
- eternal="false"
- timeToIdleSeconds="120"
- timeToLiveSeconds="120"
- overflowToDisk="true"
- />
- <cache name="sampleCache1"
- maxElementsInMemory="10000"
- eternal="false"
- timeToIdleSeconds="300"
- timeToLiveSeconds="600"
- overflowToDisk="true"
- />
- <cache name="sampleCache2"
- maxElementsInMemory="1000"
- eternal="true"
- timeToIdleSeconds="0"
- timeToLiveSeconds="0"
- overflowToDisk="false"
- />
- </ehcache></strong></span>
<span style="font-size:18px;"><strong><ehcache>
<diskStore path="java.io.tmpdir"/>
<defaultCache
maxElementsInMemory="10000"
eternal="false"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
overflowToDisk="true"
/>
<cache name="sampleCache1"
maxElementsInMemory="10000"
eternal="false"
timeToIdleSeconds="300"
timeToLiveSeconds="600"
overflowToDisk="true"
/>
<cache name="sampleCache2"
maxElementsInMemory="1000"
eternal="true"
timeToIdleSeconds="0"
timeToLiveSeconds="0"
overflowToDisk="false"
/>
</ehcache></strong></span>
在上述配置中,diskStore元素设置缓存数据文件的存储目录;defaultCache元素设置缓存的默认数据过期策略;cache元素设置具体的命名缓存的数据过期策略。每个命名缓存代表一个缓存区域,命名缓存机制允许用户在每个类以及类的每个集合的粒度上设置数据过期策略; 在defaultCache元素中,maxElementsInMemory属性设置缓存对象的最大数目;eternal属性指定是否永不过期,true为不过期,false为过期;timeToldleSeconds属性设置对象处于空闲状态的最大秒数;timeToLiveSeconds属性设置对象处于缓存状态的最大秒数;overflowToDisk属性设置内存溢出时是否将溢出对象写入硬盘;
3>在Hibernate配置文件里面启用EHCache
在hibernate.cfg.xml配置文件中,启用EHCache的配置如下:
- <span style="font-size:18px;"><strong> <!-- 启用二级缓存 -->
- <property name="hibernate.cache.use_second_level_cache">true</property>
- <!-- 设置二级缓存插件EHCache的Provider类 -->
- <property name="hibernate.cache.region.factory_class">
- org.hibernate.cache.ehcache.EhCacheRegionFactory</property></strong></span>
<span style="font-size:18px;"><strong> <!-- 启用二级缓存 -->
<property name="hibernate.cache.use_second_level_cache">true</property>
<!-- 设置二级缓存插件EHCache的Provider类 -->
<property name="hibernate.cache.region.factory_class">
org.hibernate.cache.ehcache.EhCacheRegionFactory</property></strong></span>
4>配置哪些实体类的对象需要二级缓存,有两种方式:
1>>在实体类的映射文件里面配置
在需要进行缓存的持久化对象的映射文件中配置相应的二级缓存策略,如User,hbm.xml:
- <span style="font-size:18px;"><strong><?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.hibtest1.entity.User" table="user" catalog="bookshop">
- <cache usage="read-write"/>
- <id name="id" type="java.lang.Integer">
- <column name="Id" />
- <generator class="native" />
- </id>
- <property name="loginName" type="java.lang.String">
- <column name="LoginName" length="50" />
- </property>
- </class>
- </hibernate-mapping></strong></span>
<span style="font-size:18px;"><strong><?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.hibtest1.entity.User" table="user" catalog="bookshop">
<cache usage="read-write"/>
<id name="id" type="java.lang.Integer">
<column name="Id" />
<generator class="native" />
</id>
<property name="loginName" type="java.lang.String">
<column name="LoginName" length="50" />
</property>
</class>
</hibernate-mapping></strong></span>
映射文件中使用<cache>元素设置持久化类User的二级缓存并发访问策略,usage属性取值为read-only时表示只读型并发访问策略;read-write表示读写型并发访问策略;nonstrict-read-write表示非严格读写型并发访问策略;EHCache插件不支持transactional(事务型并发访问策略)。 注意:<cache>元素只能放在<class>元素的内部,而且必须处在<id>元素的前面,<cache>元素放在哪些<class>元素下面,就说明会对哪些类进行缓存;
2>>在hibernate配置文件中统一配置,强烈推荐使用这种方式:
在hibernate.cfg.xml文件中使用<class-cache>元素来配置哪些实体类的对象需要二级缓存:
- <span style="font-size:18px;"><strong><span style="font-size:18px;"><strong><span style="font-size:18px;"><strong><class-cache usage="read-only" class="com.anlw.entity.Student"/></strong></span></strong></span></strong></span>
<span style="font-size:18px;"><strong><span style="font-size:18px;"><strong><span style="font-size:18px;"><strong><class-cache usage="read-only" class="com.anlw.entity.Student"/></strong></span></strong></span></strong></span>
在<class-cache>元素中,usage属性指定缓存策略,需要注意<class-cache>元素必须放在所有<mapping>元素的后面;
3,Hibernate中使用EHCache的测试:
- <span style="font-size:18px;"><strong> public void Query(){
- Session sess1 = HibernateSessionFactory.getSession();
- Student s1 = (Student)sess1.get(Student.class, 1);
- System.out.println(s1.getName());
- HibernateSessionFactory.closeSession();
- Session sess2 = HibernateSessionFactory.getSession();
- Student s2 = (Student)sess2.get(Student.class, 1);
- System.out.println(s2.getName());
- HibernateSessionFactory.closeSession();
- }</strong></span>
<span style="font-size:18px;"><strong> public void Query(){
Session sess1 = HibernateSessionFactory.getSession();
Student s1 = (Student)sess1.get(Student.class, 1);
System.out.println(s1.getName());
HibernateSessionFactory.closeSession();
Session sess2 = HibernateSessionFactory.getSession();
Student s2 = (Student)sess2.get(Student.class, 1);
System.out.println(s2.getName());
HibernateSessionFactory.closeSession();
}</strong></span>
上面的代码,第一次执行get方法查询出结果后,关闭了session,一级缓存被清除了,由于配置并启用了二级缓存,查询出的结果会放入二级缓存,第二次执行get方法时,首先从一级缓存中查找,没有找到,然后转到二级缓存查找,二级缓存中找到结果,就不需要从数据库查询了。
注意:在hibernate配置二级缓存时属性的顺序如下,顺序错了会空指针异常:
- <span style="font-size:18px;"><strong> <!-- 启用二级缓存 -->
- <property name="hibernate.cache.use_second_level_cache">true</property>
- <!-- 设置二级缓存插件EHCache的Provider类 -->
- <property name="hibernate.cache.region.factory_class">
- org.hibernate.cache.ehcache.EhCacheRegionFactory</property>
- <mapping class="com.anlw.entity.Student"/>
- <class-cache usage="read-only" class="com.anlw.entity.Student"/></strong></span>
<span style="font-size:18px;"><strong> <!-- 启用二级缓存 -->
<property name="hibernate.cache.use_second_level_cache">true</property>
<!-- 设置二级缓存插件EHCache的Provider类 -->
<property name="hibernate.cache.region.factory_class">
org.hibernate.cache.ehcache.EhCacheRegionFactory</property>
<mapping class="com.anlw.entity.Student"/>
<class-cache usage="read-only" class="com.anlw.entity.Student"/></strong></span>
先缓存配置,再mapping,最后calss-cache;
五,Hibernate中的查询缓存
对于经常使用的查询语句,如果启用了查询缓存 ,当第一次执行查询语句时,hibernate会将查询结果存储在二级缓存中,以后再次执行该查询语句时,从缓存中获取查询结果,从而提高查询性能;
hibernate的查询缓存主要是针对普通属性结果集的缓存,而对于实体对象的结果集只缓存id;
查询缓存的生命周期,若当前关联的表发生修改,那么查询缓存的生命周期结束;
1,查询缓存的配置
查询缓存基于二级缓存,使用查询缓存前,必须首先配置好二级缓存;
在配置了二级缓存的基础上,在hibernate的配置文件hibernate.cfg.xml中添加如下配置,可以启用查询缓存:
<property name="hibernate.cache.use_query_cache">false</property>
此外在程序中还必须手动启用查询缓存:
query.setCacheable(true);
2,测试查询缓存:
1>开启查询缓存,关闭二级缓存,开启一个session,分别调用query.list查询属性,测试前,先在先在hibernate.cfg.xml文件中开启查询缓存,关闭二级缓存,如下所示:
<property name="hibernate.cache.use_query_cache">true</property> < property name="hibernate.cache.use_second_level_cache">false</property>
- <span style="font-size:18px;"><strong> public static void query(){
- Session sess = HibernateSessionFactory.getSession();
- Transaction tx = sess.beginTransaction();
- Query query = sess.createQuery("select s.name from Student s");
- query.setCacheable(true);
- List names = query.list();
- for(Iterator iter = names.iterator();iter.hasNext();){
- String name = (String)iter.next();
- System.out.println(name);
- }
- System.out.println("----------");
- query = sess.createQuery("select s.name from Student s");
- query.setCacheable(true);
- names = query.list();
- for(Iterator iter = names.iterator();iter.hasNext();){
- String name = (String)iter.next();
- System.out.println(name);
- }
- tx.commit();
- HibernateSessionFactory.closeSession();
- }
- </strong></span>
<span style="font-size:18px;"><strong> public static void query(){
Session sess = HibernateSessionFactory.getSession();
Transaction tx = sess.beginTransaction();
Query query = sess.createQuery("select s.name from Student s");
query.setCacheable(true);
List names = query.list();
for(Iterator iter = names.iterator();iter.hasNext();){
String name = (String)iter.next();
System.out.println(name);
}
System.out.println("----------");
query = sess.createQuery("select s.name from Student s");
query.setCacheable(true);
names = query.list();
for(Iterator iter = names.iterator();iter.hasNext();){
String name = (String)iter.next();
System.out.println(name);
}
tx.commit();
HibernateSessionFactory.closeSession();
}
</strong></span>
第二次没有去查数据库,因为启用了查询缓存;
2>开启查询缓存,关闭二级缓存,开启两个session,分别调用query.list查询属性,测试前,先在先在hibernate.cfg.xml文件中开启查询缓存,关闭二级缓存,如下所示:
<property name="hibernate.cache.use_query_cache">true</property> < property name="hibernate.cache.use_second_level_cache">false</property>
- <span style="font-size:18px;"><strong> public static void query(){
- Session sess1 = HibernateSessionFactory.getSession();
- Transaction tx1 = sess1.beginTransaction();
- Query query = sess1.createQuery("select s.name from Student s");
- query.setCacheable(true);
- List names = query.list();
- for(Iterator iter = names.iterator();iter.hasNext();){
- String name = (String)iter.next();
- System.out.println(name);
- }
- tx1.commit();
- HibernateSessionFactory.closeSession();
- System.out.println("----------");
- Session sess2 = HibernateSessionFactory.getSession();
- Transaction tx2 = sess2.beginTransaction();
- query = sess2.createQuery("select s.name from Student s");
- query.setCacheable(true);
- names = query.list();
- for(Iterator iter = names.iterator();iter.hasNext();){
- String name = (String)iter.next();
- System.out.println(name);
- }
- tx2.commit();
- HibernateSessionFactory.closeSession();
- }</strong></span>
<span style="font-size:18px;"><strong> public static void query(){
Session sess1 = HibernateSessionFactory.getSession();
Transaction tx1 = sess1.beginTransaction();
Query query = sess1.createQuery("select s.name from Student s");
query.setCacheable(true);
List names = query.list();
for(Iterator iter = names.iterator();iter.hasNext();){
String name = (String)iter.next();
System.out.println(name);
}
tx1.commit();
HibernateSessionFactory.closeSession();
System.out.println("----------");
Session sess2 = HibernateSessionFactory.getSession();
Transaction tx2 = sess2.beginTransaction();
query = sess2.createQuery("select s.name from Student s");
query.setCacheable(true);
names = query.list();
for(Iterator iter = names.iterator();iter.hasNext();){
String name = (String)iter.next();
System.out.println(name);
}
tx2.commit();
HibernateSessionFactory.closeSession();
}</strong></span>
第二次没有去查数据库,因为查询缓存生命周期与session生命周期无关;
3>开启查询缓存,关闭二级缓存,开启两个session,分别调用query.list查询实体对象,测试前,先在先在hibernate.cfg.xml文件中开启查询缓存,关闭二级缓存,如下所示:
<property name="hibernate.cache.use_query_cache">true</property> < property name="hibernate.cache.use_second_level_cache">false</property>
- <span style="font-size:18px;"><strong> public static void query(){
- Session sess1 = HibernateSessionFactory.getSession();
- Transaction tx1 = sess1.beginTransaction();
- Query query = sess1.createQuery("from Student");
- query.setCacheable(true);
- List student = query.list();
- for(Iterator iter = student.iterator();iter.hasNext();){
- Student s = (Student)iter.next();
- System.out.println(s.getName()+"--"+s.getAge());
- }
- tx1.commit();
- HibernateSessionFactory.closeSession();
- System.out.println("----------");
- Session sess2 = HibernateSessionFactory.getSession();
- Transaction tx2 = sess2.beginTransaction();
- query = sess2.createQuery("from Student");
- query.setCacheable(true);
- student = query.list();
- for(Iterator iter = student.iterator();iter.hasNext();){
- Student s = (Student)iter.next();
- System.out.println(s.getName()+"--"+s.getAge());
- }
- tx2.commit();
- HibernateSessionFactory.closeSession();
- }</strong></span>
<span style="font-size:18px;"><strong> public static void query(){
Session sess1 = HibernateSessionFactory.getSession();
Transaction tx1 = sess1.beginTransaction();
Query query = sess1.createQuery("from Student");
query.setCacheable(true);
List student = query.list();
for(Iterator iter = student.iterator();iter.hasNext();){
Student s = (Student)iter.next();
System.out.println(s.getName()+"--"+s.getAge());
}
tx1.commit();
HibernateSessionFactory.closeSession();
System.out.println("----------");
Session sess2 = HibernateSessionFactory.getSession();
Transaction tx2 = sess2.beginTransaction();
query = sess2.createQuery("from Student");
query.setCacheable(true);
student = query.list();
for(Iterator iter = student.iterator();iter.hasNext();){
Student s = (Student)iter.next();
System.out.println(s.getName()+"--"+s.getAge());
}
tx2.commit();
HibernateSessionFactory.closeSession();
}</strong></span>
查询结果如下:
- <span style="font-size:18px;"><strong>Hibernate:
- select
- student0_.id as id1_0_,
- student0_.age as age2_0_,
- student0_.name as name3_0_
- from
- student student0_
- anliwenaaa--1
- test--2
- ----------
- Hibernate:
- select
- student0_.id as id1_0_0_,
- student0_.age as age2_0_0_,
- student0_.name as name3_0_0_
- from
- student student0_
- where
- student0_.id=?
- Hibernate:
- select
- student0_.id as id1_0_0_,
- student0_.age as age2_0_0_,
- student0_.name as name3_0_0_
- from
- student student0_
- where
- student0_.id=?
- anliwenaaa--1
- test--2</strong></span>
<span style="font-size:18px;"><strong>Hibernate:
select
student0_.id as id1_0_,
student0_.age as age2_0_,
student0_.name as name3_0_
from
student student0_
anliwenaaa--1
test--2
----------
Hibernate:
select
student0_.id as id1_0_0_,
student0_.age as age2_0_0_,
student0_.name as name3_0_0_
from
student student0_
where
student0_.id=?
Hibernate:
select
student0_.id as id1_0_0_,
student0_.age as age2_0_0_,
student0_.name as name3_0_0_
from
student student0_
where
student0_.id=?
anliwenaaa--1
test--2</strong></span>
第二次查询数据库时,会发出n条sql语句,因为开启了查询缓存,关闭了二级缓存,那么查询缓存会缓存实体对象的id,所以hibernate会根据实体对象的id去查询相应的实体,如果缓存中不存在相应的实体,那么将发出根据实体id查询的sql语句,否则不会发出sql,使用缓存中的数据;
4>开启查询缓存,开启二级缓存,开启两个session,分别调用query.list查询实体对象,测试前,先在先在hibernate.cfg.xml文件中开启查询缓存,开启二级缓存,如下所示:
<property name="hibernate.cache.use_query_cache">true</property> < property name="hibernate.cache.use_second_level_cache">true</property>
代码和3>一样,但是结果不同,第二次不会发出sql,因为开启了二级缓存和缓存查询,查询缓存缓存了实体对象的id,hibernate会根据实体对象的id到二级缓存中取得相应的数据;