最先接触到缓存机制是在学习计算机操作系统原理时,计算机的缓存机制是为了解决CPU和内存的速度差异。CPU存取数据的速度非常快,相对CPU来说内存的速度就慢很多。CPU需要从内存中读取一些数据但是由于内存的速度慢就无法及时提供,所以内存中使用最频繁的数据、指令会被复制到CPU的缓存中CPU就不需要总是和内存打交道这样可以提高效率。CPU的缓存也分为一级和二级,在实际访问中会先找一级缓存,一级没有就会找二级缓存如果二级缓存中也没有就只能到内存中查找。详细原理小编还需要进一步了解。
对比操作系统的缓存机制我们会发现hibernate的缓存机制大体上和操作系统是一致的。
Why:
为什么hibernate会出现缓存机制?因为hibernate是属于持久层的所以会频繁的和数据库打交道进行数据的读取,正如CPU需要从内存中读取数据类似。为了减少程序运行时访问物理数据源的次数提高运行程序的性能所以hibernate出现了缓存机制,这样hibernate在进行数据读取时会先从一级缓存中查找如果没有则查找二级缓存如果还没有就会访问数据库。和CPU缓存机制类型hibernate缓存中的数据也会根据特定机制进行更新。
What:
hibernate的缓存也分为一级和二级
一级缓存也称Session级缓存——
1、 使用session控制,生命周期同session;所以当前session关闭后,缓存就没了
2、 只能在当前线程中使用,不同的session间不能共享缓存数据
二级缓存也称sessionFactory级缓存或进程级缓存——
1、 使用sessionFactory控制,生命周期同sessionFactory
2、 在当前进程中使用
共同特点:只缓存实体对象,如果使用HQL查询普通属性不会缓存。具体的意思可以从实例中体会。
How:
首先是一级缓存的使用:可分为查询实体和查询个别属性
查询实体:
load或get查询一个实体:
/**
* 在同一个session中发出两次load查询
*/
public void testLoad() {
Sessionsession = null;
try {
session= HibernateUtils.getSession();
session.beginTransaction();
Studentstudent = (Student)session.load(Student.class, 1);
System.out.println("student.name="+ student.getName());
//不会发出查询语句,load使用缓存
student= (Student)session.load(Student.class, 1);
System.out.println("student.name="+ student.getName());
session.getTransaction().commit();
}catch(Exception e) {
e.printStackTrace();
session.getTransaction().rollback();
}finally {
HibernateUtils.closeSession(session);
}
}
执行完毕发现只发出一条查询语句,说明在第二次进行查询时session是从自己的缓存中获取的数据而不是从库中获取的。测试get方法和load方法是一样的。load和get方法的区别在于load默认是支持延迟加载的。
list或iterate查询实体集:
/**
* 在同一个session中发出两次iterate查询,查询实体对象
*/
public void testIterate () {
Sessionsession = null;
try {
session= HibernateUtils.getSession();
session.beginTransaction();
Iteratoriter = session.createQuery("from Student swhere s.id<5").iterate();
while (iter.hasNext()) {
Studentstudent = (Student)iter.next();
System.out.println(student.getName());
}
System.out.println("--------------------------------------");
//它会发出查询id的语句,但不会发出根据id查询学生的语句,因为iterate使用缓存
iter= session.createQuery("from Student swhere s.id<5").iterate();
while (iter.hasNext()) {
Studentstudent = (Student)iter.next();
System.out.println(student.getName());
}
session.getTransaction().commit();
}catch(Exception e) {
e.printStackTrace();
session.getTransaction().rollback();
}finally {
HibernateUtils.closeSession(session);
}
} </span>
执行结果如下:
第一次查询时iterate会发出1条查询所有id的语句,然后在打印student的name时发出n条根据id查询详细信息的语句一共N+1条。而第二次查询时只发出一条查询id的语句,其他的数据是从session的缓存中获取的。
使用list在同意session中查询实体集,发出的查询语句如下:会发出两条查询语句
出现如上的结果是不是说明使用iterate查询的结果才会放到session的缓存中而list的结果不会放到缓存中呢?看下面一个实例
/**
* 在同意session中先使用list,然后使用iterate进行查询
*/
public void testListIterate () {
Sessionsession = null;
try {
session= HibernateUtils.getSession();
session.beginTransaction();
Liststudents = session.createQuery("select s fromStudent s where s.id<5")
.list();
for (int i=0;i<students.size(); i++) {
Studentstudnet = (Student)students.get(i);
System.out.println(studnet.getName());
}
System.out.println("----------------------------------");
Iteratoriter = session.createQuery("from Student swhere s.id<5").iterate();
while (iter.hasNext()) {
Studentstudent = (Student)iter.next();
System.out.println(student.getName());
}
session.getTransaction().commit();
}catch(Exception e) {
e.printStackTrace();
session.getTransaction().rollback();
}finally {
HibernateUtils.closeSession(session);
}
} </span>
打印出的查询语句如下:
上图中我们看到在使用iterate进程查询时没有发出N+1条语句,只发出了查询所有id的语句,结合两个例子说明list查出的结果会放到session的缓存中但是list本身的查询不会直接使用缓存中的数据,还是在此查询数据库。
查询普通属性:
使用iterate查询某个普通属性,代码实例如下——
/**
* 在同一个session中发出两次iterate查询,查询普通属性
*/
public void testCache4() {
Sessionsession = null;
try {
session= HibernateUtils.getSession();
session.beginTransaction();
Iteratoriter = session.createQuery("select s.namefrom Student s where s.id<5").iterate();
while (iter.hasNext()) {
Stringname = (String)iter.next();
System.out.println(name);
}
System.out.println("--------------------------------------");
//iterate查询普通属性,一级缓存不会缓存,所以发出查询语句
//一级缓存是缓存实体对象的
iter= session.createQuery("select s.namefrom Student s where s.id<5").iterate();
while (iter.hasNext()) {
Stringname = (String)iter.next();
System.out.println(name);
}
session.getTransaction().commit();
}catch(Exception e) {
e.printStackTrace();
session.getTransaction().rollback();
}finally {
HibernateUtils.closeSession(session);
}
}
以上代码执行观察结果发现会发出两条查询语句,这说明只查询普通属性时session是不会进行缓存的。
一级缓存的生命周期:
/**
* 在两个session中发load查询
*/
public void testCache5() {
Sessionsession = null;
try {
session= HibernateUtils.getSession();
session.beginTransaction();
Studentstudent = (Student)session.load(Student.class, 1);
System.out.println("student.name="+ student.getName());
session.getTransaction().commit();
}catch(Exception e) {
e.printStackTrace();
session.getTransaction().rollback();
}finally {
HibernateUtils.closeSession(session);
}
try {
session= HibernateUtils.getSession();
session.beginTransaction();
Studentstudent = (Student)session.load(Student.class, 1);
//会发出查询语句,session间不能共享一级缓存数据
//因为他会伴随着session的消亡而消亡
System.out.println("student.name="+ student.getName());
session.getTransaction().commit();
}catch(Exception e) {
e.printStackTrace();
session.getTransaction().rollback();
}finally {
HibernateUtils.closeSession(session);
}
}
执行打印出的查询语句如下:
发出两条查询语句和在第一个测试中使用同一个session只发一条查询语句是有差别的,这说明了一级缓存的生命周期是和session一致的,session关闭缓存就会消失。
小结:
本篇博客介绍了通过操作系统的缓存机制映射到hibernate的缓存机制,发现它俩的思想是一样的。
通过几个实例发现get/load/iterate/list在查询实体时都是支持一级缓存的,而且一级缓存的生命周期和session是一致的,当session关闭当前session中的缓存就会消失。下篇博客通过几个代码实例分析一下二级缓存及一级和二级缓存之间的关系。