I am trying to write an application to cache which reloads every few seconds. I decided to use spring boot Caffeine and got a sample application too. But when I am specifying refreshAfterWrite property, it throws exception: refreshAfterWrite requires a LoadingCache
spring:
cache:
cache-names: instruments, directory
caffeine:
spec: maximumSize=500, expireAfterAccess=30s, refreshAfterWrite=30s
To resolve this I provide Loading Cache Bean, but cache stopped working altogether:
#Bean
public CacheLoader<Object, Object> cacheLoader() {
return string -> {
System.out.println("string = " + string);
return string;
};
}
#Bean
public LoadingCache<Object, Object> loader(CacheLoader<Object, Object> cacheLoader) {
return Caffeine.newBuilder()
.refreshAfterWrite(1, TimeUnit.SECONDS)
.build(cacheLoader);
}
Do we have some simple way for reload to work?
To conclude here, using the LoadingCache feature of Caffeine with Spring's cache abstraction does not make much sense since they share a lot of features.
#Cacheable typically provide a way to mark a method to retrieve an element that is not present in the cache yet. LoadingCache achieves the same scenario, requiring you to provide a handle that can load a missing element by id.
If you absolutely need to use a LoadingCache, I'd inject the Cache in your code and interact with it programmatically.
Related
I am facing a strange issue - I have hazelcast and redis in my project. Suddenly all #Cacheable annotations are putting entries only to hazelcast cache, even if the particular cache name is configured via redis cache builder:
#Bean
fun redisCacheManagerBuilderCustomizer(): RedisCacheManagerBuilderCustomizer? {
return RedisCacheManagerBuilderCustomizer { builder: RedisCacheManagerBuilder ->
builder
.withCacheConfiguration(
MY_CACHE,
RedisCacheConfiguration.defaultCacheConfig().entryTtl(Duration.ofDays(3))
)
}
}
Using cache:
#Cacheable(cacheNames = [CacheConfig.MY_CACHE])
#Cacheable(value= [CacheConfig.MY_CACHE])
Both does not work and forwards requests to hazelcast only. How to solve this? Using different cacheManager?
Typically, only 1 caching provider is in use to cache data, such as in the service or data access tier of your Spring [Boot] application using Spring's Cache Abstraction and infrastructure components, such as the CacheManager and caching annotations.
When multiple caching providers (e.g. Hazelcast and Redis) are on the classpath of your Spring Boot application, then it might be necessary to declare which caching provider (e.g. Redis) you want to [solely] use for caching purposes. With this arrangement, Spring Boot allows you to declare your intentions using the spring.cache.type property as explained in the ref doc, here (see first Tip). Valid values of this property are defined by the enumerated values in the CacheType enum.
However, if you want to cache data using multiple caching providers at once, then you need to explicitly declare your intentions using this approach as well.
DISCLAIMER: It has been awhile since I have traced through Spring Boot auto-configuration where caching is concerned, and how it specifically handles multiple caching providers on the application classpath, especially when a specific caching provider has not been declared, such as by explicitly declaring the spring.cache-type property. However, and again, this may actually be your intention, to use multiple caching providers in a single #Cacheable (or #CachePut) service or data access operation. If so, continue reading...
To do so, you typically use 1 of 2 approaches. These approaches are loosely described in the core Spring Framework's ref doc, here.
1 approach is to declare the cacheNames of the caches from each caching provider along with the CacheManager, like so:
#Service
class CustomerService {
#Cacheable(cacheNames = { "cacheOne", "cacheTwo" }, cacheManager="compositeCacheManager")
public Customer findBy(String name) {
// ...
}
}
In this case, "cacheOne" would be the name of the Cache managed by caching provider one (e.g. Redis), and "cacheTwo" would be the name of the Cache managed by caching provider two (i.e. "Hazelcast").
DISCLAIMER: You'd have to play around, but it might be possible to simply declare a single Cache name here (e.g. "Customers"), where the caches (or cache data structures in each caching provider) are named the same, and it would still work. I am not certain, but it seems logical this would work as well.
The key (no pun intended) to this example, however, is the declaration of the CacheManager using the cacheManager attribute of the #Cacheable annotation. As you know, the CacheManager is the Spring SPI infrastructure component used to find and manage Cache objects (caches from the caching providers) used for caching purposes in your Spring managed beans (such as CustomerService).
I named this CacheManager deliberately, "compositeCacheManager". Spring's Cache Abstraction provides the CompositeCacheManager implementation, which as the name suggests, composes multiple CacheManagers for use in single cache operation.
Therefore, you could do the following in you Spring [Boot] application configuration:
#Configuration
class MyCachingConfiguration {
#Bean
RedisCacheManager cacheManager() {
// ...
}
#Bean
HazelcastCacheManager hazelcastCacheManager() {
// ...
}
#Bean
CompositeCacheManager compositeCacheManager(RedisCacheManager redis, HazelcastCacheManager hazelcast) {
return new CompositeCacheManager(redis, hazelcast);
}
}
NOTE: Notice the RedisCacheManager is the "default" CacheManager declaration and cache provider (implementation) used when no cache provider is explicitly declared in a caching operation, since the bean name is "cacheManager".
Alternatively, and perhaps more easily, you can choose to implement the CacheResolver interface instead. The Javadoc is rather self-explanatory. Be aware of the Thread-safety concerns.
In this case, you would simply declare a CacheResolver implementation in your configuration, like so:
#Configuration
class MyCachingConfiguration {
#Bean
CacheResolver customCacheResolver() {
// return your custom CacheResolver implementation
}
}
Then in your application service components (beans), you would do:
#Service
class CustomerService {
#Cacheable(cacheNames = "Customers", cacheResolver="customCacheResolver")
public Customer findBy(String name) {
// ...
}
}
DISCLAIMER: I have not tested either approach I presented above here, but I feel reasonably confident this should work as expected. It may need some slight modifications, but should generally be the approach(es) you should follow.
If you have any troubles, please post back in the comments and I will try to follow up.
My goal is to create 2 caches with the newCacheManagerBuilder() API and store them off-heap
My understanding is that i need one cache for the Saga instances and one for the Associations. First I'm initializing the cache manager.
#Bean
#Scope(ConfigurableBeanFactory.SCOPE_SINGLETON)
public CacheManager cacheManager() {
CacheManager cacheManager = CacheManagerBuilder.newCacheManagerBuilder().build();
cacheManager.init();
return cacheManager;
}
#Bean
public org.ehcache.Cache<String, MySaga> sagaEhcache(CacheManager cacheManager) {
return cacheManager.createCache("sagaEhcache",
CacheConfigurationBuilder.newCacheConfigurationBuilder(
String.class,
MySaga.class,
ResourcePoolsBuilder.newResourcePoolsBuilder().offheap(sagaCacheOffHeapMb, MemoryUnit.MB)
).build()
);
}
#Bean
public org.ehcache.Cache<String, AssociationValue> sagaAssocEhcache(CacheManager cacheManager) {
return cacheManager.createCache("sagaAssocEhcache",
CacheConfigurationBuilder.newCacheConfigurationBuilder(
String.class,
AssociationValue.class,
ResourcePoolsBuilder.newResourcePoolsBuilder().offheap(sagaAssocCacheOffHeapMb, MemoryUnit.MB)
).build()
);
}
Are the mentioned mappings correct: String/MySaga and String/AssociationValue ?
Axon Framework uses the notion of a cache adapters to utilize caches for things like the CachingSagaStore or CachingEventSourcingRepository.
Both require an Axon implementation of Cache, which can be traversed back to allowing you to use either a JCacheAdapter or EhCacheAdapter.
As you are using EhCache, you would be required to use the EhCacheAdapter in your set up.
There is however one drawback with your setup.
You are using org.ehcache, thus version 3. Axon Framework however uses net.sf.ehcache, thus version 2.
So, unless there is some mechanism to revert EhCache version 3 to version 2, you will be required to actually downgrade the EhCache version in your application for now.
Update
As shown in the comments, you've implemented your own EhCacheAdapter to support the latest version of EhCache with Axon Framework. This thus leaves you with the following question:
Are the mentioned mappings correct: String/MySaga and String/AssociationValue?
Checking the CachingSagaStore implementation, you can see an CacheEntry<S> is used for the Saga and a Set<String> for the associations. The generic S stands for the Saga implementation, thus MySaga in your example.
Update 2
For a sample project leveraging Axon Framework together with version 2 of EhCache, you can check out the Axon Trader application.
I have a Spring Boot application, where I need to get data from a table when the app initializes.
I have a repository with the following code:
#Repository
public interface Bookepository extends JpaRepository<Book, Integer> {
Proveedor findByName(String name);
#Cacheable("books")
List<Proveedor> findAll();
}
Then from my service:
#Service
public class ServiceBooks {
public void findAll(){
booksRepo.findAll();
}
public void findByName(String name){
booksRepo.findByName(name);
}
}
And then I have a class that implements CommandLineRunner:
#Component
public class AppRunner implements CommandLineRunner {
private final BookRepository bookRepository;
public AppRunner(BookRepository bookRepository) {
this.bookRepository = bookRepository;
}
#Override
public void run(String... args) throws Exception {
bookRepository.findAll());
}
}
So here,when the application initializes, it queries to the Books table and caches the result. Inside the application each time I call find.all(), the cache is working, and I get the data from my cache.
So here are my 2 questions:
About Redis, I am not using Redis and I am doing database cache without any problem. So, where does Redis fit into this approach? I don't understand why everybody uses Redis when cache is working without needing other libraries.
When I call findByName(name), is there any chance to execute that query over the data I already have cached? I know I can have a cache on that method, but the cache will save data each time I search a particular name. If a name is searched for the first time, it will go to the database for that value. I don't want that, I would like that Spring performs the query using the data from the first cache where I have all Books.
The answers to your question
Redis avoids the DB call as it stores your response in Memory. You can use #cacheable even in controller or service. If you use #cacheable in controller, your request will not even execute the controller method, if it is already cached.
for FindByName, Redis provides a nice way to store the data based on keys.
Refer the link Cache Keys.
Once you request by Name, it will get the data from DB, the next time you request with same name, it will get from cache based on the key.
Coming back to your question, NO you should not do a search on your cached data, as caches are highly volatile, you cannot trust the data from cache. also searching through the cached data might affect the performance and you might need to write lines of unneeded additional code.
Spring boot manages the cache per application or per service. When you are using multiple instance of a service or app then certainly you'll want to manage the cache centrally. Because per service cache is not usable in this case because what one app caches in its own spring boot is logically not accessible by another apps.
So here Redis comes into picture. If you use Redis, then each instance of service will connect to the same Redis cache and get the same result.
I want to have a service which keeps a list inmemory so I don't need to access the database everytime. The service is accessed by a controller. Is this a valid approach or am I missing something? What about concurrent access here (from the controller)? Is this (stateful service) an anti-pattern?
#Service
public class ServiceCached {
private List<SomeObject> someObjects;
#PostConstruct
public void initOnce() {
someObjects = /** longer running loading methodd **/
}
public List<SomeObject> retrieveObjects() {
return someObjects;
}
}
Thanks!
I wouldn't call it an anti-pattern, but in my opinion loading the list from the database in a #PostConstruct method is not a good idea as you slow down the start up of your application, I'd rather use a lazy loading mechanism, but this would potentially introduce some concurrent access issues that would need to be handled.
In your example concurrent access from the controller should not be a problem as the list is loaded from a #PostConstruct method and the controller would depend on this service, therefore this service would need to be fully constructed before it is injected into the controller, therefore the list would already be loaded.
Preferably I'd suggest using Spring Caching: Caching Data with Spring, Documentation, Useful guide
Usage example:
#Cacheable("books")
public Book getByIsbn(String isbn) {
simulateSlowService();
return new Book(isbn, "Some book");
}
This way you do not need to take care of loading and evicting the objects. Once set up, the caching framework will take care of this for you.
I understand that using Spring's (3.1) built in CacheManager using the EhCache implementation, there are certain limitations when in proxy mode (the default) as per this post:
Spring 3.1 #Cacheable - method still executed
Consider the scenario I have:
#CacheEvict(value = "tacos", key = "#tacoId", beforeInvocation = true)
removeTaco(String tacoId) {
// Code to remove taco
}
removeTacos(Set<String> tacoIds) {
for (String tacoId : tacoIds) {
removeTaco(tacoId);
}
}
In this repository method, calling removeTacos(tacoIds) will not actually Evict anything from the Cache because of the limitation described above. My workaround, is that on a service layer above, if I wanted to delete multiple tacos, I'd be looping through each taco Id and passing it into removeTaco(), and never using removeTacos()
However, I'm wondering if there's another way to accomplish this.
1) Is there an SpEL expression that I could pass into the key that would tell EhCache to expire every id in the Set?
e.g. #CacheEvict(value = "tacos", key = "#ids.?[*]") // I know this isn't valid, just can't find the expression.
Or is there a way I can have removeTacos() call removeTaco and actually expire the Cached objects?
The #Caching annotation can be used to combine multiple annotations of the same type such as #CacheEvict or #CachePut, this is the example from the Spring documentation
#Caching(evict = { #CacheEvict("primary"), #CacheEvict(value="secondary", key="#p0") })
public Book importBooks(String deposit, Date date)
You can do one of two things
#CacheEvict(value = "tacos", allEntries = true)
removeTacos(Set<String> tacoIds)
which is not so bad if tacos are read a lot more than they are removed
OR
removeTacos(Set<String> tacoIds) {
for (String tacoId : tacoIds) {
getTacoService().removeTaco(tacoId);
}
}
by calling the service (proxy) you invoke the cache eviction.
AFAIK #CacheEvict supports only removing single entry (by key) or all entries in given cache, there's no way to remove at once multiple entries. If you want to put, update or remove multiple objects from cache (using annotations) and you may switch to memcached take a look at my project Simple Spring Memcached (SSM).
Self invocations don't go through the proxy so one of the solution is to switch to other mode than proxy. Anther solution (I'm not recommending it) may be keeping reference to the service in service (as an autowired field) and use it to invoke removeTaco.
Several months ago I had similar issue in one of my projects. It didn't use Spring Cache but SSM which also requires proxy. To made it work I moved caching (annotations) from service to DAO (repositories) layer. It solved problem with self invocation.