Configuration for specific Caffeine Caches in Spring - spring

We need to implement several methods that have different caching times. Each method is annotated with #Cacheable and our current solution includes multiple CacheManager that are set in a CachingConfigurerSupport.
public class CachingConfiguration extends CachingConfigurerSupport {
#Override
#Bean
public CacheManager cacheManager() {
CaffeineCacheManager cacheManager = new CaffeineCacheManager();
cacheManager.setCaffeine(Caffeine.newBuilder().expireAfterWrite(1, TimeUnit.DAYS));
return cacheManager;
}
#Bean
public CacheManager anotherCache() {
CaffeineCacheManager cacheManager = new CaffeineCacheManager();
cacheManager.setCaffeine(Caffeine.newBuilder().expireAfterWrite(1, TimeUnit.MINUTES));
return cacheManager;
}
}
The #Cacheable annotation then included the cacheManager:
#Cacheable(cacheNames = "someCache", cacheManager = "anotherCache")
Basically that's fine but is also errorprune if you forget the cacheManager parameter etc.
So I currently try to find a better solution but as far as I can see, there is currently no general accepted way to go.
Imho the main advantage of the CaffeineCacheManager compared to e.g. SimpleCacheManager is the possibility to define a base configuration and then initialize additional caches lazily. But wouldn't it be great if you are able to set additional caches which are never reinitialized?
Those caches must be used preferentially and created in the CachingConfigurerSupport.
Maybe I'm missing something, but shouldn't this solve the problem that has already been discussed in several threads in different forms?

Recently I decided to turn my initial PR into a separate tiny project.
To start using it just add the latest dependency from Maven Central:
<dependency>
<groupId>io.github.stepio.coffee-boots</groupId>
<artifactId>coffee-boots</artifactId>
<version>2.0.0</version>
</dependency>
Format of properties is the following:
coffee-boots.cache.spec.myCache=maximumSize=100000,expireAfterWrite=1m
If no specific configuration is defined, CacheManager defaults to Spring's behavior.

Simple way without any thrid-party lib:
spring.cache.type=caffeine
# default spec (optional)
spring.cache.caffeine.spec=maximumSize=250,expireAfterWrite=15m
# specific specs (also optional)
caffeine.specs.places=maximumSize=1000,expireAfterWrite=1h
Register custom caches:
applicationContext.registerBean { context ->
CacheManagerCustomizer<CaffeineCacheManager> { cacheManager ->
for (spec in Binder.get(context.environment).bindOrCreate("caffeine.specs", HashMap::class.java)) {
cacheManager.registerCustomCache(spec.key.toString(), Caffeine.from(spec.value.toString()).build())
}
}
}

Related

What is the modern approach to configure multiple cache managers?

Situation: my spring-boot project has two objects in which I need to cache data. The problem is that the settings for expire time must be different for the two objects.
I found a lot of information on the web about how i can set up a cache manager, but I did not understand which of these methods can be used in modern industrial code.
Please tell me how to configure settings for 2 different cache managers and if you need cache managers at all? My head is in a mess.
p.s. I don't want to use third-party dependencies like ehcache.
example:
public class Repository1(){
#Cacheable("Repository1.findAllImportantThings")
public Map<Long, String> findAllImportantThings() {...}
}
public class Repository2(){
#Cacheable("Repository2.findOnlyOne")
public Map<Long, String> findOnlyOne(String id) {...}
}
There can be more methods in a class. But the point is that I need to set different TTLs for these two classes
When you want to have different cache TTL then you need cacheManager, if in case all the caches have same TTL then it is not required.
You can do it this way :
#Bean
fun cacheManager(connectionFactory: RedisConnectionFactory): RedisCacheManager {
val oneHourTTLCacheConfig = cacheConfigWithTTL(60) //TTL for cacheKey-1
val fourHoursTTLCacheConfig = cacheConfigWithTTL(240) //TTL for cacheKey-2
val cacheConfigurations = mapOf(
"cacheKey-1"
to oneHourTTLCacheConfig,
"cacheKey-2"
to fourHoursTTLCacheConfig
)
return RedisCacheManager.builder(connectionFactory)
.cacheDefaults(redisCacheConfiguration(CacheConfiguration.THIRTY_MINUTES_DEFAULT_TTL))
.withInitialCacheConfigurations(cacheConfigurations)
.build()
} // Sets default TTL to 30 minutes if the other cacheKeys don't require any different configuration
private fun cacheConfigWithTTL(ttlInMinutes: Long): RedisCacheConfiguration =
RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofMinutes(ttlInMinutes))
.disableCachingNullValues()
private fun redisCacheConfiguration(duration: Duration): RedisCacheConfiguration =
RedisCacheConfiguration
.defaultCacheConfig()
.entryTtl(duration)
You can add this to your cache configuration class. Let me know if you have any further question.

Override configuration prefix on IBM MQ Starter - change prefix from ibm.mq to infrastructure.ibm.mq

MQ starter has
#ConfigurationProperties(prefix = "ibm.mq")
public class MQConfigurationProperties {
I want to change the config prefix to infrastructure.ibm.mq and the rest of the hiearchy on the
config is the same
I want to avoid changing the MQConfiguration.java file, and recompiling, I just want to use the starter as is, but use a slightly different config prefix
This is one way I was able to override it. The #Primary means that this takes precedence. Otherwise you get errors about finding 2 beans where only a single is accepted.
#Bean
#Primary
#ConfigurationProperties(prefix = "my.local.prefix")
public MQConfigurationProperties localConfigurationProperties() {
return new MQConfigurationProperties();
}

Cache is closed causing an exception while running test suite

I'm experiencing a problem similar to the described in this question.
I have a test suite that runs fine in development environment. One of the tests fails when executed in Bitbucket Pipelines with the following exception:
org.springframework.dao.InvalidDataAccessApiUsageException: Cache[model.Role] is closed; nested exception is java.lang.IllegalStateException: Cache[model.Role] is closed
at org.springframework.orm.jpa.EntityManagerFactoryUtils.convertJpaAccessExceptionIfPossible(EntityManagerFactoryUtils.java:364)
at org.springframework.orm.jpa.vendor.HibernateJpaDialect.translateExceptionIfPossible(HibernateJpaDialect.java:225)
at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.translateExceptionIfPossible(AbstractEntityManagerFactoryBean.java:527)
at org.springframework.dao.support.ChainedPersistenceExceptionTranslator.translateExceptionIfPossible(ChainedPersistenceExceptionTranslator.java:61)
at org.springframework.dao.support.DataAccessUtils.translateIfNecessary(DataAccessUtils.java:242)
....
I want to try the accepted solution but I don't know how to apply it to my project. The second solution depends on ehcache.xml file. I don't have this file, everything is configured in JavaConfig. How can I adopt the proposed solutions for EhCache + JCache (JSR-107) in JavaConfig?
My cache configuration:
#Configuration
#EnableCaching
public class CacheConfig {
private final javax.cache.configuration.Configuration<Object, Object> jcacheConfiguration =
Eh107Configuration.fromEhcacheCacheConfiguration(CacheConfigurationBuilder
.newCacheConfigurationBuilder(Object.class, Object.class,
ResourcePoolsBuilder.newResourcePoolsBuilder()
.heap(100, EntryUnit.ENTRIES))
.withExpiry(ExpiryPolicyBuilder.timeToLiveExpiration(Duration.ofSeconds(60)))
.build());
#Bean
public JCacheManagerCustomizer cacheManagerCustomizer() {
return cm -> {
createIfNotExists(cm, "model.Role");
createIfNotExists(cm, "model.User.roles");
// ...
};
}
private void createIfNotExists(CacheManager cacheManager, String cacheName) {
if (cacheManager.getCache(cacheName) == null) {
cacheManager.createCache(cacheName, jcacheConfiguration);
}
}
}
Gradle dependencies:
implementation group: 'org.springframework.boot', name: 'spring-boot-starter-cache'
implementation group: 'javax.cache', name: 'cache-api'
implementation group: 'org.ehcache', name: 'ehcache'
implementation group: 'org.hibernate', name: 'hibernate-jcache'
The failing test:
#SpringBootTest
#RunWith(SpringJUnit4ClassRunner.class)
public class SecondLevelCacheTest {
#Autowired
private RoleRepository roleRepository;
private CacheManager manager;
#Before
public void initCacheManager() {
CachingProvider provider = Caching.getCachingProvider();
manager = provider.getCacheManager();
final String cacheRegion = "model.Role";
manager.getCache(cacheRegion).clear();
}
#Test
public final void givenEntityIsLoaded_thenItIsCached() {
final String cacheRegion = "model.Role";
boolean hasNext = manager.getCache(cacheRegion).iterator().hasNext();
final Role role = roleRepository.findByName("USER");
boolean hasNext2 = manager.getCache(cacheRegion).iterator().hasNext();
final Role role2 = roleRepository.findByName("USER");
Assert.assertFalse(hasNext);
Assert.assertTrue(hasNext2);
}
}
The most upvoted solution is "to set shared property to false in the testing context." How can I do this with regard to my configuration?
One option is to provide a custom CachingProvider that does not share CacheManager-s. An example solution can be found here.
Annotate your failing test class with #AutoConfigureCache. By default, this annotation will install a NoOpCacheManager, i.e. a basic, no operation CacheManager implementation suitable for disabling caching, typically used for backing cache declarations without an actual backing store. It will simply accept any items into the cache not actually storing them.
Spring Documentation on #AutoConfigureCache
Spring Documentation on #NoOpCacheManager
The proposed solution you are talking about is based on Ehcache 2. You are using Ehcache 3 (good for you), so it's not valid.
Spring will take care of closing the CacheManager so normally, you don't need to take care of anything. Also, you do not need to access it through the CachingProvider. You can #Autowired the javax.cache.CacheManager and that way you are sure to get the right one.
However, you are using Hibernate. You should make sure that Spring and Hibernate are using the same CacheManager. The way to configure it depends on the Spring and Hibernate version.
Can we have the full stack trace? Right now it feels like something is closing the CacheManager without deregister it from the CachingProvider. This is impossible unless you are closing the org.ehcache.CacheManagerwithout closing the javax.cache.CacheManager wrapping it. Closing the later will cause the deregistration.

How do you supply a ClockProvider to Spring v5 (Spring Boot v2)?

What is the proper way to supply your own ClockProvider to the ValidatorFactory configuration in Spring v5 (Spring Boot v2) so that it's used everywhere the Bean Validations Validator is injected?
Use Case: You want to provide a buffer on what it considers "Present", such as described in this blog post to account for a reasonable amount of clock drift.
The simplest solution, that also keeps all Spring defaults untouched, is to override method postProcessConfiguration():
#Configuration
class TimeConfiguration {
#Bean
static Clock clock() {
return Clock.fixed(
Instant.ofEpochSecond(1700000000), ZoneOffset.UTC
);
}
#Bean
static LocalValidatorFactoryBean defaultValidator(Clock clock) {
LocalValidatorFactoryBean factoryBean = new LocalValidatorFactoryBean() {
#Override
protected void postProcessConfiguration(
javax.validation.Configuration<?> configuration) {
configuration.clockProvider(() -> clock);
}
};
MessageInterpolatorFactory interpolatorFactory =
new MessageInterpolatorFactory();
factoryBean.setMessageInterpolator(interpolatorFactory.getObject());
return factoryBean;
}
}
Spring 5 is only runtime compatible with Bean Validation 2.0, which introduced ClockProvider you'd like to use. See next code from Spring sources. I think there are two ways you can go from this. You can try to use xml configuration for validator and specify clock provider there. It'll look something like this in your validation.xml:
<validation-config
xmlns="http://xmlns.jcp.org/xml/ns/validation/configuration"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/validation/configuration
http://xmlns.jcp.org/xml/ns/validation/configuration/validation-configuration-2.0.xsd"
version="2.0">
// any other configurations...
<clock-provider>com.acme.ClockProvider</clock-provider>
</validation-config>
Another option, if you don't like xml, would be to try define and use your own LocalValidatorFactoryBean.
Also note that maybe for your usecase it'll be useful to use a relatively new feature introduced in Hibernate Validator - temporal validation tolerance, which allow to specify a tolerance for temporal constraints. For more details about it see the documentation. This tolerance can also be set in xml as well as programatically.
Check this blogpost
#Bean
#ConditionalOnMissingBean(ClockProvider.class) // to allow tests to overwrite it
public ClockProvider getClockProvider() {
return () -> Clock.systemDefaultZone();
}

How do a I setup mongodb messagestore in aggregator using annotation

I am trying to add an aggregator to my code.
Couple of problems I am facing.
1. How do I setup a messagestore using annotations only.
2. Is there any design of aggregator works ? basically some picture explaining the same.
#MessageEndpoint
public class Aggregator {
#Aggregator(inputChannel = "abcCH",outputChannel = "reply",sendPartialResultsOnExpiry = "true")
public APayload aggregatingMethod(List<APayload> items) {
return items.get(0);
}
#ReleaseStrategy
public boolean canRelease(List<Message<?>> messages){
return messages.size()>2;
}
#CorrelationStrategy
public String correlateBy(Message<AbcPayload> message) {
return (String) message.getHeaders().get(RECEIVED_MESSAGE_KEY);
}
}
In the Reference Manual we have a note:
Annotation configuration (#Aggregator and others) for the Aggregator component covers only simple use cases, where most default options are sufficient. If you need more control over those options using Annotation configuration, consider using a #Bean definition for the AggregatingMessageHandler and mark its #Bean method with #ServiceActivator:
And a bit below:
Starting with the version 4.2 the AggregatorFactoryBean is available, to simplify Java configuration for the AggregatingMessageHandler.
So, actually you should configure AggregatorFactoryBean as a #Bean and with the #ServiceActivator(inputChannel = "abcCH",outputChannel = "reply").
Also consider to use Spring Integration Java DSL to simplify your life with the Java Configuration.

Resources