Hibernate框架提供了Session.load()和Session.get()方法,用来根据实体对象的主键值从数据库中查询对应记录。针对load和get方法,hibernate提供了一级缓存和二级缓存的支持,提高查询效率,具体可以参考我的博客:通过测试用例和执行结果,让你正确推测和理解Session中Load和get的区别,不再困惑。
那么什么是查询缓存呢?hibernate中提供了一级缓存、二级缓存用来提高单个记录查询的效率。查询缓存是为了提高批量查询的效率。何为批量查询呢?就是使用HQL/SQL语句,从数据库中查询多条满足条件的记录。也就是说:load和get缓存中的key是实体对象的主键值;查询缓存中的key则是hql/sql语句。我们以HQL语句为例进行介绍,SQL语句的查询稍后再进行测试。在hibernate中可以使用如下形式的代码,进行HQL查询。
Query query = hqlSession.createQuery("from Student where name='zhangsan111'"); List<Student> studentList = (List<Student>) query.list(); Iterator<Student> studentIterator = (Iterator<Student>) query.iterate();
在hibernate4.1.6版本中,为了使用查询缓存,需要进行两步操作:
1.在hibernate.cfg.xml中开启查询缓存,因为hibernate默认情况下会关闭查询缓存。配置方式如下:
<property name="hibernate.cache.use_query_cache">true</property>2.在hibernate的Query对象中,通过代码设置开启查询缓存。代码如下:
Session hqlSession = sessionFactory.openSession(); Query query = hqlSession.createQuery("from Student where name='zhangsan111'"); query.setCacheable(true);注意:经过上面的配置,我们开启了查询缓存,但是没有开启二级缓存。我们先编写单元测试代码,然后通过查看实际的运行结果,来分析查询缓存的原理。测试代码如下:
public class TestQueryCache { private SessionFactory sessionFactory = new Configuration().configure() .buildSessionFactory(); private String testHql = "from Student where name='zhangsan111'"; @Test // 如果开启了查询缓存,list第二次查询,直接获取缓存的主键id,然后根据id查询详情 public void testUseList() { testListQuery(sessionFactory); System.out.println("-------list进行第二次查询------"); testListQuery(sessionFactory); } @Test // 是否开启查询缓存,对iterator没有影响,它都会先根据hql语句查询id,再根据id查询详情 public void testUseIterator() { testIterateQuery(sessionFactory); System.out.println("-------iterate进行第二次查询------"); testIterateQuery(sessionFactory); } @Test public void testIteratorAndList1() { testIterateQuery(sessionFactory); System.out.println("-------第一次使用iterate,第二次使用list查询------"); testListQuery(sessionFactory); } @Test public void testIteratorAndList2() { testListQuery(sessionFactory); System.out.println("-------第一次使用list,第二次使用iterate查询------"); testIterateQuery(sessionFactory); } private void testListQuery(SessionFactory sessionFactory) { Session hqlSession = sessionFactory.openSession(); Query query = hqlSession.createQuery(testHql); query.setCacheable(true); @SuppressWarnings("unchecked") List<Student> studentList = (List<Student>) query.list(); for (Student student : studentList) { System.out.println("list语句测试query cache:" + student); } hqlSession.close(); } private void testIterateQuery(SessionFactory sessionFactory) { Session hqlSession = sessionFactory.openSession(); Query query = hqlSession.createQuery(testHql); query.setCacheable(true); @SuppressWarnings("unchecked") Iterator<Student> studentList = (Iterator<Student>) query.iterate(); while (studentList.hasNext()) { System.out.println("iterate语句测试query cache:" + studentList.next()); } hqlSession.close(); } }
数据库中的记录如下:
1. 2次list()执行结果分析
list()方法执行效果如下:
Hibernate: select student0_.id as id0_, student0_.name as name0_, student0_.age as age0_ from Student student0_ where student0_.name='zhangsan111' list语句测试query cache:hibernate.Student@a09e41[id=1, name=zhangsan111, age=18] list语句测试query cache:hibernate.Student@9dd6e2[id=2, name=zhangsan111, age=18] list语句测试query cache:hibernate.Student@8698fa[id=3, name=zhangsan111, age=18] -------list进行第二次查询------ Hibernate: select student0_.id as id0_0_, student0_.name as name0_0_, student0_.age as age0_0_ from Student student0_ where student0_.id=? Hibernate: select student0_.id as id0_0_, student0_.name as name0_0_, student0_.age as age0_0_ from Student student0_ where student0_.id=? Hibernate: select student0_.id as id0_0_, student0_.name as name0_0_, student0_.age as age0_0_ from Student student0_ where student0_.id=? list语句测试query cache:hibernate.Student@1751a9e[id=1, name=zhangsan111, age=18] list语句测试query cache:hibernate.Student@60b407[id=2, name=zhangsan111, age=18] list语句测试query cache:hibernate.Student@e391c4[id=3, name=zhangsan111, age=18]第一次查询的时候,list()只发出了一条sql语句,查出了所有name='zhangsan111'的记录共有3条,这个很好理解,因为缓存中没有数据,所以list直接去数据库中进行了查询;第二次查询的时候,list发出了3条根据id去查询的sql语句。这是因为,第二次查询的hql语句跟第一次查询的时候完全相同。所以直接从查询缓存中,获取到满足条件的记录id值。之后根据id去student表中查询详情。可以看出:查询缓存,缓存的key是hql语句,缓存的value是满足hql语句的记录的主键值。也就是说,查询缓存,只是缓存数据库记录的主键值,并不会缓存记录的所有字段值。
2. 2次iterate()执行结果分析
iterate()方法执行效果如下:
Hibernate: select student0_.id as col_0_0_ from Student student0_ where student0_.name='zhangsan111' Hibernate: select student0_.id as id0_0_, student0_.name as name0_0_, student0_.age as age0_0_ from Student student0_ where student0_.id=? iterate语句测试query cache:hibernate.Student@61ec49[id=1, name=zhangsan111, age=18] Hibernate: select student0_.id as id0_0_, student0_.name as name0_0_, student0_.age as age0_0_ from Student student0_ where student0_.id=? iterate语句测试query cache:hibernate.Student@4e21db[id=2, name=zhangsan111, age=18] Hibernate: select student0_.id as id0_0_, student0_.name as name0_0_, student0_.age as age0_0_ from Student student0_ where student0_.id=? iterate语句测试query cache:hibernate.Student@1e3a0ec[id=3, name=zhangsan111, age=18] -------iterate进行第二次查询------ Hibernate: select student0_.id as col_0_0_ from Student student0_ where student0_.name='zhangsan111' Hibernate: select student0_.id as id0_0_, student0_.name as name0_0_, student0_.age as age0_0_ from Student student0_ where student0_.id=? iterate语句测试query cache:hibernate.Student@1e4905a[id=1, name=zhangsan111, age=18] Hibernate: select student0_.id as id0_0_, student0_.name as name0_0_, student0_.age as age0_0_ from Student student0_ where student0_.id=? iterate语句测试query cache:hibernate.Student@1751a9e[id=2, name=zhangsan111, age=18] Hibernate: select student0_.id as id0_0_, student0_.name as name0_0_, student0_.age as age0_0_ from Student student0_ where student0_.id=? iterate语句测试query cache:hibernate.Student@182a033[id=3, name=zhangsan111, age=18]可以发现:2次使用iterate进行查询,发出的sql完全相同。是否开启查询缓存,对iterate方式没有影响。无论如何,iterate()都会先查询出满足条件的id值,然后再根据id去数据查询记录的详情。
Hibernate: select student0_.id as col_0_0_ from Student student0_ where student0_.name='zhangsan111' Hibernate: select student0_.id as id0_0_, student0_.name as name0_0_, student0_.age as age0_0_ from Student student0_ where student0_.id=? iterate语句测试query cache:hibernate.Student@135daf[id=1, name=zhangsan111, age=18] Hibernate: select student0_.id as id0_0_, student0_.name as name0_0_, student0_.age as age0_0_ from Student student0_ where student0_.id=? iterate语句测试query cache:hibernate.Student@158f1fa[id=2, name=zhangsan111, age=18] Hibernate: select student0_.id as id0_0_, student0_.name as name0_0_, student0_.age as age0_0_ from Student student0_ where student0_.id=? iterate语句测试query cache:hibernate.Student@e9aa13[id=3, name=zhangsan111, age=18] -------第一次使用iterate,第二次使用list查询------ Hibernate: select student0_.id as id0_, student0_.name as name0_, student0_.age as age0_ from Student student0_ where student0_.name='zhangsan111' list语句测试query cache:hibernate.Student@cab854[id=1, name=zhangsan111, age=18] list语句测试query cache:hibernate.Student@10bbd42[id=2, name=zhangsan111, age=18] list语句测试query cache:hibernate.Student@14323d5[id=3, name=zhangsan111, age=18]iterate方法,发出了4次sql查询语句,list发出了一次sql查询语句。可以得到结论:iterate不会将满足条件的id值放入查询缓存中,或者说iterate将满足条件的id值放入了查询缓存,但是list方法并没有使用。个人感觉:iterate方法不会将满足查询条件的id值放入查询缓存。因为如果iterate将id放入了查询缓存,list没有理由不去使用;而且通过2次iterate测试发现,查询缓存对iterate没有影响。
4. 先list后iterate执行结果分析
testIteratorAndList2()方法执行效果如下:
Hibernate: select student0_.id as id0_, student0_.name as name0_, student0_.age as age0_ from Student student0_ where student0_.name='zhangsan111' list语句测试query cache:hibernate.Student@16daa9[id=1, name=zhangsan111, age=18] list语句测试query cache:hibernate.Student@14b9a74[id=2, name=zhangsan111, age=18] list语句测试query cache:hibernate.Student@893969[id=3, name=zhangsan111, age=18] -------第一次使用list,第二次使用iterate查询------ Hibernate: select student0_.id as col_0_0_ from Student student0_ where student0_.name='zhangsan111' Hibernate: select student0_.id as id0_0_, student0_.name as name0_0_, student0_.age as age0_0_ from Student student0_ where student0_.id=? iterate语句测试query cache:hibernate.Student@1add463[id=1, name=zhangsan111, age=18] Hibernate: select student0_.id as id0_0_, student0_.name as name0_0_, student0_.age as age0_0_ from Student student0_ where student0_.id=? iterate语句测试query cache:hibernate.Student@12a66ea[id=2, name=zhangsan111, age=18] Hibernate: select student0_.id as id0_0_, student0_.name as name0_0_, student0_.age as age0_0_ from Student student0_ where student0_.id=? iterate语句测试query cache:hibernate.Student@e580e1[id=3, name=zhangsan111, age=18]结论2次list查询的效果,可以得出结论:list会将满足查询条件的记录的id放入查询缓存,但是iterate不会使用这些缓存的id。
5. 总结
默认hibernate不会开启查询缓存,这是因为查询缓存只有在hql/hql语句语义完全一致的时候,才能命中。而实际查询场景下,查询条件、分页、排序等构成的复杂查询sql语句很难完全一致。可能是hibernate觉得命中率低,所以默认关闭了查询缓存。我们可以根据实际使用情况,决定是否开启查询缓存,唯一的原则就是命中率要尽可能的高。如果针对A表的查询,查询sql语句基本都是完全一致的情况,就可以针对A使用查询缓存;如果B表的查询条件经常变化,很难命中,那么就不要对B表使用查询缓存。这可能就是hibernate使用查询缓存的时候,既要在hibernate.cfg.xml中进行配置,也需要query.setCacheable(true)的原因。
查询缓存只对list有用,对iterate方式无用。iterate不会读也不会写查询缓存,list会读也会写查询缓存。查询缓存中的key是sql语句(这些sql语句会被hibernate解析,保证语义相同的sql,能够命中查询缓存),缓存的value是记录的主键值。