天天看点

Hibernate缓存体系之查询缓存(query cache),以及list和iterate方法的区别

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();
	}
}
           

数据库中的记录如下:

Hibernate缓存体系之查询缓存(query cache),以及list和iterate方法的区别

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:[email protected][id=1, name=zhangsan111, age=18]
list语句测试query cache:[email protected][id=2, name=zhangsan111, age=18]
list语句测试query cache:[email protected][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:[email protected][id=1, name=zhangsan111, age=18]
list语句测试query cache:[email protected][id=2, name=zhangsan111, age=18]
list语句测试query cache:[email protected][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:[email protected][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:[email protected][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:[email protected][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:[email protected][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:[email protected][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:[email protected][id=3, name=zhangsan111, age=18]
           

可以发现:2次使用iterate进行查询,发出的sql完全相同。是否开启查询缓存,对iterate方式没有影响。无论如何,iterate()都会先查询出满足条件的id值,然后再根据id去数据查询记录的详情。

3. 先iterate后list执行结果分析

testIteratorAndList1() 方法执行效果如下:

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:[email protected][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:[email protected][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:[email protected][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:[email protected][id=1, name=zhangsan111, age=18]
list语句测试query cache:[email protected][id=2, name=zhangsan111, age=18]
list语句测试query cache:[email protected][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:[email protected][id=1, name=zhangsan111, age=18]
list语句测试query cache:[email protected][id=2, name=zhangsan111, age=18]
list语句测试query cache:[email protected][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:[email protected][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:[email protected][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:[email protected][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是记录的主键值。

继续阅读