Caching
Openmrs-core uses Spring Cache combined with Infinispan (since 2.8.x) or Ehcache in older versions of core to provide the caching capabilities for service methods.
In addition to that we leverage Hibernate 2nd level cache, which is similarly backed by Infinispan (since 2.8.x) or Ehcache in older versions of core.
When writing code it is very important to use Spring Cache mechanisms instead of any custom caching done via static fields or other caching libraries. It is for OpenMRS to be able to scale by creating replicas in clustered environments. In such scenarios cache invalidation needs to be communicated to all replicas, which is handled by openmrs-core.
Configuration
Infinispan (since 2.8.x)
Infinispan supports both embedded local cache as well as embedded clustered cache. By default it is configured to use local cache configuration, which is more performant if run on a single VM. The embedded clustered cache can be enabled by setting cache.type=cluster property (via openmrs-runtime.properties, system or java environment property).
Spring cache
The Spring cache configuration is based on a local (infinispan-api-local.xml) or clustered (infinispan-api.xml) cache template named “entity” loaded based on the selected cache.type. Please review the template config files for default cache parameters.
The default template can be replaced by a custom one that can be loaded from a file specified with the cache.config property. The custom template needs to define at minimum a template named “entity” that is used by default to create caches.
Caches are not automatically created by Spring. They need to be specified in cache-api.yaml file. Caches can be also added by modules through a cache-api.yaml file in their classpath. The file shall contain only the caches element as defined in Infinispan docs at multiple caches.
Hibernate 2nd level cache
Hibernate 2nd level cache is based on an embedded local or clustered cache template similarly to Spring Cache. The same cache.type=cluster/local property determines, which one will be loaded. All default configurations for Hibernate 2nd level caches can be reviewed in Inifnispan docs. Caches are automatically created by Hibernate for all annotated entities.
Ehcache (pre 2.8.x)
The config for ehCache is loaded form ehcache-api.xml file, but you can extend it by creating apiCacheConfig.properies file.
Extending cache configuration
Cache configuration is loaded from ehcache-api.xml file, but you it can be extended by creating/modifying apiCacheConfig.properies file. It can contain multiple cache configurations and as long as they are not set in ehcache-api.xml they will be applied.
Example file:
userSearchLocales.maxElementsInMemory=500
userSearchLocales.eternal=false
userSearchLocales.timeToIdleSeconds=300
userSearchLocales.timeToLiveSeconds=300
userSearchLocales.memoryStoreEvictionPolicy=LRU
conceptDatatype.maxElementsInMemory=400
conceptDatatype.eternal=false
conceptDatatype.timeToIdleSeconds=200
conceptDatatype.timeToLiveSeconds=200
conceptDatatype.memoryStoreEvictionPolicy=LRU
As you can see there are two cache configs: "userSearchLocales" and "subscription". The format of one line should look like {cacheName}.{property}={value}. All available properties for ehcache configuration can be found here.
Caching service methods
Spring comes with several caching annotations, that you can use on your service methods. Basic annotation are @Cachable and @CacheEvict.
Annotating your method (implementation and not interface) with @Cachable tells spring to cache data when calling the method for the first time. When this method is called again Spring uses cached data instead of reading it from the database. This will make your application more efficient.
The basic usage of this annotation could look like this:
@Cacheable(value = "userSearchLocales")
public List<Locale> getSearchLocales(Locale currentLocale, User user) throws APIException {...}
When the getSearchLocales() method is called the returned data is cached using method parameters as the key and associated with the cache name "userSearchLocales".
What if data changes? We don't want to get outdated data. This is why we need to use another annotation @CacheEvict. It should be used on methods where data might be changed e.g.:
@CacheEvict(value = "userSearchLocales", allEntries = true)
public GlobalProperty saveGlobalProperty(GlobalProperty gp) {...}
Once we call saveGlobalProperty() method all entries associated with "userSearchLocales" will be deleted from cache. Caches can also be configured to auto-expire after a certain time, which is useful in cases where you cannot annotate all methods that change data.
For more information go to the Spring docs.
Applying Cacheable in modules
In order to use cache in a module:
Annotate a Spring bean method with (replacing moduleId with your module Id and CacheName with any name):
@Cacheable(value = “moduleIdCacheName”)
Please note that if you want to call the annotated method from the same bean you need to declare:
@Autowired @Lazy
private YourBeanClass self;
And call the method via self:
self.yourCachedMethod();
It is due to the fact that only then you call a proxied method, which is implementing caching. The same principle applies for other proxied methods annotated with e.g. @Transactional or @Authorized.
Optionally annotate any methods that may invalidate the cache with:
@CacheEvict(value = “moduleIdCacheName”, allEntries = true)
Create Ehcache config in api/src/main/resources/apiCacheConfig.properies (to support openmrs-core pre 2.8) with:
moduleIdCacheName.maxElementsInMemory=500 moduleIdCacheName.eternal=false moduleIdCacheName.timeToIdleSeconds=100 moduleIdCacheName.timeToLiveSeconds=100 moduleIdCacheName.memoryStoreEvictionPolicy=LRU
Create Infinispan config in api/src/main/resources/cache-api.yaml (to support openmrs-core 2.8 and later) with:
caches: moduleIdCacheName: configuration: "entity"
The use of configuration: “entity” in Infinispan applies “entity” template from infinispan-api.xml or infinispan-api-local.xml depending on the deployment configuration. It has for example 100 seconds expiration when idle for entries. You can override any value from the template for your cache in cache-api.yaml, however it is recommended to use the template as a base configuration in order for the cache to be configured dynamically as local or distributed.
Caching Hibernate entities and queries
Hibernate entities are cached if classes are annotated with @Cacheable
. In addition you can specify @Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
to select concurrency strategy. Read-write is the default strategy so it can be omitted. Please refer to Hibernate docs for more details.
Results of queries can be cached as well by calling .setCacheable(true)
on selected queries. Please refer to Hibernate docs for mode details as well.