Example Spring Boot + Hibernate + Ehcache project for demonstration purposes of cache mechanism. Detailed explanations here
To run application:
mvn package && java -jar target\company-structure-hibernate-cache-1.0-SNAPSHOT.jar
- Java 8
- Maven
- H2/PostgreSQL
It is possible to run application in one of two profiles:
- h2
- postgres
depending on database engine chose for testing.
To enable cache statistics dev
profile needs to be turned on.
Cache regions are configured via ehcache.xml file. Following regions are defined:
- companies by id
<cache name="company.byId"
maxElementsInMemory="10000" eternal="false" timeToIdleSeconds="600"
timeToLiveSeconds="3600" overflowToDisk="true"/>
- companies by name
<cache name="company.byName"
maxElementsInMemory="10000" eternal="false" timeToIdleSeconds="600"
timeToLiveSeconds="3600" overflowToDisk="true"/>
To enable cache mechanism @EnableCaching
annotation is added to configuration class. Cache beans are created via annotation:
@Configuration
@EnableCaching(mode = AdviceMode.ASPECTJ)
public class CacheConfiguration {
@Bean
public EhCacheManagerFactoryBean ehCacheManagerFactory() {
EhCacheManagerFactoryBean cacheManagerFactoryBean = new EhCacheManagerFactoryBean();
cacheManagerFactoryBean.setConfigLocation(new ClassPathResource("ehcache.xml"));
cacheManagerFactoryBean.setShared(true);
return cacheManagerFactoryBean;
}
@Bean
public EhCacheCacheManager ehCacheCacheManager() {
EhCacheCacheManager cacheManager = new EhCacheCacheManager();
cacheManager.setCacheManager(ehCacheManagerFactory().getObject());
cacheManager.setTransactionAware(true);
return cacheManager;
}
}
Caching rules are defined in CompanyRepository
implementation:
- cache creation
@Cacheable(value = "company.byId", key = "#id", unless = "#result != null and #result.name.toUpperCase().startsWith('TEST')")
public Company find(Long id) {
CriteriaBuilder builder = entityManager.getCriteriaBuilder();
CriteriaQuery<Company> query = builder.createQuery(Company.class);
Root<Company> root = query.from(Company.class);
root.fetch(Company_.cars, JoinType.LEFT);
Fetch<Company, Department> departmentFetch = root.fetch(Company_.departments, JoinType.LEFT);
Fetch<Department, Employee> employeeFetch = departmentFetch.fetch(Department_.employees, JoinType.LEFT);
employeeFetch.fetch(Employee_.address, JoinType.LEFT);
departmentFetch.fetch(Department_.offices, JoinType.LEFT);
query.select(root).distinct(true);
Predicate idPredicate = builder.equal(root.get(Company_.id), id);
query.where(builder.and(idPredicate));
return DataAccessUtils.singleResult(entityManager.createQuery(query).getResultList());
}
@Cacheable(value = "company.byName", key = "#name", unless = "#name != null and #name.toUpperCase().startsWith('TEST')")
public Company find(String name) {
CriteriaBuilder builder = entityManager.getCriteriaBuilder();
CriteriaQuery<Company> query = builder.createQuery(Company.class);
Root<Company> root = query.from(Company.class);
root.fetch(Company_.cars, JoinType.LEFT);
Fetch<Company, Department> departmentFetch = root.fetch(Company_.departments, JoinType.LEFT);
Fetch<Department, Employee> employeeFetch = departmentFetch.fetch(Department_.employees, JoinType.LEFT);
employeeFetch.fetch(Employee_.address, JoinType.LEFT);
departmentFetch.fetch(Department_.offices, JoinType.LEFT);
query.select(root).distinct(true);
Predicate idPredicate = builder.equal(root.get(Company_.name), name);
query.where(builder.and(idPredicate));
return DataAccessUtils.singleResult(entityManager.createQuery(query).getResultList());
}
- cache invalidation
@Caching(evict = {@CacheEvict(value = "company.byId", key = "#company.id"), @CacheEvict(value = "company.byName", key = "#company.name")})
public void delete(Company company) {
entityManager.remove(company);
}
- cache update
@Caching(evict = {@CacheEvict(value = "company.byName", allEntries = true), @CacheEvict(value = "company.byId", key = "#result.id", condition = "#result != null and #result.name.toUpperCase().startsWith('TEST')")},
put = {@CachePut(value = "company.byId", key = "#result.id", unless = "#result != null and #result.name.toUpperCase().startsWith('TEST')")})
public Company update(Company company) {
return entityManager.merge(company);
}
Optionally it's possible to expose MBean to gather cache statistics:
@Bean
public MBeanServer mBeanServer() {
MBeanServer mBeanServer = ManagementFactory.getPlatformMBeanServer();
return mBeanServer;
}
@Bean
public ManagementService managementService() {
ManagementService managementService = new ManagementService(ehCacheCacheManager.getCacheManager(), mBeanServer(), true, true, true, true);
managementService.init();
return managementService;
}