`

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

分享到:
评论
发表评论

文章已被作者锁定,不允许评论。

相关推荐

    hibernate二级缓存实例

    在这个"hibernate二级缓存实例"中,我们将深入探讨二级缓存的原理、配置以及在实际项目中的应用。 首先,我们需要了解一级缓存和二级缓存的区别。一级缓存是Session级别的,每个Session都有自己的一级缓存,用于...

    hibernate一级缓存和二级缓存的区别与联系

    二级缓存可以是内存中的缓存,也可以扩展到硬盘,例如使用第三方缓存提供商(如 EhCache 或者 Infinispan)。二级缓存中存储的是对象的集合数据,而不是单个对象实例,这样可以更高效地处理大量数据。二级缓存可以...

    Hibernate二级缓存

    Hibernate二级缓存是一种提高应用程序性能的技术,它将数据存储在SessionFactory级别的缓存中,使得数据可以在不同的Session之间共享。这与一级缓存(Session级别)不同,一级缓存仅存在于单个Session生命周期内,当...

    hibernate一级缓存、二级缓存和查询缓存

    本文将详细讲解Hibernate中的三级缓存:一级缓存、二级缓存和查询缓存。 ### 1. 一级缓存 一级缓存是Hibernate内置的Session级别的缓存,也被称为事务性缓存。每当我们在Session中进行对象的CRUD(创建、读取、...

    hibernate二级缓存示例源码

    **hibernate二级缓存详解** Hibernate作为Java领域中广泛使用的对象关系映射(ORM)框架,极大地简化了数据库操作。然而,在处理大量数据时,性能优化显得尤为重要,这就是二级缓存的作用。本文将深入探讨Hibernate...

    hibernate 二级缓存详解

    Hibernate 二级缓存是针对SessionFactory级别的全局缓存,与一级缓存(Session级别)不同,一级缓存只在单个Session生命周期内有效。二级缓存则允许不同Session之间共享数据,提高了数据访问效率,减少了对数据库的...

    hibernate二级缓存包

    Hibernate二级缓存是Java开发中使用Hibernate框架进行数据持久化时优化性能的一种重要技术。它在一级缓存(Session级别的缓存)的基础上,提供了一个全局的、跨会话的数据存储层,可以显著减少对数据库的访问,从而...

    day37 05-HIbernate二级缓存:一级缓存更新同步到二级缓存及二级缓存配置文件

    二级缓存通常由第三方缓存提供商如Ehcache、Infinispan等实现。 一级缓存到二级缓存的同步是自动进行的。当对象在一级缓存中被修改并提交后,Hibernate会根据配置将这些变化同步到二级缓存中。这个过程包括了更新、...

    Hibernate 二级缓存

    Hibernate 二级缓存

    hibernate二级缓存

    Hibernate 二级缓存是一种高效的数据存储机制,它能够显著提升Web应用的性能,尤其是在处理大量数据流动时。缓存的主要目标是减少应用与数据库之间的交互次数,从而降低延迟并减轻数据库服务器的压力。在Hibernate...

    Hibernate一级缓存和二级缓存

    **二、Hibernate二级缓存** 二级缓存是SessionFactory级别的,跨越了多个Session,可以被多个线程共享。它通常由第三方插件如EhCache、Infinispan等提供。二级缓存分为以下几种类型: 1. **集合缓存**:用于存储...

    Hibernate 二级缓存 总结整理

    ### 三、配置二级缓存 在Hibernate配置文件`hibernate.cfg.xml`中,我们需要开启二级缓存并指定缓存提供者: ```xml &lt;property name="hibernate.cache.use_second_level_cache"&gt;true &lt;property name="hibernate....

    hibernate二级缓存java包下载

    二级缓存是 Hibernate 缓存策略的一部分,它在应用程序的多个会话之间共享数据,进一步优化了数据库访问效率。 二级缓存分为以下关键知识点: 1. **一级缓存与二级缓存的区别**: - 一级缓存:每个 Hibernate ...

    springboot+jpa(hibernate配置redis为二级缓存) springboot2.1.4

    通过以上步骤,我们就成功地在Spring Boot 2.1.4.RELEASE项目中配置了使用Redis作为Hibernate二级缓存的环境。这将显著提升数据库查询效率,减少对数据库的压力,尤其在高并发场景下,效果尤为明显。记得在实际生产...

    Spring集成的Hibernate配置二级缓存

    以EhCache为例,我们需要在项目中引入ehcache-core或ehcache的依赖,并在Hibernate配置文件(hibernate.cfg.xml或persistence.xml)中启用二级缓存,添加如下配置: ```xml &lt;property name="hibernate.cache.use_...

    hibernate二级缓存所需要的 jar包

    本篇将详细介绍Hibernate二级缓存的概念、作用以及所需jar包的作用。 一、Hibernate二级缓存概念 Hibernate的一级缓存是指Session级别的缓存,每个Session内部都有一个一级缓存,用于存储实体对象,当Session关闭时...

    为Spring集成的Hibernate配置二级缓存

    二级缓存主要由第三方缓存提供者如Ehcache、Infinispan等实现,它们将数据存储在内存或磁盘中,以便于快速访问。 1. **引入依赖**:在项目中添加所需的缓存提供者库。例如,如果选择Ehcache,需要在`pom.xml`或`...

    hibernate 二级缓存

    本篇文章将深入探讨Hibernate二级缓存的概念、工作原理以及如何在实际项目中设置和使用。 **一、二级缓存概念** 一级缓存是每个Hibernate Session内部的一个内存区域,用于存储Session期间的操作对象。当Session...

    Hibernate 一级缓存和二级缓存的区别

    Hibernate 一级缓存和二级缓存的区别

    hibernate一级和二级缓存配置与详解

    本篇将深入探讨Hibernate的一级缓存和二级缓存,以及查询缓存的配置和使用。 ### 一级缓存 一级缓存是Hibernate默认提供的缓存,它是Session级别的,每个Hibernate Session都有一个私有的、本地的一级缓存。当我们...

Global site tag (gtag.js) - Google Analytics