Hibernate Caching Strategies

Advanced Cache Configuration with Redis, EhCache, and Hazelcast
Effective caching is crucial for high-performance Hibernate applications. This guide covers comprehensive caching strategies, from basic first-level caching to advanced distributed caching solutions with Redis, EhCache, and Hazelcast.
Hibernate Caching Architecture
Three Levels of Caching
- First Level Cache: Session-scoped, automatic, no configuration needed
- Second Level Cache: SessionFactory-scoped, configurable, shared across sessions
- Query Cache: Caches query results, requires explicit configuration
1. First Level Cache (Session Cache)
Understanding Session Cache
The first-level cache is automatically enabled and provides entity caching within a single session. Understanding its behavior is crucial for optimizing data access patterns.
Session Cache Example
@Service
@Transactional
public class ProductService {
@PersistenceContext
private EntityManager entityManager;
public Product getProduct(Long id) {
// First call - hits database
Product product1 = entityManager.find(Product.class, id);
// Second call - hits session cache
Product product2 = entityManager.find(Product.class, id);
// product1 and product2 are the same instance
assert product1 == product2;
return product1;
}
}
Session Cache Management
Cache Control Methods
@Service
@Transactional
public class CacheManagementService {
@PersistenceContext
private EntityManager entityManager;
public void clearSessionCache() {
entityManager.clear(); // Clears all cached entities
}
public void evictEntity(Class<?> entityClass, Object id) {
entityManager.getEntityManagerFactory()
.getCache()
.evict(entityClass, id);
}
public boolean isCached(Class<?> entityClass, Object id) {
return entityManager.getEntityManagerFactory()
.getCache()
.contains(entityClass, id);
}
}
2. Second Level Cache Configuration
Enabling Second Level Cache
Hibernate Configuration
# Enable second level cache
spring.jpa.properties.hibernate.cache.use_second_level_cache=true
spring.jpa.properties.hibernate.cache.use_query_cache=true
spring.jpa.properties.hibernate.cache.region.factory_class=org.hibernate.cache.jcache.JCacheRegionFactory
# Cache provider configuration
spring.jpa.properties.hibernate.javax.cache.provider=org.ehcache.jsr107.EhcacheCachingProvider
spring.jpa.properties.hibernate.javax.cache.uri=classpath:ehcache.xml
Entity Cache Configuration
Cacheable Entity
@Entity
@Table(name = "products")
@Cacheable
@Cache(usage = CacheConcurrencyStrategy.READ_WRITE, region = "product-cache")
public class Product {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false)
private String name;
@Column(nullable = false)
private BigDecimal price;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "category_id")
@Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
private Category category;
// Getters and setters
}
3. EhCache Configuration
EhCache Setup
EhCache is a popular choice for second-level caching due to its simplicity and performance characteristics.
Maven Dependencies
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-ehcache</artifactId>
<version>5.6.15.Final</version>
</dependency>
<dependency>
<groupId>org.ehcache</groupId>
<artifactId>ehcache</artifactId>
<version>3.10.8</version>
</dependency>
EhCache Configuration File
ehcache.xml
<?xml version="1.0" encoding="UTF-8"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.ehcache.org/v3"
xsi:schemaLocation="http://www.ehcache.org/v3
http://www.ehcache.org/schema/ehcache-core-3.0.xsd">
<cache-template name="default">
<key-type>java.lang.Object</key-type>
<value-type>java.lang.Object</value-type>
<expiry>
<ttl unit="minutes">30</ttl>
</expiry>
<resources>
<heap unit="entries">1000</heap>
<offheap unit="MB">10</offheap>
</resources>
</cache-template>
<cache alias="product-cache" uses-template="default">
<expiry>
<ttl unit="minutes">60</ttl>
</expiry>
<resources>
<heap unit="entries">5000</heap>
<offheap unit="MB">50</offheap>
</resources>
</cache>
<cache alias="category-cache" uses-template="default">
<expiry>
<ttl unit="hours">2</ttl>
</expiry>
<resources>
<heap unit="entries">1000</heap>
</resources>
</cache>
</config>
4. Redis Cache Configuration
Redis Setup for Distributed Caching
Redis provides excellent distributed caching capabilities for clustered applications.
Redis Dependencies
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-redis</artifactId>
<version>2.1.0</version>
</dependency>
Redis Configuration
Redis Properties
# Redis Configuration
spring.redis.host=localhost
spring.redis.port=6379
spring.redis.password=your-redis-password
spring.redis.database=0
spring.redis.timeout=2000ms
spring.redis.lettuce.pool.max-active=8
spring.redis.lettuce.pool.max-idle=8
spring.redis.lettuce.pool.min-idle=0
# Hibernate Redis Configuration
spring.jpa.properties.hibernate.cache.region.factory_class=org.hibernate.cache.redis.hibernate52.SingletonRedisRegionFactory
spring.jpa.properties.hibernate.cache.redis.host=localhost
spring.jpa.properties.hibernate.cache.redis.port=6379
spring.jpa.properties.hibernate.cache.redis.password=your-redis-password
spring.jpa.properties.hibernate.cache.redis.database=1
Redis Cache Service
Redis Cache Service
@Service
public class RedisCacheService {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
public void cacheProduct(Product product) {
String key = "product:" + product.getId();
redisTemplate.opsForValue().set(key, product, Duration.ofMinutes(30));
}
public Optional<Product> getCachedProduct(Long id) {
String key = "product:" + id;
Product product = (Product) redisTemplate.opsForValue().get(key);
return Optional.ofNullable(product);
}
public void evictProduct(Long id) {
String key = "product:" + id;
redisTemplate.delete(key);
}
public void evictAllProducts() {
Set<String> keys = redisTemplate.keys("product:*");
if (keys != null && !keys.isEmpty()) {
redisTemplate.delete(keys);
}
}
}
5. Hazelcast Cache Configuration
Hazelcast Setup
Hazelcast Dependencies
<dependency>
<groupId>com.hazelcast</groupId>
<artifactId>hazelcast</artifactId>
<version>5.3.0</version>
</dependency>
<dependency>
<groupId>com.hazelcast</groupId>
<artifactId>hazelcast-hibernate53</artifactId>
<version>2.2.1</version>
</dependency>
Hazelcast Configuration
hazelcast.xml
<?xml version="1.0" encoding="UTF-8"?>
<hazelcast xmlns="http://www.hazelcast.com/schema/config"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.hazelcast.com/schema/config
http://www.hazelcast.com/schema/config/hazelcast-config-5.3.xsd">
<network>
<port auto-increment="true">5701</port>
<join>
<multicast enabled="true">
<multicast-group>224.2.2.3</multicast-group>
<multicast-port>54327</multicast-port>
</multicast>
</join>
</network>
<map name="product-cache">
<time-to-live-seconds>3600</time-to-live-seconds>
<max-idle-seconds>1800</max-idle-seconds>
<eviction eviction-policy="LRU" max-size-policy="PER_NODE" size="10000"/>
<backup-count>1</backup-count>
<async-backup-count>0</async-backup-count>
</map>
</hazelcast>
6. Query Cache Configuration
Enabling Query Cache
Query Cache Setup
# Enable query cache
spring.jpa.properties.hibernate.cache.use_query_cache=true
# Query cache configuration
spring.jpa.properties.hibernate.cache.query_cache_factory=org.hibernate.cache.StandardQueryCacheFactory
Cached Queries
Cacheable Queries
@Repository
public interface ProductRepository extends JpaRepository<Product, Long> {
@Query("SELECT p FROM Product p WHERE p.category.name = :categoryName")
@QueryHints({
@QueryHint(name = "org.hibernate.cacheable", value = "true"),
@QueryHint(name = "org.hibernate.cacheRegion", value = "product-query-cache")
})
List<Product> findByCategoryName(@Param("categoryName") String categoryName);
@Query("SELECT p FROM Product p WHERE p.price BETWEEN :minPrice AND :maxPrice")
@QueryHints({
@QueryHint(name = "org.hibernate.cacheable", value = "true"),
@QueryHint(name = "org.hibernate.cacheRegion", value = "product-query-cache")
})
List<Product> findByPriceRange(@Param("minPrice") BigDecimal minPrice,
@Param("maxPrice") BigDecimal maxPrice);
}
7. Cache Monitoring and Management
Cache Statistics
Cache Statistics Service
@Service
public class CacheStatisticsService {
@Autowired
private SessionFactory sessionFactory;
public Map<String, Object> getCacheStatistics() {
Statistics stats = sessionFactory.getStatistics();
Map<String, Object> cacheStats = new HashMap<>();
// Second level cache statistics
cacheStats.put("secondLevelCacheHitCount", stats.getSecondLevelCacheHitCount());
cacheStats.put("secondLevelCacheMissCount", stats.getSecondLevelCacheMissCount());
cacheStats.put("secondLevelCachePutCount", stats.getSecondLevelCachePutCount());
// Query cache statistics
cacheStats.put("queryCacheHitCount", stats.getQueryCacheHitCount());
cacheStats.put("queryCacheMissCount", stats.getQueryCacheMissCount());
cacheStats.put("queryCachePutCount", stats.getQueryCachePutCount());
// Calculate hit ratios
long totalSecondLevelRequests = stats.getSecondLevelCacheHitCount() +
stats.getSecondLevelCacheMissCount();
if (totalSecondLevelRequests > 0) {
double secondLevelHitRatio = (double) stats.getSecondLevelCacheHitCount() /
totalSecondLevelRequests;
cacheStats.put("secondLevelCacheHitRatio", secondLevelHitRatio);
}
return cacheStats;
}
}
Cache Management Endpoints
Cache Management Controller
@RestController
@RequestMapping("/api/cache")
public class CacheManagementController {
@Autowired
private CacheStatisticsService cacheStatisticsService;
@Autowired
private RedisCacheService redisCacheService;
@GetMapping("/statistics")
public ResponseEntity<Map<String, Object>> getCacheStatistics() {
return ResponseEntity.ok(cacheStatisticsService.getCacheStatistics());
}
@PostMapping("/evict/product/{id}")
public ResponseEntity<String> evictProduct(@PathVariable Long id) {
redisCacheService.evictProduct(id);
return ResponseEntity.ok("Product cache evicted successfully");
}
@PostMapping("/evict/all")
public ResponseEntity<String> evictAllCache() {
redisCacheService.evictAllProducts();
return ResponseEntity.ok("All cache evicted successfully");
}
}
8. Cache Best Practices
Caching Best Practices
- Cache Read-Heavy Data: Focus on frequently accessed, rarely changed data
- Set Appropriate TTL: Balance between performance and data freshness
- Monitor Cache Hit Ratios: Aim for > 80% hit ratio
- Use Cache Regions: Organize cache by data type and access patterns
- Implement Cache Warming: Pre-load frequently accessed data
- Handle Cache Failures: Implement fallback strategies
- Regular Cache Cleanup: Remove stale or unused cache entries
Cache Warming Strategy
Cache Warming Service
@Service
public class CacheWarmingService {
@Autowired
private ProductRepository productRepository;
@Autowired
private RedisCacheService redisCacheService;
@EventListener(ApplicationReadyEvent.class)
public void warmUpCache() {
// Warm up frequently accessed products
List<Product> popularProducts = productRepository.findTop10ByOrderByViewCountDesc();
popularProducts.forEach(redisCacheService::cacheProduct);
// Warm up categories
List<Category> categories = categoryRepository.findAll();
categories.forEach(category -> {
String key = "category:" + category.getId();
redisTemplate.opsForValue().set(key, category, Duration.ofHours(2));
});
}
}
Cache Performance Tuning
Performance Optimization Tips
- Memory Allocation: Allocate 20-30% of heap for cache
- Eviction Policies: Use LRU for most use cases
- Serialization: Use efficient serialization (Kryo, Protobuf)
- Network Optimization: Use compression for distributed caches
- Connection Pooling: Configure appropriate pool sizes
- Monitoring: Set up alerts for cache performance metrics