上一篇我们将到hibernate的二级缓存,hibernate的二级缓存彻底解决了n+1问题,并提供了线程使用的缓存机制,但是hibernate的二级缓存只能缓存对象,不能缓存对象的属性,即不能对hql语句进行缓存。
我们如果要配置查询缓存,只需要在hibernate.cfg.xml中加入一条配置即可:
<!-- 开启查询缓存 -->
<property name="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();
如果是在annotation中,我们还需要在这个类上加上这样一个注解:@Cacheable
接下来我们来通过测试用例来看看我们的查询缓存
①查询缓存也是sessionFactory级别的缓存
TestCase1:
@Test
public void test2() {
Session session = null;
try {
/**
* 此时会发出一条sql取出所有的学生信息
*/
session =HibernateUtil.openSession();
List<Student> ls =session.createQuery("from Student")
.setCacheable(true) //开启查询缓存,查询缓存也是sessionFactory级别的缓存
.setFirstResult(0).setMaxResults(50).list();
Iterator<Student> stus =ls.iterator();
for(;stus.hasNext();) {
Student stu = stus.next();
System.out.println(stu.getName());
}
} catch (Exception e) {
e.printStackTrace();
} finally {
HibernateUtil.close(session);
}
try {
/**
* 此时会发出一条sql取出所有的学生信息
*/
session =HibernateUtil.openSession();
List<Student> ls =session.createQuery("from Student")
.setCacheable(true) //开启查询缓存,查询缓存也是sessionFactory级别的缓存
.setFirstResult(0).setMaxResults(50).list();
Iterator<Student> stus =ls.iterator();
for(;stus.hasNext();) {
Student stu = stus.next();
System.out.println(stu.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也只会发出一条sql,因为已经开启了查询缓存了,并且查询缓存也是sessionFactory级别的
②只有当 HQL 查询语句完全相同时,连参数设置都要相同,此时查询缓存才有效
TestCase2:
@Test
public void test3() {
Session session = null;
try {
/**
* 此时会发出一条sql取出所有的学生信息
*/
session =HibernateUtil.openSession();
List<Student> ls =session.createQuery("from Student where name like ?")
.setCacheable(true)//开启查询缓存,查询缓存也是SessionFactory级别的缓存
.setParameter(0, "%王%")
.setFirstResult(0).setMaxResults(50).list();
Iterator<Student> stus =ls.iterator();
for(;stus.hasNext();) {
Student stu = stus.next();
System.out.println(stu.getName());
}
} catch (Exception e) {
e.printStackTrace();
} finally {
HibernateUtil.close(session);
}
session = null;
try {
/**
* 此时会发出一条sql取出所有的学生信息
*/
session =HibernateUtil.openSession();
/**
* 只有当HQL完全相同的时候,连参数都要相同,查询缓存才有效
*/
// List<Student> ls =session.createQuery("from Student where name like ?")
// .setCacheable(true)//开启查询缓存,查询缓存也是SessionFactory级别的缓存
// .setParameter(0, "%王%")
// .setFirstResult(0).setMaxResults(50).list();
List<Student> ls =session.createQuery("from Student where name like ?")
.setCacheable(true)//开启查询缓存,查询缓存也是SessionFactory级别的缓存
.setParameter(0, "%张%")
.setFirstResult(0).setMaxResults(50).list();
Iterator<Student> stus = ls.iterator();
for(;stus.hasNext();) {
Student stu = stus.next();
System.out.println(stu.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_ where student0_.name like ?limit ?
Hibernate: selectstudent0_.id as id2_, student0_.name as name2_, student0_.sex as sex2_,student0_.rid as rid2_ from t_student student0_ where student0_.name like ?limit ?
我们看到,如果我们的hql查询语句不同的话,我们的查询缓存也没有作用
③查询缓存也能引起 N+1 的问题
查询缓存也能引起 N+1 的问题,我们这里首先先将 Student 对象上的二级缓存先注释掉:
<!-- 二级缓存一般设置为只读的 -->
<!-- <cacheusage="read-only"/> -->
TestCase4:
@Test
public void test4() {
Session session = null;
try {
/**
* 查询缓存缓存的不是对象而是id
*/
session =HibernateUtil.openSession();
List<Student> ls =session.createQuery("from Student where name like ?")
.setCacheable(true)//开启查询缓存,查询缓存也是SessionFactory级别的缓存
.setParameter(0, "%王%")
.setFirstResult(0).setMaxResults(50).list();
Iterator<Student> stus =ls.iterator();
for(;stus.hasNext();) {
Student stu = stus.next();
System.out.println(stu.getName());
}
} catch (Exception e) {
e.printStackTrace();
} finally {
HibernateUtil.close(session);
}
session = null;
try {
/**
* 查询缓存缓存的是id,此时由于在缓存中已经存在了这样的一组学生数据,但是仅仅只是缓存了
* id,所以此处会发出大量的sql语句根据id取对象,这也是发现N+1问题的第二个原因
* 所以如果使用查询缓存必须开启二级缓存
*/
session =HibernateUtil.openSession();
List<Student> ls =session.createQuery("from Student where name like ?")
.setCacheable(true)//开启查询缓存,查询缓存也是SessionFactory级别的缓存
.setParameter(0, "%王%")
.setFirstResult(0).setMaxResults(50).list();
Iterator<Student> stus =ls.iterator();
for(;stus.hasNext();) {
Student stu = stus.next();
System.out.println(stu.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_ where student0_.name like ?limit ?
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=?
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=?
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=?
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=?
.........................
我们看到,当我们将二级缓存注释掉以后,在使用查询缓存时,也会出现 N+1 的问题,为什么呢?
因为查询缓存缓存的也仅仅是对象的id,所以第一条 sql 也是将对象的id都查询出来,但是当我们后面如果要得到每个对象的信息的时候,此时又会发sql语句去查询,所以,如果要使用查询缓存,我们一定也要开启我们的二级缓存,这样就不会出现 N+1 问题了