Ehcache для Java: полное руководство с примерами

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Ehcache для Java: Полное руководство по кэшированию

Ehcache — это мощная, широко используемая библиотека кэширования для Java с открытым исходным кодом. Она предоставляет разработчикам простой, но высокопроизводительный API для временного хранения данных, что позволяет значительно ускорить работу приложений за счёт сокращения времени доступа к медленным источникам данных, таким как базы данных, внешние API или файловые системы. Ehcache является де-факто стандартом кэширования в экосистеме Java и используется в таких крупных проектах, как Hibernate (в качестве провайдера кэша второго уровня), Spring Framework и многих других.

Зачем нужна библиотека Ehcache? Основная цель — повышение производительности приложения. Когда данные извлекаются из базы данных или внешнего сервиса, каждый запрос требует времени на выполнение сетевого вызова, дискового ввода-вывода или сложных вычислений. Ehcache позволяет сохранить результат первого запроса в оперативной памяти (или на диске) и при повторном обращении к тем же данным мгновенно вернуть их из кэша. Это снижает нагрузку на базы данных, уменьшает задержки ответа и улучшает пользовательский опыт. Кроме того, Ehcache предоставляет механизмы для управления жизненным циклом кэша: автоматическое удаление устаревших записей (TTL, TTI), стратегии вытеснения данных при переполнении памяти (LRU, LFU, FIFO) и поддержку распределённого кэширования в кластерных средах.

Ключевые возможности Ehcache включают: кэширование в памяти и на диске, поддержку транзакций, интеграцию с Jakarta EE, JMX мониторинг, настройку через XML или программно, а также поддержку распределённого кэширования через Terracotta. Библиотека постоянно развивается: начиная с версии 3.x, Ehcache предлагает современный, типобезопасный API с поддержкой Java Generics, что делает код более чистым и надёжным.

Установка

Ehcache распространяется через Maven Central. Для использования в вашем проекте добавьте зависимость в файл pom.xml (для Maven) или build.gradle (для Gradle).

Maven

<dependency>
    <groupId>org.ehcache</groupId>
    <artifactId>ehcache</artifactId>
    <version>3.10.8</version> <!-- Проверьте актуальную версию на Maven Central -->
</dependency>

Gradle

implementation 'org.ehcache:ehcache:3.10.8'

Если вы используете более старую версию Ehcache 2.x, зависимость будет выглядеть так:

<dependency>
    <groupId>net.sf.ehcache</groupId>
    <artifactId>ehcache</artifactId>
    <version>2.10.9.2</version>
</dependency>

В этой статье мы будем использовать Ehcache 3.x, так как это современная и рекомендуемая версия.

Быстрый старт: минимальный рабочий пример

Создадим простой кэш, который хранит пары "ключ-значение" (String, String).

import org.ehcache.Cache;
import org.ehcache.CacheManager;
import org.ehcache.config.builders.CacheConfigurationBuilder;
import org.ehcache.config.builders.CacheManagerBuilder;
import org.ehcache.config.builders.ResourcePoolsBuilder;

public class EhcacheQuickStart {
    public static void main(String[] args) {
        // 1. Создаём менеджер кэша
        CacheManager cacheManager = CacheManagerBuilder.newCacheManagerBuilder()
                .withCache("myCache",
                        CacheConfigurationBuilder.newCacheConfigurationBuilder(
                                String.class,   // Тип ключа
                                String.class,   // Тип значения
                                ResourcePoolsBuilder.heap(10)) // 10 записей в памяти
                )
                .build();

        // 2. Инициализируем менеджер
        cacheManager.init();

        // 3. Получаем кэш по имени
        Cache<String, String> myCache = cacheManager.getCache("myCache", String.class, String.class);

        // 4. Работаем с кэшем
        myCache.put("key1", "Hello, Ehcache!");
        String value = myCache.get("key1");
        System.out.println("Извлечено из кэша: " + value); // Выведет: Извлечено из кэша: Hello, Ehcache!

        // 5. Закрываем менеджер (освобождаем ресурсы)
        cacheManager.close();
    }
}

Этот пример демонстрирует базовый жизненный цикл: создание CacheManager, конфигурация кэша, добавление и получение элемента, закрытие менеджера.

Основные классы и методы

Рассмотрим ключевые классы и методы Ehcache 3.x. Все примеры предполагают импорт соответствующих пакетов (org.ehcache.*).

1. CacheManagerBuilder — построитель менеджера кэша

Сигнатура: CacheManagerBuilder<T> newCacheManagerBuilder()

Что делает: Создаёт экземпляр строителя для конфигурации и создания CacheManager. CacheManager управляет жизненным циклом всех кэшей в приложении.

CacheManager cacheManager = CacheManagerBuilder.newCacheManagerBuilder()
        .withCache("users", 
            CacheConfigurationBuilder.newCacheConfigurationBuilder(Long.class, User.class, ResourcePoolsBuilder.heap(100)))
        .build(true); // true = автоматически инициализировать
cacheManager.close();

2. CacheConfigurationBuilder — конфигурация отдельного кэша

Сигнатура: CacheConfigurationBuilder<K, V> newCacheConfigurationBuilder(Class<K> keyType, Class<V> valueType, ResourcePools resourcePools)

Что делает: Определяет конфигурацию для конкретного кэша: типы ключа и значения, пулы ресурсов (heap, offheap, disk), политики истечения срока действия.

CacheConfigurationBuilder<String, Integer> configBuilder = 
    CacheConfigurationBuilder.newCacheConfigurationBuilder(
        String.class, 
        Integer.class, 
        ResourcePoolsBuilder.heap(50) // 50 элементов в куче
    )
    .withExpiry(Expirations.timeToLiveExpiration(Duration.ofMinutes(10))); // TTL 10 минут

CacheManager manager = CacheManagerBuilder.newCacheManagerBuilder()
    .withCache("scores", configBuilder)
    .build(true);

3. Cache.get(K key) — получение значения по ключу

Сигнатура: V get(K key)

Что делает: Возвращает значение, связанное с указанным ключом, или null, если ключ отсутствует в кэше.

Cache<String, String> cache = ...;
String value = cache.get("user_123");
if (value != null) {
    System.out.println("Найдено: " + value);
} else {
    System.out.println("Ключ не найден");
}

4. Cache.put(K key, V value) — добавление или обновление записи

Сигнатура: void put(K key, V value)

Что делает: Помещает значение в кэш по указанному ключу. Если ключ уже существует, значение перезаписывается.

Cache<Long, String> cache = ...;
cache.put(1L, "Alice");
cache.put(2L, "Bob");
cache.put(1L, "Alice Updated"); // Перезапись

5. Cache.remove(K key) — удаление записи

Сигнатура: boolean remove(K key)

Что делает: Удаляет запись по ключу. Возвращает true, если запись существовала и была удалена, иначе false.

Cache<Integer, String> cache = ...;
cache.put(1, "One");
boolean removed = cache.remove(1);
System.out.println("Удалено: " + removed); // true
boolean removedAgain = cache.remove(1);
System.out.println("Удалено снова: " + removedAgain); // false

6. Cache.containsKey(K key) — проверка наличия ключа

Сигнатура: boolean containsKey(K key)

Что делает: Проверяет, существует ли запись с указанным ключом в кэше, не извлекая значение.

Cache<String, String> cache = ...;
cache.put("session_1", "data");
if (cache.containsKey("session_1")) {
    System.out.println("Сессия активна");
} else {
    System.out.println("Сессия истекла или не существует");
}

7. Cache.clear() — очистка кэша

Сигнатура: void clear()

Что делает: Удаляет все записи из кэша. Кэш остаётся активным и готовым к использованию.

Cache<String, String> cache = ...;
cache.put("a", "1");
cache.put("b", "2");
cache.clear();
System.out.println("Размер кэша после очистки: " + cache.get("a")); // null

8. Cache.iterator() — итерация по записям

Сигнатура: Iterator<Cache.Entry<K, V>> iterator()

Что делает: Возвращает итератор по всем записям в кэше. Позволяет перебрать все пары ключ-значение.

Cache<String, Integer> cache = ...;
cache.put("apple", 5);
cache.put("banana", 3);
cache.put("orange", 8);

for (Cache.Entry<String, Integer> entry : cache) {
    System.out.println(entry.getKey() + " -> " + entry.getValue());
}
// Вывод (порядок не гарантирован):
// apple -> 5
// banana -> 3
// orange -> 8

9. Cache.putIfAbsent(K key, V value) — атомарная вставка

Сигнатура: V putIfAbsent(K key, V value)

Что делает: Помещает значение в кэш, только если указанный ключ отсутствует. Возвращает старое значение (или null, если вставка прошла успешно). Атомарная операция.

Cache<String, String> cache = ...;
String old = cache.putIfAbsent("lock", "acquired");
if (old == null) {
    System.out.println("Успешно захватили блокировку");
} else {
    System.out.println("Блокировка уже занята: " + old);
}

10. Cache.replace(K key, V oldValue, V newValue) — условная замена

Сигнатура: boolean replace(K key, V oldValue, V newValue)

Что делает: Заменяет значение для ключа только если текущее значение равно oldValue. Возвращает true при успешной замене. Полезно для оптимистичных блокировок.

Cache<String, Integer> cache = ...;
cache.put("counter", 10);
boolean replaced = cache.replace("counter", 10, 11);
System.out.println("Счётчик увеличен: " + replaced); // true
boolean failed = cache.replace("counter", 10, 12); // Текущее значение 11, а не 10
System.out.println("Не удалось: " + failed); // false

Реальный практический пример: кэширование данных из базы данных

Рассмотрим типичный сценарий: у нас есть сервис, который получает данные о пользователях из базы данных. Без кэширования каждый запрос к сервису приводит к SQL-запросу. С Ehcache мы кэшируем результаты, чтобы ускорить повторные обращения.

import org.ehcache.Cache;
import org.ehcache.CacheManager;
import org.ehcache.config.builders.*;
import org.ehcache.config.units.MemoryUnit;
import org.ehcache.expiry.Duration;
import org.ehcache.expiry.Expirations;

import java.util.Objects;
import java.util.concurrent.TimeUnit;

// Модель пользователя
class User {
    private final long id;
    private final String name;
    private final String email;

    public User(long id, String name, String email) {
        this.id = id;
        this.name = name;
        this.email = email;
    }

    // Геттеры, equals, hashCode, toString
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        User user = (User) o;
        return id == user.id;
    }

    @Override
    public int hashCode() {
        return Objects.hash(id);
    }

    @Override
    public String toString() {
        return "User{id=" + id + ", name='" + name + "', email='" + email + "'}";
    }
}

// Имитация доступа к базе данных
class DatabaseService {
    public User getUserById(long userId) {
        // Симуляция медленного запроса к БД
        try {
            TimeUnit.MILLISECONDS.sleep(200); // 200 мс задержка
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
        // В реальном приложении здесь был бы JDBC/Hibernate запрос
        System.out.println("[DB] Запрос пользователя с ID: " + userId);
        return new User(userId, "User_" + userId, "user" + userId + "@example.com");
    }
}

// Сервис с кэшированием
class UserService {
    private final DatabaseService databaseService;
    private final Cache<Long, User> userCache;

    public UserService(DatabaseService databaseService, CacheManager cacheManager) {
        this.databaseService = databaseService;
        // Получаем или создаём кэш для пользователей
        this.userCache = cacheManager.getCache("users", Long.class, User.class);
        if (this.userCache == null) {
            throw new IllegalStateException("Кэш 'users' не найден в CacheManager");
        }
    }

    public User getUserById(long userId) {
        // 1. Проверяем кэш
        User cachedUser = userCache                    

Рекомендаций