I have a Spring (not Boot) application with a successfully configured and used Caching mechanism through the CacheManager that looks like the following:
#Configuration
#EnableCaching(proxyTargetClass=true)
public class CachingConfig {
#Bean
public CacheManager cacheManager() {
SimpleCacheManager cacheManager = new SimpleCacheManager();
List<Cache> caches = List.of(
new ConcurrentMapCache("cache_name_1"),
// ....
);
cacheManager.setCaches(caches);
return cacheManager;
}
}
Spring also supports ETags as can be read here. And it could be configured as follows:
#Bean
public ShallowEtagHeaderFilter shallowEtagHeaderFilter() {
return new ShallowEtagHeaderFilter();
}
Is there any need for ETags when one has already configured Caching used the first approach above?
You have 2 different concepts.
Server side cache: Calls from different clients with the same parameters can get the same result from server cache.
Client side cache: Get once and use ETag to see if anything has changed, no need to get it again if not modified.
Related
I have the following cache implementation in a Spring Boot app and it is working without any problem. However, I want to define expiration for this approach. Is it possible to set expiration for #Cacheable?
I look at Expiry time #Cacheable spring boot and there is not seem to be a direct way for #Cacheable. Is it possible via a smart approach?
#Configuration
#EnableCaching
public class CachingConfig {
#Bean
public CacheManager cacheManager() {
return new ConcurrentMapCacheManager();
}
}
#Component
public class SimpleCacheCustomizer
implements CacheManagerCustomizer<ConcurrentMapCacheManager> {
#Override
public void customize(ConcurrentMapCacheManager cacheManager) {
cacheManager.setCacheNames(asList("users"));
}
}
#Cacheable("users")
public List<User> getUsers(UUID id) {...}
As said in the Spring documentation, there is no TTL for the default cache system of Spring.
8.7. How can I Set the TTL/TTI/Eviction policy/XXX feature?
Directly through your cache provider. The cache abstraction is an
abstraction, not a cache implementation. The solution you use might
support various data policies and different topologies that other
solutions do not support (for example, the JDK
ConcurrentHashMap — exposing that in the cache abstraction would be
useless because there would no backing support). Such functionality
should be controlled directly through the backing cache (when
configuring it) or through its native API
You'll have to use an other cache provider like Redis or Gemfire if you want a TTL configuration.
An example of how to use TTL with Redis is available here.
I have a Spring Boot 2 application with Redis cache. It worked just fine until I overridden CacheManager bean.
Problem: The following configuration property gets ignored (I can't turn off caching anymore):
spring.cache.type=none
Although according to the documentation it should work.
Question: How to make the spring.cache.type=none work?
There is a workaround like this, but it is far from being a good solution.
More details: Here is how my configuration looks like:
#Configuration
public class CacheConfiguration {
#Bean
RedisCacheWriter redisCacheWriter(RedisConnectionFactory connectionFactory) {
return RedisCacheWriter.lockingRedisCacheWriter(connectionFactory);
}
#Bean
CacheManager cacheManager(RedisCacheWriter redisCacheWriter) {
Map<String, RedisCacheConfiguration> ttlConfiguration = ...
RedisCacheConfiguration defaultTtlConfiguration = ...
return new RedisCacheManager(
redisCacheWriter, defaultTtlConfiguration, ttlConfiguration
);
}
}
Because you are creating the CacheManager yourself you also have to check spring.cache.type if you want to turn it of.
#Bean
#ConditionalOnExpression("${spring.cache.type} != 'none'")
CacheManager cacheManager(RedisCacheWriter redisCacheWriter) {
A Built in Spring Redis Cache configuration resides in org.springframework.boot.autoconfigure.cache.RedisCacheConfiguration
It has a #Conditional(CacheCondition.class) on it.
This CacheCondition checks the value of spring.cache.type property. If its set to "NONE" the whole configuration, including the RedisCacheManager bean won't load at all.
Now as you've created your own configuration where you define the cacheManager by yourself it gets loaded regardless the value of spring.cache.type variable
So you should probably put some conditional value (that will read the spring.cache.type value or your custom condition)
I've been successful using using the Accessing Data With JPA tutorial for Spring. I've gotten a CrudRepository of my own to work automatically by just configuring a specific DataSource #Bean, and the internal connections between these are managed by Spring Data (or Spring Boot, it's hard to tell which).
However, I can't figure out how to get that automated plumbing to handle a second DataSource #Bean. Injecting a second one causes the autoconfiguration classes to explode during startup.
Any thoughts as to how to do this? The searches I've done for this resulted in articles discussing multiple homogeneous DataSources for load balancing or other purposes, which is really not what I need. I have multiple databases with completely separate content that I need to pull into this app and I'd really like to avoid having to replicate all that automated configuration just because a second database entered the mix.
I'm hoping this is simple, but I'm fearful that it's an unsupported edge case in the autoconfiguration.
You can create two datasources and entitymanagers, one bean of them mark as #Primary
#Configuration
#EnableJpaRepositories(basePackages = "io.eddumelendez.springdatajpa.repository1")
public class FirstConfiguration {
#ConfigurationProperties(prefix = "datasource.postgres")
#Bean
#Primary
public DataSource postgresDataSource() {
return DataSourceBuilder.create().
build();
}
#Bean(name = "entityManagerFactory")
#Primary
public LocalContainerEntityManagerFactoryBean emf1(EntityManagerFactoryBuilder builder){
return builder
.dataSource(postgresDataSource())
.packages("io.eddumelendez.springdatajpa.domain1")
.persistenceUnit("users")
.build();
}
}
Configuration for another datasource:
#Configuration
#EnableJpaRepositories(basePackages = "io.eddumelendez.springdatajpa.repository2", entityManagerFactoryRef = "emf2")
public class SecondConfiguration {
#Bean
#ConfigurationProperties(prefix = "datasource.mysql")
public DataSource mysqlDataSource() {
return DataSourceBuilder.create().build();
}
#Bean
public LocalContainerEntityManagerFactoryBean emf2(EntityManagerFactoryBuilder builder){
return builder
.dataSource(mysqlDataSource())
.packages("io.eddumelendez.springdatajpa.domain2")
.persistenceUnit("customers")
.build();
}
}
Your application.properties should looks like this:
datasource.mysql.url=jdbc:mysql://localhost:3306/mysql_demo
datasource.mysql.username=root
datasource.mysql.password=root
datasource.postgres.url=jdbc:postgresql://localhost:5432/postgres_demo
datasource.postgres.username=postgres
datasource.postgres.password=postgres
In my Spring/Grails/Groovy app, I configure some cache beans:
rulesCache(InMemoryCache){..}
countriesCache(InMemoryCache){..}
myService(ServiceBean){
cache = ref('rulesCache')
}
A cache manager provides specialized services when retrieving caches, so I give the manager a list of cache beans:
cacheMgr(CacheManager){
caches = [ ref('rulesCache'), ref('countriesCache')]
}
Services must get cache beans from the manager, they can't be "wired in" (the manager returns a cache delegate, not the cache itself, that's why), so I got around this problem by doing:
cacheMgr(CacheManager){
caches = [ ref('rulesCache'), ref('countriesCache')]
}
cacheMgrDelegate(MethodInvokingFactoryBean) { bean ->
bean.dependsOn = ['cacheMgr']
targetObject = cacheMgr
targetMethod = 'getManager'
}
myService(SomeService){
cache = "#{cacheMgrDelegate.getCache('rulesCache')}"
}
This works fine, but cache beans are arbitrary, so I can't provide a list to the manager. I managed to get around this problem by listening for post initialization events from cache type objects, and registering each cache manually with the manager:
CacheManager implements BeanPostProcessor {
postProcessAfterInitialization(bean, beanName){
if(bean instanceof ICache)
registerCache(bean)
return bean
}
}
Problem
The issue is that Spring is doing initialization on myService before cacheManager registers all cache beans, so getCache() returns null:
myService(SomeService){
cache = "#{cacheMgrDelegate.getCache('rulesCache')}"
}
I understand why it's happening. I can't use dependsOn since cache beans are arbitrary, and this is where I'm stuck.
Possible Solution
During spring config phase, CacheManager.getCache(name) could return a lightweight "proxy"-like object while saving a reference to each proxy generated:
getCache(String name){
CacheProxy proxy = createProxy()
proxies.add(proxy)
return proxy
}
After all beans are configured and app context is set, cacheManager simply iterates the list of proxies and completes initialization:
onApplicationContext(){
proxies.each{ proxy ->
completeInit(proxy)
}
}
Is there a better option? I'm out of ideas :-)
Can't you simply autowire all instances of ICache instead? It should create necessary dependencies between CacheManager and the caches:
CacheManager {
#Autowired
public void setCaches(List<ICache> caches) {
...
}
...
}
I configured spring method caching with ehcache and annotation driven configuration.
I would like however to be able to disable it from a configuration file we use in the application.
My first idea was to call net.sf.ehcache.CacheManager.CacheManager() with no arguments if method caching is disabled. This throws exception:
java.lang.IllegalArgumentException: loadCaches must not return an empty Collection
at org.springframework.util.Assert.notEmpty(Assert.java:268)
at org.springframework.cache.support.AbstractCacheManager.afterPropertiesSet(AbstractCacheManager.java:49)
My second idea was to configure the net.sf.ehcache.CacheManager.CacheManager() with default data so that the cache is not used (maxElementsInMemory 0 etc.). But then the cache is still used, which is not what I want.
There is a property net.sf.ehcache.disabled but I do not want do disable hibernate caching that also uses ehcache.
Q How can I configure everything to have spring method caching but disable it from my external configuration file? I do not want to modify the application-context nor the code to enable/disable method caching. Only the configuration file we use in the application can be modified.
What I was looking for was NoOpCacheManager:
To make it work I switched from xml bean creation to a factory
I did something as follows:
#Bean
public CacheManager cacheManager() {
final CacheManager cacheManager;
if (this.methodCacheManager != null) {
final EhCacheCacheManager ehCacheCacheManager = new EhCacheCacheManager();
ehCacheCacheManager.setCacheManager(this.methodCacheManager);
cacheManager = ehCacheCacheManager;
} else {
cacheManager = new NoOpCacheManager();
}
return cacheManager;
}
You can use a spring profile, to enable (or not) the spring caching support
<beans profile="withCache">
<cache:annotation-driven />
</beans>