Hibernate 2nd level cache invalidation when another process modifies the database
Asked Answered
K

6

29

We have an application that uses Hibernate's 2nd level caching to avoid database hits.

I was wondering if there is some easy way to invalidate the Java application's Hibernate 2nd level cache when an outside process such as a MySQL administrator directly connected to modify the database (update/insert/delete).

We are using EHCache as our 2nd level cache implementation.

We use a mix of @Cache(usage = CacheConcurrencyStrategy.READ_WRITE) and @Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE), and we don't have Optimistic concurrency control enabled using timestamps on each entity.

The SessionFactory contains methods to manage the 2nd level cache: - 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

But because we annotate individual entity classes with @Cache, there's no central place for us to "reliably" (e.g. no manual steps) add that to the list.

// 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);
   }
}

There's no real way for Hibernate's 2nd level cache to know that an entity changed in the DB unless it queries that entity (which is what the cache is protecting you from). So maybe as a solution we could simply call some method to force the second level cache to evict everything (again because of lack of locking and concurrency control you risk in progress transactions from "reading" or updating stale data).

Kohn answered 21/10, 2009 at 21:45 Comment(0)
K
18

Based on ChssPly76's comments here's a method that evicts all entities from 2nd level cache (we can expose this method to admins through JMX or other admin tools):

/**
 * 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);
    }
}
Kohn answered 21/10, 2009 at 23:43 Comment(1)
I want to clear cache data from 2nd level cache by calling below method:- sessionFactory.getCache().evictEntityRegions(); I just want to know , is there any harm in doing this? For eg:- What will happen if i try to clear cache in middle of transaction?Prussia
D
15

SessionFactory has plenty of evict() methods precisely for that purpose:

sessionFactory.evict(MyEntity.class); // remove all MyEntity instances
sessionFactory.evict(MyEntity.class, new Long(1)); // remove a particular MyEntity instances
Dzungaria answered 21/10, 2009 at 22:0 Comment(3)
Is there a reliable way to evict all instances of all classes annotated with @Cache from the second level cache. We generally annotate the classes to enable second-level cache and so there's not really one central place to update the code when a new entity is added to the second level cache.Kohn
To be honest, evicting all instances seems like rather extreme case. Do it often enough and you may end up with performance worse than with no cache at all. That said, you can get names of all entities via getAllClassMetadata() method (names are keys of returned map) and call evictEntity() for each.Dzungaria
This is only an extremely rare occasion when we need to do a production "HOTFIX" of the database without taking the servers down and our existing customer service admin tools can't do it (and we'd rather not expose some admin command that lets you execute some arbitrary SQL). Then again by allowing an outside admin to execute SQL we lose all transaction auditing and logging.Kohn
L
10

Both hibernate and JPA now provide direct access to the underlying 2nd level cache:

sessionFactory.getCache().evict(..);
entityManager.getCache().evict(..)
Luella answered 15/5, 2012 at 9:1 Comment(0)
M
5

I was searching how to invalidate all Hibernate caches and I found this useful snippet:

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

Hope it helps to someone else.

Maxson answered 11/3, 2014 at 10:3 Comment(0)
H
1

You may try doing this:

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;
}

I hope It helps.

Hick answered 28/5, 2014 at 8:57 Comment(0)
B
0

One thing to take into account when using distributed cache is that QueryCache is local, and evicting it on one node, does not evicts it from other. Another issue is - evicting Entity region without evicting Query region will cause N+1 selects,when trying to retrieve date from Query cache. Good readings on this topic here.

Bubalo answered 3/7, 2019 at 17:37 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.