`
septem
  • 浏览: 53170 次
  • 性别: Icon_minigender_1
  • 来自: 上海
社区版块
存档分类
最新评论

hibernate二级缓存实战

阅读更多
通过这篇文章纪录hibernate二级缓存的一些使用经历,利用几个test case,从代码角度说明二级缓存在使用过程中一些需要注意的问题

使用到的Model类有两个,Author, Book, 两者之间为一对多的关系
@Entity
@Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class Author {
	
	private Long id;
	private String name;
	
	private Set<Book> books = new HashSet<Book>();
        // getter setter methods omitted
}


@Entity
@Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class Book {
	
	private Long id;
	private String title;
	
	private Author author;
        // getter setter methods omitted
}


主要的测试类为TestHibernateSecondLevelCache.java

public class TestHibernateSecondLevelCache {
	
	protected Logger logger = LoggerFactory.getLogger(getClass());
	
	private static SessionFactory sessionFactory;
	
	@BeforeClass
	public static void setUpSessionFactory(){
		sessionFactory = new AnnotationConfiguration().configure().buildSessionFactory();
	}
	
	@After
	public void clearSecondLevelCache(){
		logger.info("clear second level cache");
		sessionFactory.evict(Author.class);
		sessionFactory.evict(Book.class);
                sessionFactory.getStatistics().clear();
	}

    private Session openSession(){
		return sessionFactory.openSession();
	}
	
	private Statistics getStatistics(){
		return sessionFactory.getStatistics();
	}
}


方法setUpSessionFactory用于创建Hibernate SessionFactory,因为创建Session Factory是个相对比较耗时的操作,因此加上Junit4的@BeforeClass annotation,表示该Session Factory只会创建一次,被所有的test case共享.而clearSecondLevelCache方法会在每个test case结束时调用,用于清空二级缓存,防止前一个test case的結果影响后一个test case

测试使用的hibernate-core版本为:3.3.2.GA, hibernate-annotations版本为:3.4.0.GA,测试的数据库为hsqldb内存数据库

一. session.get()

先来看一下session.get是否会查找二级缓存

    
    @Test
	public void testSessionGetCache(){
		Author author = createAuthor();
		
		assertGetMissCache(Author.class, author.getId());
		assertGetHitCache(Author.class, author.getId());
		
		updateAuthor(author);
		
		assertGetMissCache(Author.class, author.getId());
	}
        
    private Author createAuthor(){
		Session session = openSession();
		Author author = new Author();
		author.setName("septem");
		session.save(author);
		session.close();
		return author;
	}
	
	@SuppressWarnings("unchecked")
	private void assertGetMissCache(Class clazz, Serializable id){
		Statistics stat = getStatistics();
		long missCount = stat.getSecondLevelCacheMissCount();
		Session session = openSession();
		session.get(clazz, id);
		session.close();
		assertEquals(missCount + 1, stat.getSecondLevelCacheMissCount());
	}
	
	@SuppressWarnings("unchecked")
	private void assertGetHitCache(Class clazz, Serializable id){
		Statistics stat = getStatistics();
		long hitCount = stat.getSecondLevelCacheHitCount();
		Session session = openSession();
		session.get(clazz, id);
		session.close();
		assertEquals(hitCount + 1, stat.getSecondLevelCacheHitCount());
	}

    private void updateAuthor(Author author){
		author.setName("new_name");
		Session session = openSession();
		session.update(author);
		session.flush();
		session.close();
	}

testSessionGetCache首先通过createAuthor创建一个author对象,然后在assertGetMissCache里面通过author.id使用get方法查出之前创建的author,因为这是每一次调用get方法,所以hibernate从数据库取回author对象,并将它存入二级缓存.测试結果通过hibernate statistics统计信息里的second level cache miss count来判断这次的get查询未命中缓存

接着assertGetHitCache用同一个id通过get方法获取author对象,因为这个id的对象之前已存入二级缓存,所以这次操作命中缓存

最后通过updateAuthor更新之前的author对象,hibernate会自动将该对象从二级缓存中清除,因此第三次调用get方法时没有命中缓存

总结 : session.get方法会先中二级缓存中通过id做为key查找相应的对象,如果不存在,再发送SQL语句到数据库中查询

二. session.load()

第二步试一下session.load方法

@Test
	public void testSessionLoadCache(){
		Author author = createAuthor();
		
		assertLoadMissCache(Author.class, author.getId());
		assertLoadHitCache(Author.class, author.getId());
		
		updateAuthor(author);
		
		assertLoadMissCache(Author.class, author.getId());
	}

        @SuppressWarnings("unchecked")
	private void assertLoadMissCache(Class clazz, Serializable id){
		Statistics stat = getStatistics();
		long missCount = stat.getSecondLevelCacheMissCount();
		Session session = openSession();
		Author author = (Author) session.load(clazz, id);
		author.getName();
		session.close();
		assertEquals(missCount + 1, stat.getSecondLevelCacheMissCount());
	}
	
	@SuppressWarnings("unchecked")
	private void assertLoadHitCache(Class clazz, Serializable id){
		Statistics stat = getStatistics();
		long hitCount = stat.getSecondLevelCacheHitCount();
		Session session = openSession();
		session.load(clazz, id);
		Author author = (Author) session.load(clazz, id);
		author.getName();
		session.close();
		assertEquals(hitCount + 1, stat.getSecondLevelCacheHitCount());
	}



同样的結果,每一次通过id load未命中缓存,第二次通过相同的id调用load方法命中缓存,而更新过author对象后,缓存失效,第三次查询通过数据库获取author

有一点跟get方法不同:
Author author = (Author) session.load(clazz, id);
		author.getName();


总结: 调用load方法的时候,hibernate一开始并没有查询二级缓存或是数据库,而是先返回一个代理对象,该对象只包含id,只有显示调用对象的非id属性时,比如author.getName(),hibernate才会去二级缓存查找,如果没命中缓存再去数据库找,数据库还找不到则抛异常.load方法会尽量推迟对象的查找工作,这是它跟get方法最大的区别.

这两者的测试用例如下:

@Test(expected=ObjectNotFoundException.class)
	public void testSessionLoadNonexistAuthor(){
		Session session = openSession();
		Author author = (Author) session.load(Author.class, -1L);
                assertEquals(Long.valueOf(-1), author.getId());
		author.getName();
		session.close();
	}
	
	@Test
	public void testSessionGetNonexistAuthor(){
		Session session = openSession();
		Author author = (Author) session.get(Author.class, -1L);
        session.close();
		assertNull(author);
	}


三. session.createQuery().list()

@SuppressWarnings("unchecked")
	@Test
	public void testSessionList(){
		Author author = createAuthor();
		createAuthor();
		
		Session session = openSession();
		//hit database to select authors and populate the cache
		List<Author> authors = session.createQuery("from Author").list();
		session.close();
		
		assertEquals(authors.size(), getStatistics().getSecondLevelCachePutCount());
		
		Session session2 = openSession();
		//hit database again to select authors
		session2.createQuery("from Author").list();
		session2.close();
		
		assertEquals(authors.size(), getStatistics().getSecondLevelCachePutCount());
		
		assertGetHitCache(Author.class, author.getId());
	}


首先创建2个author对象,使用HQL : "from Author"调用list方法,这时hibernate直接从数据库查询所有的author对象,并没有从缓存中查询,但是通过list方法查出的所有author对象会存入二级缓存,这点通过getStatistics().getSecondLevelCachePutCount()可以看出来

接着再调用list方法一次,因为此时还没找开查询缓存,list方法重新从数据查了一次.因为第一次查询已将所有的author存入缓存,所以再调用get方法时会命中缓存,assertGetHitCache通过

总结: list方法不会从二级缓存中查找,但它从数据库中查找出来的对象会被存入cache


四. session.createQuery().iterate()

@SuppressWarnings("unchecked")
	@Test
	public void testSessionIterate(){
		Author author = createAuthor();
		createAuthor();
		
		int authorCount = 0;
		
		Session session = openSession();
                //hit database to get ids for all author
		Iterator<Author>  it = session.createQuery("from Author").iterate();
		while(it.hasNext()){
			Author a = it.next();
			a.getName();
			authorCount++;
		}
		session.close();
		assertEquals(authorCount, getStatistics().getEntityLoadCount());
		assertGetHitCache(Author.class, author.getId());
	}


先创建2个author对象, 通过HQL: "from Author"调用iterate方法,此时hibernate并没有查author对象,而是先从数据库查出所有author的id,控制台会输入类似以下的SQL:

select id from author


在对iterator里面遍历的时候,会根据id一个一个地从先中缓存中查找author,没找到再访问数据库


总结: iterate方法使用的是典型的N+1次查询,先从数据库查询出所有对象的ID,再根据ID一个一个地从二级缓存查找,二级缓存找不到再查询数据库


五. association cache

hibernate支持对关联进行缓存,先在Book.java加上books集合的缓存配置

@OneToMany(mappedBy = "author", fetch = FetchType.LAZY)
	@Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
	public Set<Book> getBooks() {
		return books;
	}


测试用例如下:

@Test
	public void testAssociationCache(){
		Author author = createAuthorWith3Books();
		assertGetBooksForAuthorMissCache(author, 1);
		assertGetBooksForAuthorHitCache(author, 4);
		updateOneBookForAuthor(author);
		assertGetBooksForAuthorMissCache(author, 1);
		addNewBookForAuthor(author);
		assertGetBooksForAuthorMissCache(author, 1);
	}

    private Author createAuthorWith3Books(){
		Session session = openSession();
		
		Author author = new Author();
		author.setName("septem");
		
		Book book1 = new Book();
		book1.setTitle("book1");
		book1.setAuthor(author);
		
		Book book2 = new Book();
		book2.setTitle("book2");
		book2.setAuthor(author);
		
		Book book3 = new Book();
		book3.setTitle("book3");
		book3.setAuthor(author);
		
		author.getBooks().add(book1);
		author.getBooks().add(book2);
		author.getBooks().add(book3);
		
		session.save(book1);
		session.save(book2);
		session.save(book3);
		
		session.close();
		return author;
	}

    private void assertGetBooksForAuthorMissCache(Author author, long miss){
		Session session = openSession();
		Author a = (Author) session.get(Author.class, author.getId());
		long missCount = getStatistics().getSecondLevelCacheMissCount();
		a.getBooks().size();
		session.close();
		assertEquals(missCount + miss, getStatistics().getSecondLevelCacheMissCount());
	}
	
	private void assertGetBooksForAuthorHitCache(Author author, long hit){
		Session session = openSession();
		Author a = (Author) session.get(Author.class, author.getId());
		long hitCount = getStatistics().getSecondLevelCacheHitCount();
		a.getBooks().size();
		session.close();
		assertEquals(hitCount + hit, getStatistics().getSecondLevelCacheHitCount());
	}
	
	private void updateOneBookForAuthor(Author author){
		Session session = openSession();
		
		Author a = (Author) session.get(Author.class, author.getId());
		Book book = (Book) session.get(Book.class, a.getBooks().iterator().next().getId());
		book.setTitle("new_title");
		session.flush();
		
		session.close();
	}
	
	private void addNewBookForAuthor(Author author){
		Session session = openSession();
		
		Author a = (Author) session.get(Author.class, author.getId());
		Book book = new Book();
		book.setTitle("new_book");
		book.setAuthor(a);
		a.getBooks().add(book);
		session.save(book);
		session.update(a);
		session.flush();
		session.close();
	}


先创建一个author,为该author添加3个book对象.在assertGetBooksForAuthorMissCache通过author.getBooks访问关联的book集合,因为延迟加载的关系,此时并没有查询缓存也没有查询数据库,在调用a.getBooks().size()也就是访问book集合的元素时,hibernate先中缓存中查找,没有发现关联缓存,重新数据库查询,生成的SQL类似如下:

select * from book where author_id = ?


此时statistics的missCount只增加了1,因为调用author.getBooks没有命中缓存.hibernate从数据库查询出books后,将books关联以及三个book对象都存入二级缓存.

关联的缓存是以什么样的形式存在呢?注意关联缓存没有保存books集合本身,而是保存所有book的id,假设3个book对象的id分别为1, 2, 3,则author缓存的格式类似于如下:

*---------------------------------*
|        Author Data Cache        |
|---------------------------------|
| 1 -> [ "septem" , [ 1, 2, 3 ] ] |
*---------------------------------*


第二步执行assertGetBooksForAuthorHitCache(author, 4)的时候,我们看到hitCount增加了4.因为第二次调用author.getBooks的时候,命中了关联缓存,从缓存中取回3个id,又分别用id一个一个地从二级缓存中取回3个book对象,一共命中缓存4次

接着通过updateOneBookForAuthor(author)更新了其中的一个book对象,假设更新的是id为1的book.接着的assertGetBooksForAuthorMissCache(author, 1)方法里面missCount又增加了1.book虽然更新了,但是author.getBooks还是能命中缓存,因为book id列表还是[ 1, 2, 3 ].从缓存中取回book id列表,通过book id查找book的时候,因为id为1的book已经更新过了,它的二级缓存失效了,重新去数据库取,此时missCount增加了1,而id为2,3的book还是从二级缓存中找到的.这个方法hibernate会生成类似如下的SQL:

select * from book where id = 1


更新其中的一个book对象不会造成关联缓存的失效,但如果更新了集合id列表的话,缓存就会失效.先通过addNewBookForAuthor为author增加一个books对象,此时books集合里面一共有4个book对象,最后的assertGetBooksForAuthorMissCache(author, 1)我们可以看到缓存失效,missCount增加了1.此时同第一次调用author.getBooks一样,hibernate生成类似如下的SQL

select * from book where author_id = ?



总结: 关联缓存保存的是集合的id列表,而不是集合本身,关联命中缓存的时候,会根据id一个一个地先从二级缓存查找,找不到再查询数据库.更新集合中的某个对象不会造成关联缓存失效,只有改变集合的id列表才会造成缓存失效


五. 查询缓存query cache

在hibernate.cfg.xml里面加上以下配置开启查询缓存:

<property name="hibernate.cache.use_query_cache">true</property>

测试用例如下:
@Test
	public void testQueryCache(){
		createAuthor();
		createAuthor();
		
		assertQueryMissCache();
		
		assertQueryHitCache();
		
		createAuthor();
		assertQueryMissCache();
	}


先做准备工作,创建两个author对象,假设它们的id分别为1,2.assertQueryMissCache里面第一次调用list方法,注意调用list前必须setCacheable(true)才会使用查询缓存,此时未命中查询缓存,hibernate从数据库查询Author对象,将此次查询存入查询缓存,同时会将查询到的author对象存入二级缓存

查询缓存并不保存查询结果集,而只是保存结果集的id,它的结构类似以下数据:

*---------------------------------------------------------------*
|                         Query Cache                           |
|---------------------------------------------------------------|
| [ ["from Author where name = ?", [ "septem"] ] -> [  1, 2 ] ] |
*---------------------------------------------------------------*

注意缓存的key与HQL,参数以及分页参数有关

再调用assertQueryHitCache()用同样的HQL与参数重新查询Author此时会命中查询缓存,并根据结果集id一个一个地查询author对象,因为author对象之前已存入二级缓存,所以这次查询也会命中二级缓存

查询缓存的失效比较特殊,只要查询涉及的任何一张表的数据发生变化,缓存就会失效.比如我们再创建一个Author对象,此时Author表发生了变化,原来的查询缓存就失效了


总结: 查询缓存的key与HQL,查询参数以及分布参数有关,而且一旦查询涉及到的任何一张表的数据发生了变化,缓存就失效了,所以在生产环境中命中率较低.查询缓存保存的是结果集的id列表,而不是结果集本身,命中缓存的时候,会根据id一个一个地先从二级缓存查找,找不到再查询数据库.


涉及到的所有代码保存在google code上
svn checkout http://hibernate-cache-testcase.googlecode.com/svn/trunk/ hibernate-cache-testcase
2
0
分享到:
评论
7 楼 黑色柳丁110 2012-07-05  
黑色柳丁110 写道
黑色柳丁110 写道
黑色柳丁110 写道
        



6 楼 黑色柳丁110 2012-07-05  
黑色柳丁110 写道
黑色柳丁110 写道
        


5 楼 黑色柳丁110 2012-07-05  
黑色柳丁110 写道
        

4 楼 黑色柳丁110 2012-07-02  
        
3 楼 dominic6988 2011-03-29  
写的不错,又学新东西了,come on and keep up a good work.
2 楼 fuanyu 2010-12-02  
博主,我用svn更新不了你上面的地址。你有源码吗?发一份到我的QQ邮箱:382619163@qq.com  ;谢谢。。
1 楼 fuanyu 2010-12-02  
讲得很详细,回去试试。。

相关推荐

    J2EE企业级项目开发-1期 05 hibernate二级缓存实战经验.doc

    J2EE企业级项目开发-1期 05 hibernate二级缓存实战经验.doc 学习资料 复习资料 教学资源

    hibernate二级缓存实战之EhCacheProvider

    NULL 博文链接:https://yedehua.iteye.com/blog/940916

    Hibernate实战(第2版 中文高清版)

     13.4.5 控制二级高速缓存   13.5 小结   第14章 利用HQL和JPA QL查询   14.1 创建和运行查询   14.1.1 准备查询   14.1.2 执行查询   14.1.3 使用具名查询   14.2 基本的HQL和JPA QL查询   14.2.1...

    Hibernate4实战(pdf_source).

    Hibernate4(关系映射,事务,原理,性能和二级缓存,最佳实践) Hibernate4(基本开发,入门,配置,CRUD)

    深入浅出Hibernate(PDF)第二部分

    本书内容深入浅出,先讲述持久层设计与ORM,再由Hibernate概述、Hibernate基础Hibernate高级特性顺序展开,直至Hibernate实战,重点讲述了Hibernate的基础语法、基础配置、O/R映射、数据关联、数据检索、HQL实用技术...

    深入浅出Hibernate(PDF)第一部分

    本书内容深入浅出,先讲述持久层设计与ORM,再由Hibernate概述、Hibernate基础Hibernate高级特性顺序展开,直至Hibernate实战,重点讲述了Hibernate的基础语法、基础配置、O/R映射、数据关联、数据检索、HQL实用技术...

    深入浅出hibernate(PDF)第三部分

    本书内容深入浅出,先讲述持久层设计与ORM,再由Hibernate概述、Hibernate基础Hibernate高级特性顺序展开,直至Hibernate实战,重点讲述了Hibernate的基础语法、基础配置、O/R映射、数据关联、数据检索、HQL实用技术...

    夏昕.深入浅出Hibernate

    从一个基础程序入手,讲述Hibernate的基本语法与配置,慢慢升高到缓存、延迟加载等高级特性。本书内容深入浅出,先讲述持久层设计与ORM,再由Hibernate概述、Hibernate基础Hibernate高级特性顺序展开,直至Hibernate...

    Spring3.x企业应用开发实战(完整版) part1

    17.4.5 使用Hibernate二级缓存 17.5 对持久层进行测试 17.5.1 配置Unitils测试环境 17.5.2 准备测试数据库及测试数据 17.5.3 编写DAO测试基类 17.5.4 编写BoardDao测试用例 17.6 服务层开发 17.6.1 UserService的...

    Spring.3.x企业应用开发实战(完整版).part2

    17.4.5 使用Hibernate二级缓存 17.5 对持久层进行测试 17.5.1 配置Unitils测试环境 17.5.2 准备测试数据库及测试数据 17.5.3 编写DAO测试基类 17.5.4 编写BoardDao测试用例 17.6 服务层开发 17.6.1 UserService的...

    Java Web程序设计教程

    10.2.3应用二级缓存 214 10.2.4应用第三方缓存 216 10.3项目实战——借还图书 217 本章小结 224 课后练习 224 第11章spring框架基础 226 11.1spring框架概述 226 11.1.1认识spring框架 226 11.1.2spring框架...

    iBATIS实战

    2.2.2 iBATIS之于大型、企业级系统 31 2.3 为何使用iBATIS 31 2.3.1 简单性 32 2.3.2 生产效率 32 2.3.3 性能 32 2.3.4 关注点分离 33 2.3.5 明确分工 33 2.3.6 可移植性:Java、.NET及其他 33 2.3.7 开源和诚实 33 ...

    最新Java面试题视频网盘,Java面试题84集、java面试专属及面试必问课程

    │ Java面试题58:hibernate的缓存.mp4 │ Java面试题59.webservice的使用场景.mp4 │ Java面试题60.Activiti的简单介绍.mp4 │ Java面试题61.linux的使用场景.mp4 │ Java面试题62.linux常用命令.mp4 │ Java面试题...

Global site tag (gtag.js) - Google Analytics