How to build and utilize a cache using CacheBuilder in Java - java

I have a method that pulls in a bunch of data. This has the potential to take a decent amount of time due to the large data set and the amount of computation required. The method that does this call will be used many times. The result list should return the same results each time. With that being said, I want to cache the results, so I only have to do that computation once. I'm supposed to use the CacheBuilder class. The script I have is essentially something like:
class CheckValidValues implements AValidValueInterface {
private ADataSourceInterface dataSource;
public CheckValidValues(ADataSourceInterface dataSource) {
this.dataSource = dataSource;
}
#Override
public void validate(String value) {
List<?> validValues = dataSource.getValidValues();
if (!validValues.contains(value)) {
// throw an exception
So I'm not even sure where I should be putting the caching method (i.e. in the CheckValidValues class or the getValidValues() method in dataSource. Also, I'm not entirely sure how you can add code into one of the methods without it instantiating the cache multiple times. Here's the route that I'm trying to take, but have no idea if it's correct. Adding above the List validValues = dataSource.getValidValues() line:
LoadingCache<String, List<?>> validValuesCache = CacheBuilder.newBuilder()
.expireAfterAccess(30, TimeUnit.SECONDS)
.build(
new CacheLoader<String, List<?>>() {
public List<?> load(#Nonnull String validValues) {
return valuesSupplier.getValidValues();
}
}
);
Then later, I'd think I could get that value with:
validValuesCache.get("validValues");
What I think should happen there is that it will do the getValidValues command and store that in the cache. However, if this method is being called multiple times, then, to me, that would mean it would create a new cache each time.
Any idea what I should do for this? I simply want to add the results of the getValidValues() method to cache so that it can be used in the next iteration without having to redo any computations.

You only want to cache a single value, the list of valid values. Use Guavas' Suppliers.memoizeWithExpiration(Supplier delegate, long duration, TimeUnit unit)
Each valid value is only existing once. So your List is essentially a Set. Back it by a HashSet (or a more efficient variant in Guava). This way the contains() is a hash table lookup instead of a sequential search inside the list.

We use Guava and Spring-Caching in a couple of projects where we defined the beans via Java configuration like this:
#Configuration
#EnableCaching
public class GuavaCacheConfig {
...
#Bean(name="CacheEnabledService")
public SomeService someService() {
return new CacheableSomeService();
}
#Bean(name="guavaCacheManager")
public CacheManager cacheManager() {
// if different caching strategies should occur use this technique:
// http://www.java-allandsundry.com/2014/10/spring-caching-abstraction-and-google.html
GuavaCacheManager guavaCacheManager = new GuavaCacheManager();
guavaCacheManager.setCacheBuilder(cacheBuilder());
return guavaCacheManager;
}
#Bean(name = "expireAfterAccessCacheBuilder")
public CacheBuilder<Object, Object> cacheBuilder() {
return CacheBuilder.newBuilder()
.recordStats()
.expireAfterAccess(5, TimeUnit.SECONDS);
}
#Bean(name = "keyGenerator")
public KeyGenerator keyGenerator() {
return new CustomKeyGenerator();
}
...
}
Note that the code above was taken from one of our integration tests.
The service, which return values should be cached is defined as depicted below:
#Component
#CacheConfig(cacheNames="someCache", keyGenerator=CustomKeyGenerator.NAME, cacheManager="guavaCacheManager")
public class CacheableService {
public final static String CACHE_NAME = "someCache";
...
#Cacheable
public <E extends BaseEntity> E findEntity(String id) {
...
}
...
#CachePut
public <E extends BaseEntity> ObjectId persist(E entity) {
...
}
...
}
As Spring-Caching uses an AOP approach, on invoking a #Cacheable annotated method Spring will first check if already a previous stored return value is available in the cache for the invoked method (depending on the cache key; we use a custom key generator therefore). If no value is yet available, Spring will invoke the actual service method and store the return value into the local cache which is available on subsequent calls.
#CachePut will always execute the service method and put the return value into the cache. This is useful if an existing value inside the cache should be replaced by a new value in case of an update for example.

Related

JOOQ listeners: context data is not cleaned between two queries

In my current project, I use java 11/JOOQ 3.15/Micronaut/Micrometer. In order to have relevant SQL metrics, I would like to put a name on my JOOQ queries.
To do that, I have tried to use the ctx.data() field combined with a custom ExecuteListener.
Let's take a really simplified listener:
#Singleton
public class JooqListener extends DefaultExecuteListener {
transient StopWatch watch;
private final MeterRegistry meterRegistry;
public JooqListener(MeterRegistry meterRegistry) {
this.meterRegistry = meterRegistry;
}
#Override
public void executeStart(ExecuteContext ctx) {
watch = new StopWatch();
}
#Override
public void fetchEnd(ExecuteContext ctx) {
Tags prometheusTag = Tags.of("queryName", ctx.configuration().data("queryName").toString());
meterRegistry.timer("sql.query.timer", prometheusTag)
.record(watch.split(), TimeUnit.NANOSECONDS);
}
// I have tried to remove the data manually, but not working
#Override
public void end(ExecuteContext ctx) {
ctx.configuration().data().remove("queryName");
}
}
If I send 2 different queries from two different repositories, like for example:
DSLContext context = DSL.using(jooqConfiguration);
context.data("queryName", "query1");
return context.select(1).from("dual").fetch();
And just after, let say I'm not attentive and I forgot to name my query:
DSLContext context = DSL.using(jooqConfiguration);
return context.select(2).from("dual").fetch();
ctx.configuration().data("queryName") in my listener will always contain "query1", which I didn't expect because ExecuteListeners are listening query by query, and furthermore, I have created two different DSLContexts. It looks like the ctx.data() cannot be cleaned but just overwritten.
Is it an expected behaviour? Is there an other object/method I should use which can be limited to the query scope? (I searched a lot on google but "data" keyword is a little bit annoying...)
Thank you
A DSLContext just wraps a Configuration. It doesn't have its own lifecycle. So, if you're modifying the Configuration.data() map through DSLContext, you're modifying a globally shared object. In other words, you must not modify Configuration.data() except for when you initialise your configuration for the first time. See this section of the manual for more details.
A better way to do what you intend to do is:
// Create a "derived" configuration, which is a new,
// independent Configuration instance
DSLContext context = DSL.using(jooqConfiguration.derive());
context.data("queryName", "query1");
return context.select(1).from("dual").fetch();
And then, in your ExecuteListener:
#Override
public void fetchEnd(ExecuteContext ctx) {
// Reading the Configuration.data() is still fine:
Tags prometheusTag = Tags.of("queryName",
ctx.configuration().data("queryName").toString());
meterRegistry.timer("sql.query.timer", prometheusTag)
.record(watch.split(), TimeUnit.NANOSECONDS);
}
#Override
public void end(ExecuteContext ctx) {
// But you shouldn't modify it
ctx.configuration().data().remove("queryName");
}

Spring 4 #Service with #RequestScope

In order to optimize sql request, I've made a service that aggregate other services consumptions to avoid unecessary calls.
(Some pages of my webapp are called millions times by day, so I want to reuse the results of database queries as many times as possible on each request)
The solution I create is this one :
My service has #RequestScope instead of default scope (Singleton)
In MyService
#Service
#RequestScope
public MyService {
private int param;
#Autowired
private OtherService otherService;
#Autowired
private OtherService2 otherService2;
private List<Elements> elements;
private List<OtherElements> otherElements;
public void init(int param) {
this.param = param;
}
public List<Elements> getElements() {
if(this.elements == null) {
//Init elements
this.elements = otherService.getElements(param);
}
return this.elements;
}
public List<OtherElements> getOtherElements() {
if(this.otherElements == null) {
//Init otherElements
this.otherElements = otherService2.getOtherElements(param);
}
return this.otherElements;
}
public String getMainTextPres() {
//Need to use lElements;
List<Elements> elts = this.getElements();
....
return myString;
}
public String getSecondTextPres() {
//Need to use lElements;
List<Elements> elts = this.getElements();
//Also Need to use lElements;
List<OtherElements> otherElts = this.getOtherElements();
....
return myString;
}
}
In my controller :
public class myController {
#Autowired MyService myService;
#RequestMapping...
public ModelAndView myFunction(int param) {
myService.init(param);
String mainTextPres = myService.getMainTextPres();
String secondTextPres = myService.getSecondTextPres();
}
#OtherRequestMapping...
public ModelAndView myFunction(int param) {
myService.init(param);
String secondTextPres = myService.getSecondTextPres();
}
}
Of course, I've simplified my example, because myService use lots of other elements, and i protect the initialization of his members attributes
This method has the advantage of doing lazy loading of the attributes only when I need them.
If somewhere in my project (in same or other controller) I only need the SecondTextPres, then calling "getSecondTextPres" will initialize both lists which is not the case in my example beacuse the first list has been initialized when "getMainTextPres" was called.
My question are :
What do you think of this way of doing things ?
May I have performance issues because I instantiate my service on each request ?
Thanks a lot !
Julien
I think that your idea is not going to fly. I you call the same or different controller this is will be different request - in that case new bean will be created (elements and other elements are empty again).
Have you been thinking about caching? Spring has nice support where you can define cache expiration, etc
It's not quite clear to me what exactly you want to optimise instantiating Service in request scope? If you are bothered about memory foot print, you could easily measure it by JMX or VisualVM.
On the other hand, you could make all Service calls pure, i.e. depending on function parameters and (ofc) database state only and instantiate the Service with default scope as Singleton.
This decision will save you reasonable amount of resources as you will not instantiate possible large object graph on each call and will not require GC to clean the thing after Request is done.
The rule of thumb is to think why exactly you need the specific Class instantiated on every call and if it doesn't keep any state specific to call, make it Singleton.
Speaking about lazy loading, it always helps to think about worst case repeated like 100 times. Will it really save you something comparing to be loaded once and for the whole Container lifetime.

Spring Boot #CachePut value is null

I have a map that stores simple POJOs, the key is the id field of the POJO.
From Spring's #CachePut annotation, I expected something like this:
JobModel jm = new JobModel();
cachemap.put(jm.getId(), jm);
Still, it inserts null values to the cache every time. If I disallow null values when I configure the Cache, I get en exception saying null is being inserted.
Code:
#SpringBootApplication
#EnableScheduling
#EnableAutoConfiguration
#EnableCaching
public class Application {
public static void main(String[] args) {
applicationContext = SpringApplication.run(Application.class, args);
}
...
private GuavaCache jobCache() {
return new GuavaCache(CacheNames.CACHE_JOB, CacheBuilder.newBuilder()
.maximumSize(999999)
.build(),
false);// or true, still null values
}
#Bean(name = "cacheManager")
public CacheManager cacheManager() {
SimpleCacheManager simpleCacheManager = new SimpleCacheManager();
simpleCacheManager.setCaches(Arrays.asList(
jobCache(),....
));
return simpleCacheManager;
}
And the Dao class, implementing an interface, the Annotations are declared in the Dao's (the class that implements the interface, not in the interface itself)
#CachePut(value = CacheNames.CACHE_JOB,key = "jm.id")
#Transactional
#Override
public void insert(JobModel jm) {...}
I tried jm.id, jm.getid(), #a0.getId(). Still, everytime I get and exception from com.google.common.base.Preconditions.checkNotNull() (or just a simple null insert). I placed a breakpoint there and I can see that the key is what I expected it to be (a guid, string), but the value is null.
Per the spring docs at http://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/cache/annotation/CachePut.html
it always causes the method to be invoked and its result to be stored in the associated cache.
Your insert method needs to return the value you want cached.
Could be something as simple as:
#CachePut(value = CacheNames.CACHE_JOB,key = "jm.id")
#Transactional
#Override
public JobModel insert(JobModel jm) {
return jm;
}
though this doesn't feel like the best way to design the interaction. You may want to look at moving the annotation to whatever method is constructing the JobModel passed to the insert method or if the JobModel is being read from a database, the method which saves the JobModel to the database may be a good place as well.
Also, you can use unless conditional property on the cache annotations as below. This parameter takes a SpEL expression that is evaluated to either true or false. If true, the method is cached - if not, it behaves as if the method is not cached.
#CachePut(value = CacheNames.CACHE_JOB, key = "jm.id", unless = "#result == null")
#Transactional
#Override
public void insert(JobModel jm) {
return jm;
}

Spring #Cacheable: Preserve old value on error

I am planning to use the Spring #Cacheable annotation in order to cache the results of invoked methods.
But this implementation somehow does not look very "safe" to me. As far as I understand, the returned value will be cached by the underlying caching engine and will be deleted when the Spring evict method is called.
I would need an implementation which does not destroy the old value until the new value was loaded. This would be required and the following scenario should work:
Cacheable method is called -> Valid result returned
Result will be cached by the Spring #Cacheable backend
Spring invalidates cache because it expired (e.g. TTL of 1 hour)
Cacheable method is called again -> Exception/null value returned!
OLD result will be cached again and thus, future invokations of the method will return a valid result
How would this be possible?
Your requirement of serving old values if the #Cacheable method throws an exception can easily be achieved with a minimal extension to Google Guava.
Use the following example configuration
#Configuration
#EnableWebMvc
#EnableCaching
#ComponentScan("com.yonosoft.poc.cache")
public class ApplicationConfig extends CachingConfigurerSupport {
#Bean
#Override
public CacheManager cacheManager() {
SimpleCacheManager simpleCacheManager = new SimpleCacheManager();
GuavaCache todoCache = new GuavaCache("todo", CacheBuilder.newBuilder()
.refreshAfterWrite(10, TimeUnit.MINUTES)
.maximumSize(10)
.build(new CacheLoader<Object, Object>() {
#Override
public Object load(Object key) throws Exception {
CacheKey cacheKey = (CacheKey)key;
return cacheKey.method.invoke(cacheKey.target, cacheKey.params);
}
}));
simpleCacheManager.setCaches(Arrays.asList(todoCache));
return simpleCacheManager;
}
#Bean
#Override
public KeyGenerator keyGenerator() {
return new KeyGenerator() {
#Override
public Object generate(Object target, Method method, Object... params) {
return new CacheKey(target, method, params);
}
};
}
private class CacheKey extends SimpleKey {
private static final long serialVersionUID = -1013132832917334168L;
private Object target;
private Method method;
private Object[] params;
private CacheKey(Object target, Method method, Object... params) {
super(params);
this.target = target;
this.method = method;
this.params = params;
}
}
}
CacheKey serves the single purpose of exposing SimpleKey attributes. Guavas refreshAfterWrite will configure the refresh time without expiring the cache entries. If the methods annotated with #Cacheable throws an exception the cache will continue to serve the old value until evicted due to maximumSize or replaced by a new value from succesful method response. You can use refreshAfterWrite in conjunction with expireAfterAccess and expireAfterAccess.
I may be wrong in my reading of the Spring code, notably org.springframework.cache.interceptor.CacheAspectSupport#execute(org.springframework.cache.interceptor.CacheOperationInvoker, org.springframework.cache.interceptor.CacheAspectSupport.CacheOperationContexts), but I believe the abstraction does not provide what you ask indeed.
Spring will not expire entries, this will be left to the underlying caching implementation.
You mention that you would like to see values even though they are expired. That's against the expiry abstraction used in most cache implementations that I know of.
Returning a previously cached value on invocation error is clearly use case specific. The Spring abstraction will simply throw the error back at the user. The CacheErrorHandler mechanism only deals with cache invocation related exceptions.
All in all, it seems to me that what you are asking for is very use case specific and thus not something an abstraction would/should offer.

Spring cache for a given request

I am writing a web application using Spring MVC. I have a interface that looks like this:
public interface SubscriptionService
{
public String getSubscriptionIDForUSer(String userID);
}
The getSubscriptionIDForUser actually makes a network call to another service to get the subscription details of the user. My business logic calls this method in multiple places in its logic. Hence, for a given HTTP request I might have multiple calls made to this method. So, I want to cache this result so that repeated network calls are not made for the same request. I looked at the Spring documentation, but could not find references to how can I cache this result for the same request. Needless to say the cache should be considered invalid if it is a new request for the same userID.
My requirements are as follows:
For one HTTP request, if multiple calls are made to getSubscriptionIDForUser, the actual method should be executed only once. For all other invocations, the cached result should be returned.
For a different HTTP request, we should make a new call and disregard the cache hit, if at all, even if the method parameters are exactly the same.
The business logic might execute its logic in parallel from different threads. Thus for the same HTTP request, there is a possibility that Thread-1 is currently making the getSubscriptionIDForUser method call, and before the method returns, Thread-2 also tries to invoke the same method with the same parameters. If so, then Thread-2 should be made to wait for the return of the call made from Thread-1 instead of making another call. Once the method invoked from Thread-1 returns, Thread-2 should get the same return value.
Any pointers?
Update: My webapp will be deployed to multiple hosts behind a VIP. My most important requirement is Request level caching. Since each request will be served by a single host, I need to cache the result of the service call in that host only. A new request with the same userID must not take the value from the cache. I have looked through the docs but could not find references as to how it is done. May be I am looking at the wrong place?
I'd like to propose another solution that a bit smaller than one proposed by #Dmitry. Instead of implementing own CacheManager we can use ConcurrentMapCacheManager provided by Spring in 'spring-context' artifact. So, the code will look like this (configuration):
//add this code to any configuration class
#Bean
#Scope(value = WebApplicationContext.SCOPE_REQUEST, proxyMode = ScopedProxyMode.TARGET_CLASS)
public CacheManager cacheManager() {
return new ConcurrentMapCacheManager();
}
and may be used:
#Cacheable(cacheManager = "cacheManager", cacheNames = "default")
public SomeCachedObject getCachedObject() {
return new SomeCachedObject();
}
I ended up with solution as suggested by herman in his comment:
Cache manager class with simple HashMap:
public class RequestScopedCacheManager implements CacheManager {
private final Map<String, Cache> cache = new HashMap<>();
public RequestScopedCacheManager() {
System.out.println("Create");
}
#Override
public Cache getCache(String name) {
return cache.computeIfAbsent(name, this::createCache);
}
#SuppressWarnings("WeakerAccess")
protected Cache createCache(String name) {
return new ConcurrentMapCache(name);
}
#Override
public Collection<String> getCacheNames() {
return cache.keySet();
}
public void clearCaches() {
cache.clear();
}
}
Then make it RequestScoped:
#Bean
#Scope(value = WebApplicationContext.SCOPE_REQUEST, proxyMode = ScopedProxyMode.TARGET_CLASS)
public CacheManager requestScopedCacheManager() {
return new RequestScopedCacheManager();
}
Usage:
#Cacheable(cacheManager = "requestScopedCacheManager", cacheNames = "default")
public YourCachedObject getCachedObject(Integer id) {
//Your code
return yourCachedObject;
}
Update:
After a while, I have found that my previous solution was incompatible with Spring-actuator. CacheMetricsRegistrarConfiguration is trying to initialize request scoped cache outside the request scope, which leads to exception.
Here is my alternative Implementation:
public class RequestScopedCacheManager implements CacheManager {
public RequestScopedCacheManager() {
}
#Override
public Cache getCache(String name) {
Map<String, Cache> cacheMap = getCacheMap();
return cacheMap.computeIfAbsent(name, this::createCache);
}
protected Map<String, Cache> getCacheMap() {
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
if (requestAttributes == null) {
return new HashMap<>();
}
#SuppressWarnings("unchecked")
Map<String, Cache> cacheMap = (Map<String, Cache>) requestAttributes.getAttribute(getCacheMapAttributeName(), RequestAttributes.SCOPE_REQUEST);
if (cacheMap == null) {
cacheMap = new HashMap<>();
requestAttributes.setAttribute(getCacheMapAttributeName(), cacheMap, RequestAttributes.SCOPE_REQUEST);
}
return cacheMap;
}
protected String getCacheMapAttributeName() {
return this.getClass().getName();
}
#SuppressWarnings("WeakerAccess")
protected Cache createCache(String name) {
return new ConcurrentMapCache(name);
}
#Override
public Collection<String> getCacheNames() {
Map<String, Cache> cacheMap = getCacheMap();
return cacheMap.keySet();
}
public void clearCaches() {
for (Cache cache : getCacheMap().values()) {
cache.clear();
}
getCacheMap().clear();
}
}
Then register a not(!) request scoped bean. Cache implementation will get request scope internally.
#Bean
public CacheManager requestScopedCacheManager() {
return new RequestScopedCacheManager();
}
Usage:
#Cacheable(cacheManager = "requestScopedCacheManager", cacheNames = "default")
public YourCachedObject getCachedObject(Integer id) {
//Your code
return yourCachedObject;
}
EHCache comes to mind right off the bat, or you could even roll-your-own solution to cache the results in the service layer. There are probably a billion options on caching here. The choice depends on several factors, like do you need the values to timeout, or are you going to clean the cache manually. Do you need a distributed cache, like in the case where you have a stateless REST application that is distributed amongst several app servers. You you need something robust that can survive a crash or reboot.
You can use Spring Cache annotations and create your own CacheManager that caches at request scope. Or you can use the one I wrote: https://github.com/rinoto/spring-request-cache

Categories

Resources