我们继续来说hibernate的缓存,先来探讨一下N+1的问题。
【问题情况】
1)一对多(one-to-many),在1的这方,通过1条sql查找得到了1个对象,由于关联的存在,那么又需要将这个对
象关联的集合取出,所以合集数量是n还要发出n条sql,于是本来的1条sql查询变成了1 +n条 .
2)多对一<many-to-one> ,在多的这方,通过1条sql查询得到了n个对象,由于关联的存在,也会将这n个对象对
应的1方的对象取出,于是本来的1条sql查询变成了1+n条 .
3)iterator查询时,通过iterator()方法来获得我们对象的时候,hibernate首先会发出1条sql去查询出所有对
象的id值,当我们如果需要查询到某个对象的具体信息的时候,hibernate此时会根据查询出来的id值再发sql
语句去从数据库中查询对象的信息,这就是典型的N+1的问题。
【解决办法】
1)lazy=true,hibernate3开始已经默认是lazy=true了;lazy=true时不会立刻查询关联对象,只有当需要关联对
象(访问其属性,非id字段)时才会发生查询动作。
2)使用二级缓存,二级缓存的应用将不怕1+N 问题,因为即使第一次查询很慢(未命中),以后查询直接缓存命
中也是很快的。刚好又利用了1+N .
3)当然你也可以设定fetch="join",一次关联表全查出来,但失去了延迟加载的特性。
【二级缓存】
我们上篇文章说了一级缓存是session级别的,当session关闭了,缓存也就不存在了。所以为了提高我们的
查询效率,我们需要手动配置二级缓存(sessionFactory级别)的缓存。
1.下载.
hibernate并没有提供相应的二级缓存的组件,所以需要加入额外的二级缓存包,常用的二级缓存包
EHcache。下载之后将里面的几个jar包导入即可。
2.配置属性
在hibernate.cfg.xml配置文件中配置我们二级缓存的一些属性:
<span style="font-size:18px;"><!-- 开启二级缓存 -->
<propertyname="hibernate.cache.use_second_level_cache">true</property>
<propertyname="hibernate.cache.region.factory_class">org.hibernate.cache.ehcache.EhCacheRegionFactory</property>
<!-- 二级缓存配置文件的位置 -->
<propertyname="hibernate.cache.provider_configuration_file_resource_path">ehcache.xml</property>
</span>
3.配置缓存信息
配置hibernate的二级缓存是通过使用ehcache的缓存包,所以我们需要创建一个ehcache.xml的配置文件,
来配置我们的缓存信息,将其放到项目根目录下。
<span style="font-size:18px;"><ehcache>
<!--指定二级缓存存放在磁盘上的位置-->
<diskStorepath="user.dir"/>
<!--我们可以给每个实体类指定一个对应的缓存,如果没有匹配到该类,则使用这个默认的缓存配置-->
<defaultCache
maxElementsInMemory="10000" //在内存中存放的最大对象数
eternal="false" //是否永久保存缓存,设置成false
timeToIdleSeconds="120"
timeToLiveSeconds="120"
overflowToDisk="true" //如果对象数量超过内存中最大的数,是否将其保存到磁盘中,设置成true
/>
<!--
1、timeToLiveSeconds的定义是:以创建时间为基准开始计算的超时时长;
2、timeToIdleSeconds的定义是:在创建时间和最近访问时间中取出离现在最近的时间作为基准计算的超时时长;
3、如果仅设置了timeToLiveSeconds,则该对象的超时时间=创建时间+timeToLiveSeconds,假设为A;
4、如果没设置timeToLiveSeconds,则该对象的超时时间=max(创建时间,最近访问时间)+timeToIdleSeconds,假设为B;
5、如果两者都设置了,则取出A、B最少的值,即min(A,B),表示只要有一个超时成立即算超时。
-->
<!--可以给每个实体类指定一个配置文件,通过name属性指定,要使用类的全名-->
<cache name="com.wyj.bean.Student"
maxElementsInMemory="10000"
eternal="false"
timeToIdleSeconds="300"
timeToLiveSeconds="600"
overflowToDisk="true"
/>
<cachename="sampleCache2"
maxElementsInMemory="1000"
eternal="true"
timeToIdleSeconds="0"
timeToLiveSeconds="0"
overflowToDisk="false"
/> -->
</ehcache></span>
4.开启二级缓存
需要在Student.hbm.xml中加上一下配置:
<hibernate-mappingpackage="com.wyj.bean">
<class name="Student"table="t_student">
<!-- 二级缓存一般设置为只读的-->
<cacheusage="read-only"/>
<id name="id"type="int" column="id">
<generator class="native"/>
</id>
<propertyname="name" column="name" type="string"/>
<propertyname="sex" column="sex" type="string"/>
<many-to-one name="room"column="rid" fetch="join"/>
</class>
</hibernate-mapping>
二级缓存的使用策略一般有这几种:read-only、nonstrict-read-write、read-write、transactional。注
意:我们通常使用二级缓存都是将其配置成read-only ,即我们应当在那些不需要进行修改的实体类上使用二级
缓存,否则如果对缓存进行读写的话,性能会变差,这样设置缓存就失去了意义。
5.测试运行(解决N+1问题)
<span style="font-size:18px;">@Test
public void testCache13()
{
Session session =null;
try
{
session =HibernateUtil.openSession();
/**
*将查询出来的Student对象缓存到二级缓存中去
*/
List<Student> stus =(List<Student>) session.createQuery(
"select stu fromStudent stu").list();
}
catch (Exception e)
{
e.printStackTrace();
}
finally
{
HibernateUtil.close(session);
}
try
{
/**
*由于学生的对象已经缓存在二级缓存中了,此时再使用iterate来获取对象的时候,首先会通过一条
*取id的语句,然后在获取对象时去二级缓存中,如果发现就不会再发SQL,这样也就解决了N+1问题
* 而且内存占用也不多
*/
session =HibernateUtil.openSession();
Iterator<Student>iterator = session.createQuery("from Student")
.iterate();
for (;iterator.hasNext();)
{
Student stu = (Student)iterator.next();
System.out.println(stu.getName());
}
}
catch (Exception e)
{
e.printStackTrace();
}
}
</span>
注:
1.二级缓存缓存的仅仅是对象,如果查询出来的是对象的一些属性,则不会被加到缓存中去
2.二级缓存不会缓存我们的hql查询语句。需要配置查询缓存才会缓存hql语句。
【查询缓存】
我们如果要配置查询缓存,只需要在hibernate.cfg.xml中加入一条配置即可:
<span style="font-size:18px;"><!-- 开启查询缓存-->
<propertyname="hibernate.cache.use_query_cache">true</property>
然后我们如果在查询hql语句时要使用查询缓存,就需要在查询语句后面设置这样一个方法:
List<Student>ls = session.createQuery("from Student where name like ?")
.setCacheable(true)//开启查询缓存,查询缓存也是SessionFactory级别的缓存
.setParameter(0,"%王%")
.setFirstResult(0).setMaxResults(50).list();</span>
注:
1.只有当 HQL查询语句完全相同时,连参数设置都要相同,此时查询缓存才有效
2.和一级/二级缓存不同,查询缓存的生命周期 ,是不确定的,当前关联的表发生改变时,查询缓存的生命周期
结束。
【总结】
选择合适的缓存策略,可以提高系统的性能。我们都知道一级缓存对性能提高没有太大的意义,因为生命
周期太短了。查询缓存意义不是很大,查询缓存说明白了就是存放由list方法或者iterate方法查询的数据,我们
在查询时很少出现完全相同的条件查询,这就是说命中率低。也就是二级缓存才是需要我们重点学习的,二级缓
存由SessionFactory对象管理,是应用级别的缓存。它可以缓存整个应用的持久化对象。