通过这篇文章纪录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是一种强大的对象-关系映射(Object-Relational Mapping,简称ORM)工具,主要用于Java环境下的应用程序。它能够将应用程序中的对象模型映射到关系型数据库的表...
### Hibernate缓存机制详解 #### 一、概述 Hibernate 是一款流行的 Java 持久层框架,它简化了 Java 应用程序与数据库交互的过程。然而,直接使用 JDBC 访问数据库存在一定的性能开销。为了提升性能,Hibernate ...
### Hibernate缓存机制及优化策略 #### 一、概述 Hibernate作为一款优秀的对象关系映射(ORM)框架,在Java开发领域被广泛应用于数据库操作。它提供了丰富的缓存机制来提高应用性能并降低数据库访问压力。本文将...
当需要数据时,Hibernate首先会在缓存中查找,如果找到(即缓存命中),则直接返回,避免了执行数据库查询带来的性能损失。 1-2 Hibernate缓存分类 - 一级缓存:也称为Session缓存,它是一个事务级别的缓存,生命...
当需要数据时,Hibernate首先会在缓存中查找,若找到所需数据(称为“缓存命中”),则直接返回,避免了数据库查询的开销。 **缓存分类**:Hibernate提供了两级缓存。 1. **一级缓存**:也称为Session缓存,位于...
Hibernate 缓存是提高应用程序性能的关键机制,它分为一级缓存和二级缓存。一级缓存是 Hibernate Session 内部的缓存,它是默认开启且不可卸载的,主要用于保持 Session 内部数据状态同步。由于 Session 生命周期较...
例如,监控缓存命中率可以帮助评估缓存的有效性。 #### 七、总结 缓存是提高应用系统性能的关键技术之一。在Hibernate框架中,一级缓存和二级缓存各自承担着不同的角色,通过合理的设计和配置,可以充分发挥缓存的...
总结,Hibernate缓存机制是其性能优化的重要手段,通过理解并合理利用一级缓存、二级缓存和查询缓存,能够显著提升应用的运行效率。然而,缓存的使用也需谨慎,以免引发数据一致性问题。开发者应结合实际业务场景,...
8. **性能监控**:Ehcache提供了一套完善的监控和统计功能,开发者可以通过监控界面查看缓存命中率、缓存大小等指标,以便进行性能调优。 综上所述,Hibernate二级缓存配合Ehcache使用,能够有效提升Java应用的性能...
通过深入了解Hibernate缓存的工作机制及其使用方法,可以帮助开发者更好地利用这一特性来优化应用的性能。需要注意的是,在使用缓存的过程中,需要平衡好缓存带来的性能提升与可能引起的一致性问题之间的关系。
### Hibernate缓存深入解析 #### 缓存的重要性与工作原理 缓存技术是提升系统性能的关键手段之一,尤其是在处理大量数据访问的应用程序中。通过减少对底层数据存储系统的直接访问频率,缓存能够显著加快数据读取...
配置Hibernate缓存通常涉及以下步骤: 1. 引入缓存库(如Ehcache)。 2. 在`hibernate.cfg.xml`或`pom.xml`中配置缓存提供者。 3. 在实体类上使用注解指定是否参与缓存,以及缓存策略。 4. 配置二级缓存区域和查询...
3. **缓存粒度**:合理设计缓存的粒度,过细可能导致大量小缓存,过粗可能导致无效的缓存命中。 4. **缓存失效**:对于有实时性要求的数据,需考虑如何及时更新缓存,避免返回旧数据。 总之,Hibernate的二级缓存...
- **性能监控**:监控缓存命中率和失效情况,以评估其效果并进行优化。 在实践中,通过深入理解Hibernate的二级缓存机制,尤其是查询缓存,开发者可以有效地提高应用的响应速度,减轻数据库负载,从而提升整体性能...
6. **性能调优**:监控缓存命中率,根据实际负载调整缓存大小和策略,避免缓存击穿或雪崩现象。 通过本教程,你将学习到如何在实际项目中配置和优化Hibernate的二级缓存,以及如何解决与之相关的各种问题。这将有助...
在测试过程中,注意监控日志,查看是否有异常信息,以及缓存命中率、缓存读写操作等统计信息,以便优化缓存配置。 总之,理解和掌握Hibernate中的二级缓存ehcache对于提高Java应用的性能至关重要。正确配置和使用...
这些工具可以帮助识别和解决性能瓶颈,如慢查询、缓存命中率低等问题。性能监控通常包括以下方面: - **SQL 语句监控**:记录执行的 SQL 语句及其执行时间。 - **缓存命中率监控**:统计缓存的命中次数与未命中次数...
2. **缓存分区**:根据业务需求,可以将数据进行分区,避免所有数据集中在同一个缓存区域,提高缓存命中率。 3. **缓存更新同步**:确保当数据库中的数据发生变化时,能够及时同步到缓存中,避免数据不一致。可以...
- 监控和调整:使用EhCache的监控工具,观察缓存命中率、大小等指标,根据实际情况调整缓存策略。 - 缓存粒度控制:考虑缓存整个对象还是部分属性,以及缓存的大小和结构,平衡缓存性能和内存消耗。 6. **分布式...