Hibernate 2-й уровень кеша, когда другой процесс изменяет базу данных

StackOverflow https://stackoverflow.com/questions/1603846

Вопрос

У нас есть приложение, которое использует кэширование 2-го уровня Hibernate, чтобы избежать попаданий в базу данных.

Мне было интересно, есть ли какой-нибудь простой способ аннулировать кэш 2-го уровня приложения Java, когда внешний процесс, такой как администратор MySQL, напрямую подключен для изменения базы данных (обновление / вставка / удаление).

Мы используем EHCache в качестве реализации кэша 2-го уровня.

Мы используем сочетание @Cache (использовании = CacheConcurrencyStrategy.READ_WRITE) и @Cache (использовании = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE), и у нас не включено управление оптимистическим параллелизмом с использованием временных меток для каждой сущности.

SessionFactory содержит методы для управления кешем 2-го уровня:  - Управление кэшами

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 узнать, что сущность изменилась в БД, если она не запрашивает эту сущность (от чего кеш защищает вас). Поэтому, возможно, в качестве решения мы могли бы просто вызвать какой-либо метод, чтобы заставить кэш второго уровня исключать все (опять же из-за отсутствия блокировки и контроля параллелизма вы рискуете при выполнении транзакций из-за «чтения» или обновления устаревших данных).

Это было полезно?

Решение

На основе комментариев 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 содержит множество evict () методы именно для этой цели:

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

И hibernate, и JPA теперь предоставляют прямой доступ к базовому кешу 2-го уровня:

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

Я искал, как сделать недействительными все кэши Hibernate, и нашел этот полезный фрагмент:

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

Надеюсь, это поможет кому-то еще.

Вы можете попробовать сделать это:

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

Надеюсь, это поможет.

При использовании распределенного кэша необходимо учитывать, что QueryCache является локальным, и его удаление на одном узле не исключает его на другом. Другая проблема заключается в том, что удаление области Entity без исключения области Query приведет к выбору N + 1 при попытке получить дату из кэша Query. Хорошие чтения по этой теме здесь .

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top