当另一个进程修改数据库时,Hibernate二级缓存失效

28
我们有一个应用程序,使用Hibernate的二级缓存来避免数据库查询。
我想知道是否有一种简单的方法在外部进程(例如直接连接到修改数据库的MySQL管理员)时使Java应用程序的Hibernate二级缓存失效(更新/插入/删除)。
我们使用EHCache作为我们的二级缓存实现。
我们使用@Cache(usage = CacheConcurrencyStrategy.READ_WRITE)和@Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)混合使用,并且没有启用乐观并发控制,在每个实体上使用时间戳。
SessionFactory包含管理二级缓存的方法: - Managing the Caches
sessionFactory.evict(Cat.class, catId); //evict a particular Cat
sessionFactory.evict(Cat.class);  //evict all Cats
sessionFactory.evictCollection("Cat.kittens", catId); //evict a particular collection of kittens
sessionFactory.evictCollection("Cat.kittens"); //evict all kitten collections

但是因为我们使用@Cache注释单个实体类,所以没有一个可靠的中央位置可以让我们将其添加到列表中(例如没有手动步骤)。

// Easy to forget to update this to properly evict the class
public static final Class[] cachedEntityClasses = {Cat.class, Dog.class, Monkey.class}

public void clear2ndLevelCache() {
  SessionFactory sessionFactory = ...   //Retrieve SessionFactory

   for (Class entityClass : cachedEntityClasses) {
       sessionFactory.evict(entityClass);
   }
}

在没有查询实体的情况下,Hibernate的二级缓存无法知道实体在数据库中是否发生了更改(这正是缓存所保护您免受的)。因此,也许我们可以通过调用某些方法来强制清除二级缓存中的所有内容作为解决方案(再次由于缺乏锁定和并发控制,您可能会冒着正在进行的事务从“读取”或更新陈旧数据的风险)。

6个回答

16

根据ChssPly76的评论,这是一种排除所有2级缓存实体的方法(我们可以通过JMX或其他管理工具向管理员公开此方法):

/**
 * Evicts all second level cache hibernate entites. This is generally only
 * needed when an external application modifies the game 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);
    }
}

我想通过调用以下方法从二级缓存中清除缓存数据:-sessionFactory.getCache().evictEntityRegions();我只是想知道,这样做会有什么危害吗?例如:如果我尝试在事务进行中清除缓存,会发生什么? - Vikas Sharma

14

SessionFactory有许多evict()方法,专门用于此目的:

sessionFactory.evict(MyEntity.class); // remove all MyEntity instances
sessionFactory.evict(MyEntity.class, new Long(1)); // remove a particular MyEntity instances

有没有一种可靠的方法可以从二级缓存中驱逐所有带有@Cache注释的类的所有实例。我们通常使用注释来启用二级缓存,因此在添加新实体到二级缓存时,没有真正的一个中心位置来更新代码。 - Dougnukem
1
说实话,清除所有实例似乎是一个相当极端的情况。如果经常这样做,你可能会得到比没有缓存更糟糕的性能。话虽如此,你可以通过getAllClassMetadata()方法获取所有实体的名称(名称是返回映射的键),并为每个实体调用evictEntity() - ChssPly76
这只是极为罕见的情况,我们需要在不关闭服务器的情况下对数据库进行生产“HOTFIX”,而我们现有的客户服务管理工具无法完成此操作(我们也不想暴露某些管理员命令,让您执行任意SQL)。然而,通过允许外部管理员执行SQL,我们失去了所有事务审计和日志记录。 - Dougnukem

9

现在,Hibernate和JPA都提供直接访问底层二级缓存的功能:

sessionFactory.getCache().evict(..);
entityManager.getCache().evict(..)

5

我正在寻找如何使所有的Hibernate缓存无效化,然后我发现了这个有用的片段:

sessionFactory.getCache().evictQueryRegions();
sessionFactory.getCache().evictDefaultQueryRegion();
sessionFactory.getCache().evictCollectionRegions();
sessionFactory.getCache().evictEntityRegions();

希望这对其他人有所帮助。

1
您可以尝试这样做:

private EntityManager em;

public void clear2ndLevelHibernateCache() {
    Session s = (Session) em.getDelegate();
    SessionFactory sf = s.getSessionFactory();

    sf.getCache().evictQueryRegions();
    sf.getCache().evictDefaultQueryRegion();
    sf.getCache().evictCollectionRegions();
    sf.getCache().evictEntityRegions();

    return;
}

我希望它有所帮助。


0
使用分布式缓存时需要考虑的一件事是,QueryCache是本地的,从一个节点上清除它并不会从其他节点上清除它。另一个问题是-清除实体区域而不清除查询区域将导致在尝试从查询缓存中检索数据时进行N+1次选择。关于这个主题的好文章在这里

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接