Second Level Cache

Cache ConceptDatabaseORMHibernateJPAPerformance

GitHub Overview

hooopo/second_level_cache

Write Through and Read Through caching library inspired by CacheMoney and cache_fu, support ActiveRecord 4, 5 and 6.

Stars395
Watchers15
Forks90
Created:November 7, 2011
Language:Ruby
License:MIT License

Topics

activerecordactiverecord5cachecache-moneyrailsrails5rails6

Star History

hooopo/second_level_cache Star History
Data as of: 10/22/2025, 08:07 AM

Cache Concept

Second Level Cache

Overview

Second Level Cache is a caching mechanism in ORM frameworks that is shared at the SessionFactory scope, enabling entity data sharing across multiple sessions and reducing database access.

Details

Second Level Cache is one of the data caching components available in object-relational mapping (ORM) libraries like Hibernate. It's separate from first-level cache and is globally available within the SessionFactory scope. Since cached data is shared across sessions, all sessions/users can benefit from cached data, even for data inserted by another session, as long as they use the same SessionFactory. Data persists even after sessions close, and all associated caches and cache managers terminate only when the SessionFactory is closed.

Pros and Cons

Pros

  • Cross-Session Sharing: Cache data can be shared across multiple sessions
  • Reduced Database Load: Minimizes DB access for frequently accessed data
  • Improved Application Performance: Especially effective for high-volume web applications
  • Clustering Support: Sharing across multiple application instances
  • Flexible Configuration: Can enable/disable per entity
  • TTL Support: Can set expiration times for cache entries

Cons

  • Data Consistency Risk: Direct database updates don't synchronize with cache
  • Increased Memory Usage: High memory consumption from large cache data
  • Configuration Complexity: Requires proper concurrency strategy selection
  • Debugging Difficulty: Hard to identify cache-related issues
  • Performance Impact: May sometimes decrease performance

Key Links

Usage Examples

Enabling Second Level Cache in Hibernate

<!-- hibernate.cfg.xml -->
<hibernate-configuration>
    <session-factory>
        <!-- Enable second level cache -->
        <property name="hibernate.cache.use_second_level_cache">true</property>
        
        <!-- Enable query cache -->
        <property name="hibernate.cache.use_query_cache">true</property>
        
        <!-- Specify cache provider (EhCache) -->
        <property name="hibernate.cache.region.factory_class">
            org.hibernate.cache.ehcache.EhCacheRegionFactory
        </property>
        
        <!-- Enable statistics -->
        <property name="hibernate.generate_statistics">true</property>
    </session-factory>
</hibernate-configuration>

Cache Configuration in Entity

import org.hibernate.annotations.Cache;
import org.hibernate.annotations.CacheConcurrencyStrategy;

@Entity
@Table(name = "users")
@Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class User {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @Column(name = "username")
    private String username;
    
    @Column(name = "email")
    private String email;
    
    // Cache for related entities
    @OneToMany(mappedBy = "user", fetch = FetchType.LAZY)
    @Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
    private Set<Order> orders = new HashSet<>();
    
    // getter/setter methods
}

Programmatic Cache Operations

import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.cache.spi.CacheImplementor;

public class UserService {
    
    private SessionFactory sessionFactory;
    
    public User findUser(Long id) {
        try (Session session = sessionFactory.openSession()) {
            // 1. Check first level cache
            // 2. Check second level cache
            // 3. Load from database (if necessary)
            return session.get(User.class, id);
        }
    }
    
    public void evictUserFromCache(Long id) {
        // Remove specific entity from cache
        sessionFactory.getCache().evict(User.class, id);
    }
    
    public void evictAllUsers() {
        // Remove all User entities from cache
        sessionFactory.getCache().evict(User.class);
    }
    
    public void clearAllCaches() {
        // Clear all caches
        sessionFactory.getCache().evictAllRegions();
    }
}

Using Query Cache

public class UserRepository {
    
    public List<User> findActiveUsers() {
        try (Session session = sessionFactory.openSession()) {
            return session.createQuery(
                "FROM User u WHERE u.active = true", User.class)
                .setCacheable(true)  // Enable query cache
                .setCacheRegion("activeUsers")  // Specify cache region
                .getResultList();
        }
    }
    
    public List<User> findUsersByRole(String role) {
        try (Session session = sessionFactory.openSession()) {
            return session.createQuery(
                "FROM User u WHERE u.role = :role", User.class)
                .setParameter("role", role)
                .setCacheable(true)
                .getResultList();
        }
    }
}

EhCache Configuration File

<!-- ehcache.xml -->
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:noNamespaceSchemaLocation="ehcache.xsd">

    <!-- Default cache configuration -->
    <defaultCache
        maxElementsInMemory="10000"
        eternal="false"
        timeToIdleSeconds="120"
        timeToLiveSeconds="120"
        overflowToDisk="true"
        diskPersistent="false"
        diskExpiryThreadIntervalSeconds="120"/>

    <!-- Cache for User entity -->
    <cache name="com.example.model.User"
           maxElementsInMemory="5000"
           eternal="false"
           timeToIdleSeconds="300"
           timeToLiveSeconds="600"
           overflowToDisk="false"/>

    <!-- Query result cache -->
    <cache name="org.hibernate.cache.internal.StandardQueryCache"
           maxElementsInMemory="1000"
           eternal="false"
           timeToLiveSeconds="120"/>

    <!-- Query cache metadata -->
    <cache name="org.hibernate.cache.spi.UpdateTimestampsCache"
           maxElementsInMemory="5000"
           eternal="true"/>
</ehcache>

Spring Boot Configuration

@Configuration
@EnableCaching
public class CacheConfig {
    
    @Bean
    public CacheManager cacheManager() {
        EhCacheCacheManager cacheManager = new EhCacheCacheManager();
        cacheManager.setCacheManager(ehCacheManagerFactory().getObject());
        return cacheManager;
    }
    
    @Bean
    public EhCacheManagerFactoryBean ehCacheManagerFactory() {
        EhCacheManagerFactoryBean factory = new EhCacheManagerFactoryBean();
        factory.setConfigLocation(new ClassPathResource("ehcache.xml"));
        factory.setShared(true);
        return factory;
    }
}

@Service
public class UserService {
    
    @Cacheable(value = "users", key = "#id")
    public User findById(Long id) {
        // Database loading process
        return userRepository.findById(id);
    }
    
    @CacheEvict(value = "users", key = "#user.id")
    public User updateUser(User user) {
        return userRepository.save(user);
    }
    
    @CacheEvict(value = "users", allEntries = true)
    public void clearUserCache() {
        // Clear all user cache
    }
}

Cache Statistics and Monitoring

public class CacheMonitoringService {
    
    private SessionFactory sessionFactory;
    
    public void printCacheStatistics() {
        Statistics stats = sessionFactory.getStatistics();
        
        System.out.println("=== Cache Statistics ===");
        System.out.println("Second level cache hits: " + 
            stats.getSecondLevelCacheHitCount());
        System.out.println("Second level cache misses: " + 
            stats.getSecondLevelCacheMissCount());
        System.out.println("Second level cache puts: " + 
            stats.getSecondLevelCachePutCount());
        
        System.out.println("Query cache hits: " + 
            stats.getQueryCacheHitCount());
        System.out.println("Query cache misses: " + 
            stats.getQueryCacheMissCount());
        
        // Statistics by region
        String[] regionNames = stats.getSecondLevelCacheRegionNames();
        for (String regionName : regionNames) {
            SecondLevelCacheStatistics regionStats = 
                stats.getSecondLevelCacheStatistics(regionName);
            System.out.println("Region " + regionName + ":");
            System.out.println("  Hits: " + regionStats.getHitCount());
            System.out.println("  Misses: " + regionStats.getMissCount());
            System.out.println("  Elements: " + regionStats.getElementCountInMemory());
        }
    }
    
    public double getCacheHitRatio() {
        Statistics stats = sessionFactory.getStatistics();
        long hits = stats.getSecondLevelCacheHitCount();
        long misses = stats.getSecondLevelCacheMissCount();
        
        if (hits + misses == 0) {
            return 0.0;
        }
        
        return (double) hits / (hits + misses);
    }
}