Ehcache Implementation for Database Operations in Spring Boot

Alexander Ang
7 min readDec 5, 2019

Prerequisite: https://medium.com/@alexanderang.24/spring-boot-cache-implementation-for-database-operations-cf846d6d8e3c

Dependency needed on pom.xml (+ from prerequisite):

<dependency>
<groupId>javax.cache</groupId>
<artifactId>cache-api</artifactId>
<version>1.1.0</version>
</dependency>
<dependency>
<groupId>org.ehcache</groupId>
<artifactId>ehcache</artifactId>
<version>3.6.2</version>
</dependency>

ehcache.xml (put this on resources folder)

<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.ehcache.org/v3"
xmlns:jsr107="http://www.ehcache.org/v3/jsr107"
xsi:schemaLocation="
http://www.ehcache.org/v3 http://www.ehcache.org/schema/ehcache-core-3.0.xsd
http://www.ehcache.org/v3/jsr107 http://www.ehcache.org/schema/ehcache-107-ext-3.0.xsd">
<cache alias="findAllCache">
<expiry>
<ttl unit="seconds">5</ttl>
</expiry>
<listeners>
<listener>
<class>com.example.demo.config.CacheEventLogger</class>
<event-firing-mode>ASYNCHRONOUS</event-firing-mode>
<event-ordering-mode>UNORDERED</event-ordering-mode>
<events-to-fire-on>CREATED</events-to-fire-on>
<events-to-fire-on>EXPIRED</events-to-fire-on>
</listener>
</listeners>
<resources>
<heap unit="entries">2</heap>
<offheap unit="MB">10</offheap>
</resources>
</cache>
<cache alias="findByIdCache">
<!-- <key-type>java.lang.Long</key-type>-->
<!-- <value-type>com.example.demo.dto.PeopleDto</value-type>-->
<expiry>
<tti unit="seconds">5</tti>
</expiry>
<listeners>
<listener>
<class>com.example.demo.config.CacheEventLogger</class>
<event-firing-mode>ASYNCHRONOUS</event-firing-mode>
<event-ordering-mode>UNORDERED</event-ordering-mode>
<events-to-fire-on>CREATED</events-to-fire-on>
<events-to-fire-on>EVICTED</events-to-fire-on>
<events-to-fire-on>EXPIRED</events-to-fire-on>
<events-to-fire-on>REMOVED</events-to-fire-on>
<events-to-fire-on>UPDATED</events-to-fire-on>
</listener>
</listeners>
<resources>
<heap unit="entries">2</heap>
<offheap unit="MB">50</offheap>
</resources>
</cache>
</config>

PeopleDto needs to implements Serializable. Objects have to be represented as byte arrays to be stored on disk and in Java the only standard way of doing that is Serialization.

A little changes in PeopleService’s caching annotation. Added cache keys and null result validations.

Added new class to enable ehcache logging.

To set ehcache.xml as the cache configuration file, add this line to application.properties:

spring.cache.jcache.config=classpath:ehcache.xml

Ehcache implementation is simulated using API hit steps (with system log below separated by new line gap):

  1. /all (no cache)
  2. /all (cached)
  3. /all (cache expired because time to live (ttl) is set to 5 seconds on xml configuration)
2019-12-04 17:43:47.455 TRACE 10832 --- [nio-8080-exec-4] o.s.cache.interceptor.CacheInterceptor   : Computed cache key 'SimpleKey []' for operation Builder[public java.util.List com.example.demo.service.PeopleService.findAll()] caches=[findAllCache] | key='' | keyGenerator='' | cacheManager='' | cacheResolver='' | condition='' | unless='#result == null' | sync='false'2019-12-04 17:43:47.455 TRACE 10832 --- [nio-8080-exec-4] o.s.cache.interceptor.CacheInterceptor   : No cache entry for key 'SimpleKey []' in cache(s) [findAllCache]2019-12-04 17:43:47.455 TRACE 10832 --- [nio-8080-exec-4] o.s.cache.interceptor.CacheInterceptor   : Computed cache key 'SimpleKey []' for operation Builder[public java.util.List com.example.demo.service.PeopleService.findAll()] caches=[findAllCache] | key='' | keyGenerator='' | cacheManager='' | cacheResolver='' | condition='' | unless='#result == null' | sync='false'2019-12-04 17:43:47.476  INFO 10832 --- [e [_default_]-4] c.example.demo.config.CacheEventLogger   : Cache event CREATED for item with key SimpleKey []. Old value = null, New value = [PeopleDto(id=93, firstName=Sar, lastName=Joko), PeopleDto(id=94, firstName=Sar, lastName=Joko), PeopleDto(id=95, firstName=Sar, lastName=Jokowi), PeopleDto(id=96, firstName=Sarjoko, lastName=wi)]
2019-12-04 17:43:48.999 TRACE 10832 --- [nio-8080-exec-5] o.s.cache.interceptor.CacheInterceptor : Computed cache key 'SimpleKey []' for operation Builder[public java.util.List com.example.demo.service.PeopleService.findAll()] caches=[findAllCache] | key='' | keyGenerator='' | cacheManager='' | cacheResolver='' | condition='' | unless='#result == null' | sync='false'2019-12-04 17:43:49.000 TRACE 10832 --- [nio-8080-exec-5] o.s.cache.interceptor.CacheInterceptor : Cache entry for key 'SimpleKey []' found in cache 'findAllCache'
2019-12-04 17:43:55.761 TRACE 10832 --- [nio-8080-exec-7] o.s.cache.interceptor.CacheInterceptor : Computed cache key 'SimpleKey []' for operation Builder[public java.util.List com.example.demo.service.PeopleService.findAll()] caches=[findAllCache] | key='' | keyGenerator='' | cacheManager='' | cacheResolver='' | condition='' | unless='#result == null' | sync='false'2019-12-04 17:43:55.768 TRACE 10832 --- [nio-8080-exec-7] o.s.cache.interceptor.CacheInterceptor : No cache entry for key 'SimpleKey []' in cache(s) [findAllCache]2019-12-04 17:43:55.768 TRACE 10832 --- [nio-8080-exec-7] o.s.cache.interceptor.CacheInterceptor : Computed cache key 'SimpleKey []' for operation Builder[public java.util.List com.example.demo.service.PeopleService.findAll()] caches=[findAllCache] | key='' | keyGenerator='' | cacheManager='' | cacheResolver='' | condition='' | unless='#result == null' | sync='false'2019-12-04 17:43:55.772 INFO 10832 --- [e [_default_]-4] c.example.demo.config.CacheEventLogger : Cache event EXPIRED for item with key SimpleKey []. Old value = [PeopleDto(id=93, firstName=Sar, lastName=Joko), PeopleDto(id=94, firstName=Sar, lastName=Joko), PeopleDto(id=95, firstName=Sar, lastName=Jokowi), PeopleDto(id=96, firstName=Sarjoko, lastName=wi)], New value = null2019-12-04 17:43:55.782 INFO 10832 --- [e [_default_]-4] c.example.demo.config.CacheEventLogger : Cache event CREATED for item with key SimpleKey []. Old value = null, New value = [PeopleDto(id=93, firstName=Sar, lastName=Joko), PeopleDto(id=94, firstName=Sar, lastName=Joko), PeopleDto(id=95, firstName=Sar, lastName=Jokowi), PeopleDto(id=96, firstName=Sarjoko, lastName=wi)]

While on “findByIdCache” (/id), the cache will not expired after 5 seconds if hit within that 5 seconds time period, because it used time to idle configuration. The cache lifetime will be added by another 5 seconds if hit.

  1. /id (no cache)
  2. /id (cached)
  3. /id (cached)
  4. /id (cached)
2019-12-04 17:58:31.969 TRACE 10832 --- [nio-8080-exec-6] o.s.cache.interceptor.CacheInterceptor   : Computed cache key '93' for operation Builder[public com.example.demo.dto.PeopleDto com.example.demo.service.PeopleService.findById(java.lang.Long)] caches=[findByIdCache] | key='#id' | keyGenerator='' | cacheManager='' | cacheResolver='' | condition='' | unless='#result == null' | sync='false'2019-12-04 17:58:31.970 TRACE 10832 --- [nio-8080-exec-6] o.s.cache.interceptor.CacheInterceptor   : No cache entry for key '93' in cache(s) [findByIdCache]2019-12-04 17:58:31.970 TRACE 10832 --- [nio-8080-exec-6] o.s.cache.interceptor.CacheInterceptor   : Computed cache key '93' for operation Builder[public com.example.demo.dto.PeopleDto com.example.demo.service.PeopleService.findById(java.lang.Long)] caches=[findByIdCache] | key='#id' | keyGenerator='' | cacheManager='' | cacheResolver='' | condition='' | unless='#result == null' | sync='false'2019-12-04 17:58:31.973  INFO 10832 --- [e [_default_]-5] c.example.demo.config.CacheEventLogger   : Cache event CREATED for item with key 93. Old value = null, New value = PeopleDto(id=93, firstName=Sar, lastName=Joko)
2019-12-04 17:58:35.571 TRACE 10832 --- [nio-8080-exec-9] o.s.cache.interceptor.CacheInterceptor : Computed cache key '93' for operation Builder[public com.example.demo.dto.PeopleDto com.example.demo.service.PeopleService.findById(java.lang.Long)] caches=[findByIdCache] | key='#id' | keyGenerator='' | cacheManager='' | cacheResolver='' | condition='' | unless='#result == null' | sync='false'2019-12-04 17:58:35.571 TRACE 10832 --- [nio-8080-exec-9] o.s.cache.interceptor.CacheInterceptor : Cache entry for key '93' found in cache 'findByIdCache'
2019-12-04 17:58:39.516 TRACE 10832 --- [nio-8080-exec-8] o.s.cache.interceptor.CacheInterceptor : Computed cache key '93' for operation Builder[public com.example.demo.dto.PeopleDto com.example.demo.service.PeopleService.findById(java.lang.Long)] caches=[findByIdCache] | key='#id' | keyGenerator='' | cacheManager='' | cacheResolver='' | condition='' | unless='#result == null' | sync='false'2019-12-04 17:58:39.516 TRACE 10832 --- [nio-8080-exec-8] o.s.cache.interceptor.CacheInterceptor : Cache entry for key '93' found in cache 'findByIdCache'
2019-12-04 17:58:42.511 TRACE 10832 --- [io-8080-exec-10] o.s.cache.interceptor.CacheInterceptor : Computed cache key '93' for operation Builder[public com.example.demo.dto.PeopleDto com.example.demo.service.PeopleService.findById(java.lang.Long)] caches=[findByIdCache] | key='#id' | keyGenerator='' | cacheManager='' | cacheResolver='' | condition='' | unless='#result == null' | sync='false'2019-12-04 17:58:42.512 TRACE 10832 --- [io-8080-exec-10] o.s.cache.interceptor.CacheInterceptor : Cache entry for key '93' found in cache 'findByIdCache'

On /update hit, it will update “findAllCache” and “findByIdCache” through @CachePut annotation.

2019-12-04 18:04:24.127  INFO 10832 --- [nio-8080-exec-6] com.example.demo.service.PeopleService   : Update: Updating cache with name: findAllCache and findByIdCache2019-12-04 18:04:24.130 TRACE 10832 --- [nio-8080-exec-6] o.s.cache.interceptor.CacheInterceptor   : Computed cache key 'PeopleDto(id=98, firstName=Sarjok, lastName=owi)' for operation Builder[public com.example.demo.dto.PeopleDto com.example.demo.service.PeopleService.updatePeople(com.example.demo.dto.PeopleDto)] caches=[findAllCache] | key='' | keyGenerator='' | cacheManager='' | cacheResolver='' | condition='' | unless=''2019-12-04 18:04:24.130 TRACE 10832 --- [nio-8080-exec-6] o.s.cache.interceptor.CacheInterceptor   : Computed cache key '98' for operation Builder[public com.example.demo.dto.PeopleDto com.example.demo.service.PeopleService.updatePeople(com.example.demo.dto.PeopleDto)] caches=[findByIdCache] | key='#peopleDto.id' | keyGenerator='' | cacheManager='' | cacheResolver='' | condition='' | unless=''2019-12-04 18:04:24.131  INFO 10832 --- [e [_default_]-6] c.example.demo.config.CacheEventLogger   : Cache event UPDATED for item with key 98. Old value = PeopleDto(id=98, firstName=Sarjok, lastName=owi), New value = PeopleDto(id=98, firstName=Sarjok, lastName=owi)

On /insert hit, it will delete “findAllCache” through @CacheEvict annotation.

2019-12-04 18:05:58.819  INFO 10832 --- [nio-8080-exec-2] com.example.demo.service.PeopleService   : Insert: Flushing cache with name: findAllCache2019-12-04 18:05:58.824 TRACE 10832 --- [nio-8080-exec-2] o.s.cache.interceptor.CacheInterceptor   : Computed cache key 'PeopleDto(id=null, firstName=Sarjoko, lastName=wi)' for operation Builder[public com.example.demo.dto.PeopleDto com.example.demo.service.PeopleService.insertPeople(com.example.demo.dto.PeopleDto)] caches=[findAllCache] | key='' | keyGenerator='' | cacheManager='' | cacheResolver='' | condition='',false,false2019-12-04 18:05:58.824 TRACE 10832 --- [nio-8080-exec-2] o.s.cache.interceptor.CacheInterceptor   : Invalidating cache key [PeopleDto(id=null, firstName=Sarjoko, lastName=wi)] for operation Builder[public com.example.demo.dto.PeopleDto com.example.demo.service.PeopleService.insertPeople(com.example.demo.dto.PeopleDto)] caches=[findAllCache] | key='' | keyGenerator='' | cacheManager='' | cacheResolver='' | condition='',false,false on method public com.example.demo.dto.PeopleDto com.example.demo.service.PeopleService.insertPeople(com.example.demo.dto.PeopleDto)

On /flush hit, it will delete all cache.

2019-12-04 18:13:08.387  INFO 11357 --- [nio-8080-exec-4] com.example.demo.service.PeopleService   : Flushing cache with name: findByIdCache2019-12-04 18:13:08.388  INFO 11357 --- [e [_default_]-0] c.example.demo.config.CacheEventLogger   : Cache event REMOVED for item with key 93. Old value = PeopleDto(id=93, firstName=Sar, lastName=Joko), New value = null2019-12-04 18:13:08.389  INFO 11357 --- [nio-8080-exec-4] com.example.demo.service.PeopleService   : Flushing cache with name: findAllCache2019-12-04 18:13:08.389  INFO 11357 --- [e [_default_]-0] c.example.demo.config.CacheEventLogger   : Cache event REMOVED for item with key SimpleKey []. Old value = [PeopleDto(id=93, firstName=Sar, lastName=Joko), PeopleDto(id=94, firstName=Sar, lastName=Joko), PeopleDto(id=95, firstName=Sar, lastName=Jokowi), PeopleDto(id=96, firstName=Sarjoko, lastName=wi), PeopleDto(id=97, firstName=Sarjok, lastName=owi), PeopleDto(id=98, firstName=Sarjok, lastName=owi), PeopleDto(id=99, firstName=Sarjoko, lastName=wi), PeopleDto(id=100, firstName=Sarjoko, lastName=wi), PeopleDto(id=101, firstName=Sarjoko, lastName=wi)], New value = null

You can see the source code here:
https://github.com/plankrun/learn-ehcache

References:

https://springframework.guru/using-ehcache-3-in-spring-boot/

--

--