It’s fairly common for applications to continually ask a datastore for the same information repeatedly. Requests to datastores consume application resources and thus have a performance cost even when the requested data is small. The Spring Platform provides a solution allows applications to store information in an in memory caching system that allows applications to check the cache for the required data prior to making a call to the database. This example shows how to use Spring Boot and Kotlin to cache files that we are storing in the database.
Database Entity
We are going to define a database entity that stores files in a database. Since retrieving such data can be an expesive call to the database, we are going to cache this entity.
@Entity data class PersistedFile( @field: Id @field: GeneratedValue var id : Long = 0, var fileName : String = "", var mime : String = "", @field : Lob var bytes : ByteArray? = null)
You will notice that this class has a ByteArray field that is stored as a LOB in the database. In theory, this could be as many bytes as the system allows so ideally we would store this in cache. Other good candidates are entity classes that have complex object graphs and may result in the ORM generated complex SQL to retreive the managed object.
Enable Caching
Spring Boot defines a CachingManager internally for the application. You are free to use your own, but you need to configure your Spring Boot environment first.
Dependencies
You need to have spring-boot-starter-cache in your pom.xml or other dependency manager.
org.springframework.boot spring-boot-starter-web
Annotation
You also need to tell the environment to turn on caching by using the @EnableCaching
@SpringBootApplication @EnableJpaRepositories @EnableCaching //Spring Boot provides a CacheManager our of the box //but it only turns on when this annotation is present class CachingTutorialApplication
Decorate the Caching Methods
At this point, we only need to decorate the methods we want the environment cache. This is done by decorating our methods with the @Cacheable annotation and then providing the annotation with the name of a cache. We can also optionally tell the cache manager what to use for the key. Here is the code for our service class followed by an explanation.
//We are going to use this class to handle caching of our PersistedFile object //Normally, we would encapsulate our repository, but we are leaving it public to keep the code down @Service class PersistedFileService(@Autowired val persistedFileRepository: PersistedFileRepository){ //This annotation will cause the cache to store a persistedFile in memory //so that the program doesn't have to hit the DB each time for the file. //This will result in faster page load times. Since we know that managed objects //have unique primary keys, we can just use the primary key for the cache key @Cacheable(cacheNames = arrayOf("persistedFile"), key="#id") fun findOne(id : Long) : PersistedFile = persistedFileRepository.findOne(id) //This annotation will cause the cache to store persistedFile ids //By storing the ids, we don't need to hit the DB to know if a file exists first @Cacheable(cacheNames = arrayOf("persistedIds")) fun exists(id: Long?): Boolean = persistedFileRepository.exists(id) }
The first method, findOne, is used to look up a persistedFile object from the database. You will notice that we pass persistedFile as an argument to cacheNames and then use the primary key as the key for this item’s cache. We can use the PK because we know it’s a unique value so we can help make the cache more performant. However, keep in mind that the key is optional.
We can also avoid another call to the database by storing if items exist in the database in the cache. The first time exists() is called, the application will fire a count sql statement to the database. On subsequent calls, the cache will simply return true or false depending on what is stored in the cache.
Putting it all together
I put together a small web application that demonstates the caching working together. I turned on the show sql property in the applications.properties file so that viewers can see when the application is making calls to the database. You will notice that the first time I retreive the persisted file, there is sql generated. However, on the second call to the same object, no sql is generated because the application isn’t making a call to the database.
You can get the complete code from my GitHub page at this link.
Here are some links to posts that are related to concepts used in Spring Boot that we used today.
How do you handle eviction from the cache after an entity has been modified ?
LikeLiked by 1 person
Hi Jo8192! There is a @CacheEvict annotation that takes the same arguments @Cache. You just decorate the methods that you want to use to evict objects from the cache and let Spring do the work!
http://docs.spring.io/spring/docs/4.3.9.RELEASE/spring-framework-reference/htmlsingle/#cache-annotations-evict
LikeLike