Pregunta

Tenemos una aplicación que utiliza el almacenamiento en caché de segundo nivel de Hibernate para evitar los accesos a la base de datos.

Me preguntaba si hay alguna manera fácil de invalidar la caché de segundo nivel de Hibernate de la aplicación Java cuando un proceso externo como un administrador de MySQL se conectó directamente para modificar la base de datos (actualizar / insertar / eliminar).

Estamos utilizando EHCache como nuestra implementación de caché de segundo nivel.

Usamos una combinación de @Cache (use = CacheConcurrencyStrategy.READ_WRITE) y @Cache (use = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE), y no tenemos el control de concurrencia optimista habilitado con marcas de tiempo en cada entidad.

El SessionFactory contiene métodos para administrar el caché de segundo nivel:  - 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

Pero debido a que anotamos las clases de entidades individuales con @Cache, no hay un lugar central para que " confíeblemente " (por ejemplo, sin pasos manuales) agregue eso a la lista.

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

No hay una manera real para que la memoria caché de segundo nivel de Hibernate sepa que una entidad cambió en la base de datos a menos que consulte a esa entidad (de lo que la caché lo protege). Entonces, tal vez como una solución, simplemente podríamos llamar a algún método para forzar a la memoria caché de segundo nivel a desalojarlo todo (de nuevo, debido a la falta de bloqueo y al control de concurrencia, se corre el riesgo de que las transacciones en curso se lean o se actualicen los datos).

¿Fue útil?

Solución

Basado en los comentarios de ChssPly76 , aquí hay un método que desaloja a todas las entidades de la caché de segundo nivel (podemos exponer este método a los administradores a través de JMX u otras herramientas de administración):

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

Otros consejos

SessionFactory tiene un montón de Desalojar () métodos precisamente para ese propósito:

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

Hibernate y JPA ahora brindan acceso directo al caché de segundo nivel subyacente:

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

Estaba buscando cómo invalidar todos los cachés de Hibernate y encontré este fragmento de código útil:

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

Espero que ayude a alguien más.

Puedes intentar hacer esto:

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

Espero que ayude.

Una cosa que se debe tener en cuenta al usar el caché distribuido es que QueryCache es local, y al desalojarlo en un nodo, no lo desaloja de otro. Otro problema es que el desalojo de la región de la entidad sin desalojar la región de la consulta provocará que se seleccione N + 1 al intentar recuperar la fecha de la memoria caché de la consulta. Buenas lecturas sobre este tema aquí .

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top