`
septem
  • 浏览: 54476 次
  • 性别: 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二级缓存的实战应用,这对于提高应用程序性能和减少对数据库的频繁访问至关重要。 **Hibernate二级缓存** 二级缓存是Hibernate...

    hibernate二级缓存实战之EhCacheProvider

    二级缓存与一级缓存( Hibernate Session 内部的缓存)不同,一级缓存是每个Session独享的,而二级缓存则是跨Session共享的,可以提高多个并发用户访问相同数据时的性能。 首先,我们需要在Hibernate配置文件(通常...

    Hibernate二级缓存+分页功能

    **一、Hibernate二级缓存** Hibernate一级缓存是指Session级别的缓存,它是默认开启的,每个Session都会维护一个对象缓存,用来存放该Session加载的实体对象。一级缓存的生命周期与Session相同,当Session关闭时,...

    hibernate开启二级缓存和查询缓存

    在 Hibernate 中,二级缓存和查询缓存是提高应用性能的重要机制。下面将详细介绍如何开启并理解这两个缓存机制。 ### 1. 一级缓存与二级缓存 #### 1.1 一级缓存 一级缓存是 Hibernate 内置的 Session 缓存,它是每...

    Hibernate-二级缓存总结 开发技术 - Java.zip

    阅读该文档将有助于深入理解并掌握Hibernate二级缓存的应用。 综上所述,Hibernate的二级缓存是提升Java应用程序性能的有效手段,但同时也需要谨慎对待,合理配置和使用,以防止可能带来的数据一致性问题。通过深入...

    Hibernate实战

    缓存机制可以提高数据访问速度,减少数据库负载,包括一级缓存(Session级)和二级缓存(SessionFactory级)。性能优化涉及查询优化、批处理、连接池配置等。事件监听器和拦截器允许在特定操作前后执行自定义逻辑,...

    hibernate实战 英文版

    4. **缓存机制**:为了提高性能,Hibernate支持多种缓存策略,书中详细介绍了如何合理地使用这些缓存选项。 5. **查询语言**:Hibernate提供了强大的查询语言HQL(Hibernate Query Language),用于执行复杂的数据库...

    Hibernate的缓存使用

    本文将深入探讨Hibernate的缓存使用,包括一级缓存和二级缓存的概念、工作原理以及如何在实际开发中有效利用它们。 ### 一级缓存 一级缓存是Hibernate默认提供的缓存,也称为Session缓存。每当我们在Session中进行...

    hibernate实战第二版.pdf

    4. **缓存机制**:Hibernate支持二级缓存,能有效提高数据访问速度和应用性能。书中会讲解一级缓存(Session级别的缓存)和二级缓存(SessionFactory级别的缓存)的配置和使用,以及第三方缓存集成如 EhCache 和 ...

    Hibernate实战(第2版).zip

    在高级特性部分,书中有对懒加载、集合映射、多对多关系、一对多关系、一对一关系的处理,以及性能优化策略,如缓存机制(第一级缓存、第二级缓存)、批处理等。这部分内容对于提升项目的运行效率至关重要。 此外,...

    hibernate-level2-cache:在Hibernate 2级缓存上进行测试

    一、Hibernate二级缓存简介 Hibernate的二级缓存是进程级缓存,它存储了同一个应用程序的所有会话中的对象,不同于一级缓存(Session级别的缓存),二级缓存可以被多个会话共享。二级缓存由第三方插件如Ehcache、...

    hibernate缓存

    在Hibernate中,缓存分为一级缓存和二级缓存。 1. **一级缓存**:每个Hibernate Session都有一个一级缓存,它是SessionFactory的一部分,自动启用且不可关闭。当实体被加载或保存时,它们会被放入一级缓存。在同一...

    Hibernate开发实战讲解(源代码)

    8. **缓存策略**:Hibernate内置了二级缓存机制,包括一级缓存(Session级别的缓存)和二级缓存(SessionFactory级别的缓存)。了解缓存的工作原理和使用策略,可以提高数据读取效率,减少数据库压力。 9. **事务...

    Manning.Java.Persistence.with.Hibernate[Hibernate实战第二版].pdf

    总之,《Java Persistence with Hibernate》第二版是一本不可多得的参考书籍,它不仅涵盖了一个ORM框架的所有核心概念,还提供了丰富的实战经验和专家见解。通过阅读这本书,你可以掌握如何利用Hibernate来构建高效...

    hibernate---实战

    二级缓存可配置为进程级或集群级,可以显著提升应用性能。 10. **性能优化**:包括延迟加载、批处理、缓存策略调整、预加载等都是Hibernate性能优化的重要手段。 在《Hibernate实战》这本书中,读者可以期待全面...

    Hibernate4实战(pdf_source).

    《Hibernate4实战》这本书主要涵盖了Hibernate4框架在Java开发中的应用和高级技术,包括关系映射、事务管理、核心原理、性能优化以及二级缓存的使用。以下是对这些主题的详细解析: 1. **关系映射**:Hibernate是...

    Hibernate实战(第2版).pdf

    3. **缓存策略**:合理设置缓存策略,利用Hibernate的一级缓存和二级缓存机制。 4. **事务管理**:确保所有的数据库操作都在事务的保护下进行,避免数据不一致的问题。 通过以上介绍,我们可以看到Hibernate作为一...

    Hibernate实战(第2版) 高清扫描版

    8. **第二级缓存和查询缓存**:讨论Hibernate的缓存机制,如何使用第二级缓存提高性能,以及查询缓存如何减少数据库查询次数。 9. **性能优化**:提供一系列优化策略,如延迟加载、批处理、连接池配置等,帮助...

    Hibernate开发实战源代码

    10. **缓存机制**:了解Hibernate的一级缓存和二级缓存,以及它们在性能优化中的作用。 11. **事务管理**:学习如何使用Hibernate的`Transaction`接口进行事务控制,保证数据一致性。 12. **实体生命周期**:熟悉...

    hibernate实战 第2版.pdf

    二级缓存是sessionFactory级别的缓存,它可以被整个应用程序共享,用于缓存不经常变化的数据。 Hibernate支持多种数据库操作优化技术,比如批处理插入(Batch Insertion),它可以减少数据库交互次数,提高批量插入...

Global site tag (gtag.js) - Google Analytics