为什么在hibernate中使用二级缓存

时间:2014-07-09 10:28:45

标签: hibernate caching ehcache

我已经处理过hibernate.But我无法理解何时使用二级缓存。 我看到了hibernate的文档。但我无法弄清楚使用二级缓存的原因

2 个答案:

答案 0 :(得分:3)

首先,让我们谈谈进程级缓存(或者他们在Hibernate中称之为二级缓存)。为了使它工作,你应该a)配置缓存提供者b)告诉hibernate要缓存哪些实体(如果使用这种映射,则在hbm.xml文件中)。您告诉缓存提供程序它应该存储多少个对象以及何时/为什么它们应该被无效。因此,假设您有一个Book和Author实体,每次从DB获取它们时,只会从实际DB中选择那些不在缓存中的实体。这显着提高了性能。在以下情况下它非常有用:

您只能通过Hibernate写入数据库(因为它需要一种方法来知道何时更改或使缓存中的实体无效) 你经常阅读对象 您有一个节点,并且您没有复制。否则,您需要复制缓存本身(使用像JGroups这样的分布式缓存),这会增加更多的复杂性,并且它不会像无共享应用程序那样扩展。 它在以下情况下无效:

让我们说你正在撰写这样一个问题:来自作者a fetch join a.books 。只会从缓存中提取作者,将从数据库中获取书籍。联接无法点击缓存。 如果在您的映射中您提到 fetch =" join" ,则这意味着将在任何地方使用联接而不是单独的select语句。仅当使用 fetch =" select" 时,进程级缓存才适用于子对象。 如果您不按ID选择。第二级缓存存储实体地图'其他属性的ID(它实际上并不存储对象,但数据本身),所以如果您的查找如下所示:来自作者姓名=:名称,那么您就不会这样做。点击缓存。

现在,关于查询缓存。您应该注意,它不是一个单独的缓存,它是进程级缓存的补充。我们假设您有一个国家/地区实体。它是静态的,所以你知道每次从Country说出来时都会有相同的结果集。这是查询缓存的完美候选者,它将自己存储ID列表,当您下次选择所有国家/地区时,它会将此列表返回到进程级缓存,而后者又将,将返回每个ID的对象,因为这些对象已存储在二级缓存中。每次与实体相关的任何内容发生变化时,查询缓存都会失效。因此,我们假设您已从作者配置为放入查询缓存。由于作者经常更改,它不会有效。因此,您应该仅将查询缓存用于更多或更少的静态数据。

二级缓存是一个键值存储。只有通过id

获取实体时它才有效

当通过hibernate更新/删除实体时,每个实体使第二级缓存无效/更新。如果以不同的方式更新数据库,则不会失效。

查询的

(例如客户列表)使用查询缓存。

实际上,拥有一个键值分布式缓存很有用 - 这就是memcached的功能,它支持facebook,twitter等等。但是如果你没有通过id进行查找,那么它就不会非常有用。

二级缓存的工作原理

让我们一点一点地写出所有事实:

每当hibernate会话尝试加载一个实体时,它首先在第一级缓存中查找实体的缓存副本(与特定的hibernate会话相关联)。

如果实体的缓存副本存在于第一级缓存中,则它将作为加载方法返回。

如果第一级缓存中没有缓存实体,则会查找二级缓存以获取缓存实体。

如果二级缓存具有缓存实体,则它将作为load方法返回。但是,在返回实体之前,它也存储在第一级缓存中,以便下一次调用实体的加载方法将从第一级缓存本身返回实体,并且不需要再次进入二级缓存。

如果在第一级缓存和第二级缓存中也找不到实体,则在作为load()方法的响应返回之前,执行数据库查询并将实体存储在两个缓存级别中。

如果通过hibernate会话API进行了修改,则二级缓存会为已修改的实体验证自身。

如果某个用户或进程直接在数据库中进行更改,则在该缓存区域的“timeToLiveSeconds”持续时间过去之前,二级缓存无法自行更新。在这种情况下,最好使整个缓存无效,让hibernate再次构建其缓存。

您可以使用下面的代码段来使整个休眠二级缓存无效。

/**
 * Evicts all second level cache hibernate entites. This is generally only
 * needed when an external application modifies the databaase.
 */
public void evict2ndLevelCache() {
    try {
        Map<String, ClassMetadata> classesMetadata = sessionFactory.getAllClassMetadata();
        for (String entityName : classesMetadata.keySet()) {
            logger.info("Evicting Entity from 2nd level cache: " + entityName);
            sessionFactory.evictEntity(entityName);
        }
    } catch (Exception e) {
        logger.logp(Level.SEVERE, "SessionController", "evict2ndLevelCache", "Error evicting 2nd level hibernate cache entities: ", e);
    }
}

为了更多地了解使用示例,我编写了一个测试应用程序,其中我将EhCache配置为二级缓存。让我们看看各种场景:

a)第一次获取实体

DepartmentEntity department = (DepartmentEntity) session.load(DepartmentEntity.class, new Integer(1));
System.out.println(department.getName());

System.out.println(HibernateUtil.getSessionFactory().getStatistics().getEntityFetchCount());           //Prints 1
System.out.println(HibernateUtil.getSessionFactory().getStatistics().getSecondLevelCacheHitCount());   //Prints 0

输出:1 0

说明:实体不存在于第一级或第二级缓存中,因此,它是从数据库中提取的。

b)第二次获取实体

//Entity is fecthed very first time
DepartmentEntity department = (DepartmentEntity) session.load(DepartmentEntity.class, new Integer(1));
System.out.println(department.getName());

//fetch the department entity again
department = (DepartmentEntity) session.load(DepartmentEntity.class, new Integer(1));
System.out.println(department.getName());

System.out.println(HibernateUtil.getSessionFactory().getStatistics().getEntityFetchCount());           //Prints 1
System.out.println(HibernateUtil.getSessionFactory().getStatistics().getSecondLevelCacheHitCount());   //Prints 0

输出:1 0

说明:实体存在于一级缓存中,因此从那里获取。无需转到二级缓存。

c)实体从第一级缓存中逐出并再次获取

//Entity is fecthed very first time
DepartmentEntity department = (DepartmentEntity) session.load(DepartmentEntity.class, new Integer(1));
System.out.println(department.getName());

//fetch the department entity again
department = (DepartmentEntity) session.load(DepartmentEntity.class, new Integer(1));
System.out.println(department.getName());

//Evict from first level cache
session.evict(department);

department = (DepartmentEntity) session.load(DepartmentEntity.class, new Integer(1));
System.out.println(department.getName());

System.out.println(HibernateUtil.getSessionFactory().getStatistics().getEntityFetchCount());           //Prints 1
System.out.println(HibernateUtil.getSessionFactory().getStatistics().getSecondLevelCacheHitCount());   //Prints 1

输出:1 1

说明:首次从数据库中获取实体。这导致它存储在第一级和第二级缓存中。从第一级缓存中获取第二次加载调用。然后我们从第一级缓存中驱逐实体。因此,第三次load()调用转到二级缓存,getSecondLevelCacheHitCount()返回1.

d)从另一个会话访问二级缓存

/Entity is fecthed very first time
DepartmentEntity department = (DepartmentEntity) session.load(DepartmentEntity.class, new Integer(1));
System.out.println(department.getName());

//fetch the department entity again
department = (DepartmentEntity) session.load(DepartmentEntity.class, new Integer(1));
System.out.println(department.getName());

//Evict from first level cache
session.evict(department);

department = (DepartmentEntity) session.load(DepartmentEntity.class, new Integer(1));
System.out.println(department.getName());

department = (DepartmentEntity) anotherSession.load(DepartmentEntity.class, new Integer(1));
System.out.println(department.getName());

System.out.println(HibernateUtil.getSessionFactory().getStatistics().getEntityFetchCount());           //Prints 1
System.out.println(HibernateUtil.getSessionFactory().getStatistics().getSecondLevelCacheHitCount());   //Prints 2

输出:1 2

说明:当从同一会话工厂创建的另一个会话尝试获取实体时,它会在二级缓存中成功查找,并且不会进行数据库调用。

答案 1 :(得分:1)

几乎每当我们在设计中使用cache时,它都是为了提高性能。 Hibernate的第一级和第二级缓存是相同的。我假设您的问题是,因为我们已经有了第一级缓存,为什么我们仍然需要二级缓存?

第一级缓存与&#34; session&#34;相关联。宾语。其他会话对象无法看到它。会话关闭后,缓存的对象永远消失。相反,二级缓存会保留在内存中,直到它出于某种原因被逐出。

为什么不直接使用一级缓存并使其可用?使缓存无效是我们编程领域中最复杂的事情之一。限制范围使得依赖更加容易和安全。因此我们默认使用第一级缓存。二级缓存更复杂,需要更多配置。因此,该功能就在那里,只需要小心并确保在使用时正确配置。