← Назад к вопросам

Какая область видимости Cache первого уровня?

2.0 Middle🔥 101 комментариев
#ORM и Hibernate#Кэширование и NoSQL

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Cache первого уровня (L1 Cache): область видимости и механизм в Hibernate

Кеш первого уровня — это одна из ключевых особенностей Hibernate ORM. Рассмотрю его устройство, область видимости и практическое применение.

Область видимости L1 Cache

Ответ: L1 Cache привязан к SESSION (Сессия Hibernate)

Архитектура кэширования:

┌─────────────────────────────────────────────────────┐
│ L1 Cache (First-Level Cache) - SESSION SCOPE        │
│ Область видимости: внутри одной сессии Hibernate   │
├─────────────────────────────────────────────────────┤
│ - Не кэшируется между сессиями                      │
│ - Очищается при close() сессии                      │
│ - Проверяется перед SELECT запросами                │
│ - Управляется Hibernate автоматически              │
│ - Нельзя отключить                                 │
└─────────────────────────────────────────────────────┘
           ↓
┌─────────────────────────────────────────────────────┐
│ L2 Cache (Second-Level Cache) - APPLICATION SCOPE   │
│ Область видимости: на весь Application              │
│ (Redis, Memcached, EhCache и т.д.)                 │
└─────────────────────────────────────────────────────┘

Как работает L1 Cache

Пример 1: Базовый сценарий

public class FirstLevelCacheExample {
    public static void main(String[] args) {
        SessionFactory sessionFactory = new Configuration()
            .configure("hibernate.cfg.xml")
            .buildSessionFactory();
        
        // Первая сессия
        Session session1 = sessionFactory.openSession();
        Transaction tx1 = session1.beginTransaction();
        
        // Первый SELECT - идёт в БД
        User user1 = session1.get(User.class, 1L);
        System.out.println("Первый запрос: SELECT * FROM users WHERE id = 1");
        
        // Второй SELECT с тем же id - берётся из L1 Cache
        User user2 = session1.get(User.class, 1L);
        System.out.println("Второй запрос: НЕТ запроса в БД (данные из cache)");
        
        // user1 == user2 (один и тот же объект в памяти)
        System.out.println("user1 == user2: " + (user1 == user2));  // true
        
        tx1.commit();
        session1.close();  // L1 Cache очищается
        
        // Вторая сессия - новый L1 Cache
        Session session2 = sessionFactory.openSession();
        Transaction tx2 = session2.beginTransaction();
        
        // Третий SELECT - опять идёт в БД (другая сессия)
        User user3 = session2.get(User.class, 1L);
        System.out.println("Третий запрос: SELECT * FROM users WHERE id = 1 (новая сессия)");
        
        // user1 != user3 (разные объекты, но одинаковые данные)
        System.out.println("user1 == user3: " + (user1 == user3));  // false
        
        tx2.commit();
        session2.close();
    }
}

Важные вывод:

  • Кэш существует только внутри одной сессии
  • При закрытии сессии кэш очищается
  • Нельзя поделиться данными между сессиями через L1 Cache

Содержимое L1 Cache

public class CacheContentsExample {
    public static void main(String[] args) {
        Session session = sessionFactory.openSession();
        Transaction tx = session.beginTransaction();
        
        // L1 Cache пуст
        System.out.println("Количество объектов в cache: " + 
                          session.getStatistics().getEntityCount());  // 0
        
        // Загружаем пользователя
        User user = session.get(User.class, 1L);
        // L1 Cache: {User#1 -> user object}
        System.out.println("Количество объектов в cache: " + 
                          session.getStatistics().getEntityCount());  // 1
        
        // Загружаем заказ
        Order order = session.get(Order.class, 100L);
        // L1 Cache: {User#1 -> user, Order#100 -> order}
        System.out.println("Количество объектов в cache: " + 
                          session.getStatistics().getEntityCount());  // 2
        
        // Модифицируем объект (грязный статус)
        user.setEmail("new@example.com");
        // Объект остаётся в cache с пометкой "dirty"
        
        tx.commit();
        // Сохранённые объекты остаются в L1 Cache
        
        session.close();
        // L1 Cache очищается полностью
    }
}

Управление L1 Cache

Очистка вручную

public class ManualCacheClearExample {
    public static void main(String[] args) {
        Session session = sessionFactory.openSession();
        Transaction tx = session.beginTransaction();
        
        User user = session.get(User.class, 1L);
        // Объект в L1 Cache
        
        // Очистить весь L1 Cache
        session.clear();
        // Теперь user НЕ в cache, но объект java остаётся
        
        // Повторный запрос - идёт в БД
        User user2 = session.get(User.class, 1L);
        System.out.println("user == user2: " + (user == user2));  // false
        
        tx.commit();
        
        // Евicting отдельного объекта
        session.evict(user);  // Удалить конкретный объект из cache
        
        session.close();
    }
}

Refresh объекта из БД

public class RefreshFromDBExample {
    public static void main(String[] args) {
        Session session = sessionFactory.openSession();
        Transaction tx = session.beginTransaction();
        
        User user = session.get(User.class, 1L);
        System.out.println("Email: " + user.getEmail());  // john@example.com
        
        // Обновляем напрямую в БД (другой процесс)
        // UPDATE users SET email = new@example.com WHERE id = 1
        
        // В Hibernate объект остаётся старый
        System.out.println("Email в cache: " + user.getEmail());  // john@example.com
        
        // Обновляем объект из БД
        session.refresh(user);
        System.out.println("Email после refresh: " + user.getEmail());  // new@example.com
        
        tx.commit();
        session.close();
    }
}

Уровни кэширования в Hibernate

Запрос в Hibernate:
        ↓
┌──────────────────────┐
│ L1 Cache (Session)   │ ← Проверяем первый уровень
└──────────────────────┘
        ↓
Не найден? → Проверяем L2 Cache
        ↓
┌──────────────────────┐
│ L2 Cache (AppLevel)  │ ← Проверяем второй уровень
└──────────────────────┘
        ↓
Не найден? → Проверяем Query Cache
        ↓
┌──────────────────────┐
│ Query Cache          │
└──────────────────────┘
        ↓
Не найден? → SELECT из БД
        ↓
Добавляем в L1 Cache → Ответ

Spring Data и L1 Cache

@Service
public class UserService {
    @Autowired
    private UserRepository userRepository;
    
    @Transactional  // Сессия жива во время метода
    public void processUser() {
        // Транзакция открывает сессию Hibernate
        
        User user1 = userRepository.findById(1L).get();
        // SELECT выполнен, user1 в L1 Cache
        
        User user2 = userRepository.findById(1L).get();
        // SELECT НЕ выполнен (данные из L1 Cache)
        
        System.out.println(user1 == user2);  // true
        
        // Сессия закрывается, L1 Cache очищается
    }
    
    // Отдельные транзакции - отдельные сессии
    @Transactional
    public void firstTransaction() {
        User user = userRepository.findById(1L).get();  // SELECT #1
    }  // Сессия закрывается, cache очищается
    
    @Transactional
    public void secondTransaction() {
        User user = userRepository.findById(1L).get();  // SELECT #2 (cache другой)
    }
}

Проблемы и решения

Проблема 1: Stale Data (устаревшие данные)

public void staledDataExample() {
    Session session = sessionFactory.openSession();
    
    User user = session.get(User.class, 1L);
    System.out.println(user.getEmail());  // john@example.com
    
    // Другой процесс обновил в БД
    // UPDATE users SET email = new@example.com WHERE id = 1
    
    // Hibernate не знает об этом, возвращает старые данные
    System.out.println(user.getEmail());  // john@example.com (STALE!)
    
    // Решение: refresh
    session.refresh(user);
    System.out.println(user.getEmail());  // new@example.com
}

Проблема 2: Memory Leak в длительных сессиях

public void memoryLeakExample() {
    Session session = sessionFactory.openSession();
    
    for (int i = 0; i < 100_000; i++) {
        User user = session.get(User.class, (long)i);
        // L1 Cache растёт: 100k объектов в памяти
        // Может привести к OutOfMemoryError
    }
    
    session.close();
    
    // Решение: периодически очищать cache
    Session session2 = sessionFactory.openSession();
    for (int i = 0; i < 100_000; i++) {
        User user = session2.get(User.class, (long)i);
        
        if (i % 1000 == 0) {
            session2.clear();  // Очистить каждые 1000 объектов
        }
    }
    session2.close();
}

Конфигурация L1 Cache

<!-- hibernate.cfg.xml -->
<hibernate-configuration>
    <session-factory>
        <!-- L1 Cache нельзя отключить, но можно контролировать поведение -->
        
        <!-- Автоматическое управление сессией -->
        <property name="current_session_context_class">thread</property>
        
        <!-- Flush mode (как часто синхронизировать с БД) -->
        <property name="flush_before_query">true</property>
        
        <!-- Статистика (для отладки) -->
        <property name="generate_statistics">true</property>
    </session-factory>
</hibernate-configuration>

Ключевые правила L1 Cache

Помните:

  1. L1 Cache = Session Scope — привязан к одной сессии
  2. Обязателен — нельзя отключить, всегда работает
  3. Автоматический — Hibernate управляет сам
  4. Идентичность объектов — user1 == user2 в одной сессии
  5. Очищается — при close() или clear()
  6. Уменьшает запросы — SELECT один раз за сессию
  7. Thread-safe — каждый поток имеет свою сессию

Сравнение кэшей

ПараметрL1 CacheL2 CacheQuery Cache
Область видимостиSessionApplicationGlobal
УправлениеAutomaticManualManual
Может быть отключенНетДаДа
Тип хранилищаMemory (JVM)ConfigurableMemory/Disk
По умолчаниюВключенОтключенОтключен
Примеры-Redis, EhCache-

Резюме

L1 Cache имеет область видимости SESSION — это ключевая особенность, которая отличает его от L2 Cache (Application scope). Каждая сессия Hibernate имеет свой собственный L1 Cache, который очищается при закрытии сессии. Это гарантирует корректность и изоляцию данных между параллельными запросами.