由上一篇文章hibernate缓存机制(二)-一级缓存解决n+1问题 hibernate一级缓存能解决n+1问题,但是它是session级别的,session一旦关闭,缓存也就关闭,为了解决这个问题,hibernate提出了二级缓存(sessionFactory级别)。
一、什么是hibernate的二级缓存
Hibernate的二级缓存就是要为Hibernate配置一种全局缓存,让多个线程和多个事务都可以共享这个缓存。我们希望的是一个人使用过,其他人也可以使用,
session没有这种效果。
二级缓存是独立于Hibernate的软件部件,属于第三方的产品,多个厂商和组织都提供有缓存产品,例如,EHCache和OSCache等等。
二、怎么在hibernate中使用二级缓存
1、首先就要在hibernate.cfg.xml配置文件中配置使用哪个厂家的缓存产品。
2、接着需要配置该缓存产品自己的配置文件。
3、最后要配置Hibernate中的哪些实体对象要纳入到二级缓存的管理中。
明白了二级缓存原理和有了这个思路后,很容易配置起Hibernate的二级缓存。
扩展知识:一个SessionFactory可以关联一个二级缓存,也即一个二级缓存只能负责缓存一个数据库中的数据,当使用Hibernate 的二级缓存后,
注意不要有其他的应用或SessionFactory来更改当前数据库中的数据,这样缓存的数据就会与数据库中的实际数据不一致。
三、具体事例
使用hibernate二级缓存,我们首先需要对其进行配置,配置步骤如下:
1.hibernate并没有提供相应的二级缓存的组件,所以需要加入额外的二级缓存包,常用的二级缓存包是EHcache。这个我们在下载好的hibernate的lib->optional->ehcache下可以找到(我这里使用的hibernate4.1.7版本),然后将里面的几个jar包导入即可。
2.在hibernate.cfg.xml配置文件中配置我们二级缓存的一些属性:
<!-- 开启二级缓存 --> <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> |
我这里使用的是hibernate4.1.7版本,如果是使用hibernate3的版本的话,那么二级缓存的提供类则要配置成这个:
<!--这个类在4.0版本以后已经不建议被使用了--> |
3.配置hibernate的二级缓存是通过使用 ehcache的缓存包,所以我们需要创建一个 ehcache.xml 的配置文件,来配置我们的缓存信息,将其放到项目根目录下
<ehcache>
<!-- Sets the path to the directory where cache .data files are created.
If the path is a Java System Property it is replaced by its value in the running VM.
The following properties are translated: user.home - User's home directory user.dir - User's current working directory java.io.tmpdir - Default temp file path -->
<!--指定二级缓存存放在磁盘上的位置--> <diskStore path="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.xiaoluo.bean.Student" maxElementsInMemory="10000" eternal="false" timeToIdleSeconds="300" timeToLiveSeconds="600" overflowToDisk="true" />
<cache name="sampleCache2" maxElementsInMemory="1000" eternal="true" timeToIdleSeconds="0" timeToLiveSeconds="0" overflowToDisk="false" /> -->
</ehcache> |
4.开启我们的二级缓存
①如果使用xml配置,我们需要在 Student.hbm.xml 中加上一下配置:
<hibernate-mappingpackage="com.xiaoluo.bean">
<class name="Student" table="t_student">
<!-- 二级缓存一般设置为只读的 -->
<cacheusage="read-only"/>
<id name="id"type="int" column="id">
<generator class="native"/>
</id>
<property name="name"column="name" type="string"></property>
<property name="sex"column="sex" type="string"></property>
<many-to-one name="room"column="rid" fetch="join"></many-to-one>
</class>
</hibernate-mapping>
二级缓存的使用策略一般有这几种:read-only、nonstrict-read-write、read-write、transactional。注意:我们通常使用二级缓存都是将其配置成 read-only ,即我们应当在那些不需要进行修改的实体类上使用二级缓存,否则如果对缓存进行读写的话,性能会变差,这样设置缓存就失去了意义。
②如果使用annotation配置,我们需要在Student这个类上加上这样一个注解:
@Entity
@Table(name="t_student")
@Cache(usage=CacheConcurrencyStrategy.READ_ONLY) // 表示开启二级缓存,并使用read-only策略
public class Student
{
private int id;
private String name;
private String sex;
private Classroom room;
.......
}
这样我们的二级缓存配置就算完成了,接下来我们来通过测试用例测试下我们的二级缓存是否起作用
①二级缓存是sessionFactory级别的缓存
TestCase1:
public class TestSecondCache
{
@Test
public void testCache1()
{
Session session = null;
try
{
session =HibernateUtil.openSession();
Student stu = (Student)session.load(Student.class, 1);
System.out.println(stu.getName() +"-----------");
}
catch (Exception e)
{
e.printStackTrace();
}
finally
{
HibernateUtil.close(session);
}
try
{
/**
* 即使当session关闭以后,因为配置了二级缓存,而二级缓存是sessionFactory级别的,所以会从缓存中取出该数据
* 只会发出一条sql语句
*/
session =HibernateUtil.openSession();
Student stu = (Student)session.load(Student.class, 1);
System.out.println(stu.getName() +"-----------");
/**
* 因为设置了二级缓存为read-only,所以不能对其进行修改
*/
session.beginTransaction();
stu.setName("aaa");
session.getTransaction().commit();
}
catch (Exception e)
{
e.printStackTrace();
session.getTransaction().rollback();
}
finally
{
HibernateUtil.close(session);
}
}
Hibernate: selectstudent0_.id as id2_2_, student0_.name as name2_2_, student0_.sex as sex2_2_,student0_.rid as rid2_2_, classroom1_.id as id1_0_, classroom1_.name asname1_0_, classroom1_.sid as sid1_0_, special2_.id as id0_1_, special2_.name asname0_1_, special2_.type as type0_1_ from t_student student0_ left outer joint_classroom classroom1_ on student0_.rid=classroom1_.id left outer joint_special special2_ on classroom1_.sid=special2_.id where student0_.id=?
aaa-----------
aaa-----------
因为二级缓存是sessionFactory级别的缓存,我们看到,在配置了二级缓存以后,当我们session关闭以后,我们再去查询对象的时候,此时hibernate首先会去二级缓存中查询是否有该对象,有就不会再发sql了。
②二级缓存缓存的仅仅是对象,如果查询出来的是对象的一些属性,则不会被加到缓存中去
TestCase2:
@Test
public void testCache2()
{
Session session = null;
try
{
session =HibernateUtil.openSession();
/**
* 注意:二级缓存中缓存的仅仅是对象,而下面这里只保存了姓名和性别两个字段,所以不会被加载到二级缓存里面
*/
List<Object[]> ls =(List<Object[]>) session
.createQuery("selectstu.name, stu.sex from Student stu")
.setFirstResult(0).setMaxResults(30).list();
}
catch (Exception e)
{
e.printStackTrace();
}
finally
{
HibernateUtil.close(session);
}
try
{
/**
* 由于二级缓存缓存的是对象,所以此时会发出两条sql
*/
session =HibernateUtil.openSession();
Student stu = (Student)session.load(Student.class, 1);
System.out.println(stu);
}
catch (Exception e)
{
e.printStackTrace();
}
}
Hibernate: select student0_.name as col_0_0_, student0_.sex as col_1_0_ fromt_student student0_ limit ?
Hibernate: select student0_.id as id2_2_, student0_.name as name2_2_, student0_.sexas sex2_2_, student0_.rid as rid2_2_, classroom1_.id as id1_0_,classroom1_.name as name1_0_, classroom1_.sid as sid1_0_, special2_.id asid0_1_, special2_.name as name0_1_, special2_.type as type0_1_ from t_studentstudent0_ left outer join t_classroom classroom1_ onstudent0_.rid=classroom1_.id left outer join t_special special2_ onclassroom1_.sid=special2_.id where student0_.id=?
我们看到这个测试用例,如果我们只是取出对象的一些属性的话,则不会将其保存到二级缓存中去,因为二级缓存缓存的仅仅是对象。
③通过二级缓存来解决 N+1 的问题
TestCase3:
@Test
public void testCache3()
{
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();
}
}
当我们如果需要查询出两次对象的时候,可以使用二级缓存来解决N+1的问题。
④二级缓存会缓存 hql 语句吗?
TestCase4:
@Test
public void testCache4()
{
Session session = null;
try
{
session =HibernateUtil.openSession();
List<Student> ls =session.createQuery("from Student")
.setFirstResult(0).setMaxResults(50).list();
}
catch (Exception e)
{
e.printStackTrace();
}
finally
{
HibernateUtil.close(session);
}
try
{
/**
* 使用List会发出两条一模一样的sql,此时如果希望不发sql就需要使用查询缓存
*/
session =HibernateUtil.openSession();
List<Student> ls =session.createQuery("from Student")
.setFirstResult(0).setMaxResults(50).list();
Iterator<Student> stu = ls.iterator();
for(;stu.hasNext();)
{
Student student = stu.next();
System.out.println(student.getName());
}
}
catch (Exception e)
{
e.printStackTrace();
}
finally
{
HibernateUtil.close(session);
}
}
Hibernate: selectstudent0_.id as id2_, student0_.name as name2_, student0_.sex as sex2_,student0_.rid as rid2_ from t_student student0_ limit ?
Hibernate: selectstudent0_.id as id2_, student0_.name as name2_, student0_.sex as sex2_,student0_.rid as rid2_ from t_student student0_ limit ?
我们看到,当我们如果通过 list() 去查询两次对象时,二级缓存虽然会缓存查询出来的对象,但是我们看到发出了两条相同的查询语句,这是因为二级缓存不会缓存我们的hql查询语句,要想解决这个问题,我们就要配置我们的查询缓存了。
查询缓存请见下一章hibernate缓存机制(四)-查询缓存