I am just starting to learn Spring Boot and am trying to use cache with the Google Sheets API to study Springboot. To use cache with Google Sheets in Spring Boot, I added the spring-boot-starter-cache dependency in my pom.xml file and enabled cache support by using the #EnableCaching annotation in my application's main class. I then used the #Cacheable annotation for caching the response from the Google Sheets API when calling a method to get a spreadsheet. However, the cache does not work, as the method is still being called every time.
Here is an example of the code I am using:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
#Service
public class GoogleSheetsService {
private static final Logger logger = LoggerFactory.getLogger(GoogleSheetsService.class);
#Cacheable(value = "spreadsheet", key = "#spreadsheetId")
public Spreadsheet getSpreadsheet(String spreadsheetId) {
logger.info("Getting spreadsheet from API for {}", spreadsheetId);
// Call Google Sheets API to get spreadsheet
return spreadsheet;
}
}
I expected the cache to return the cached value for the same spreadsheetId, but the method is being called every time and the "Getting spreadsheet from API for {}" log is being printed. Can you please help me understand what might be causing the cache to not work as expected?
I can doubt two things.
Are other methods of Google Sheets Service calling the spreadsheet method?
#Cacheable uses the AOP concept of Spring Framework.
So it doesn't apply to calling other methods in the same class.
Have you registered the spreadsheet with CacheManager?
With Java Config, it is as follows,
#Configuration
#EnableCaching
public class CacheConfig {
#Bean
public CacheManager cacheManager() {
ConcurrentMapCacheManager mgr = new ConcurrentMapCacheManager();
mgr.setCacheNames(asList("employees"));
return mgr;
}
}
link
Is the spreadsheet registered with cacheManager?
Related
I am trying to use Apache Ignite with Couchbase at the backend as persistence layer. I am doing this as a data grid so that any changes made to ignite in-memory cache gets written to couchbase eventually. I am implementing this with spring-boot and spring-data. The igniteConfiguration bean looks like this
#Bean(name = "igniteConfiguration")
public IgniteConfiguration igniteConfiguration() {
IgniteConfiguration igniteConfiguration = new IgniteConfiguration();
CacheConfiguration cache = new CacheConfiguration("sample");
cache.setAtomicityMode(CacheAtomicityMode.ATOMIC);
//The below line where I am confused
cache.setCacheStoreFactory(FactoryBuilder.factoryOf(CacheStoreImplementationWithCouchbaseRepositoryBean.class);
cache.setCacheStoreSessionListenerFactories()
cache.setReadThrough(true);
cache.setWriteThrough(true);
igniteConfiguration.setCacheConfiguration(cache);
return igniteConfiguration;
}
I need to provide one implementation of cacheStore interface of Ignite in order to connect couchbase as backend data store. I configured Couchbase Repository class and created a bean of it in the CacheStoreImplementationWithCouchbaseRepositoryBean.java. But this repository bean is not getting initiated because CacheStoreImplementation class is not in spring context and getting null always.
As I used spring-data. Now I have
Spring data repository for Ignite
Spring data repository for couchbase
One implementation of cacheStore interface of ignite
But not sure how to send the couchbase repository bean to the cacheStore implementation in a way so that it uses this repository class to execute crud operation in couchbase internally.
bivrantoshakil -
"I need to provide one implementation of cacheStore interface of Ignite in order to connect couchbase as backend data store. I configured Couchbase Repository class and created a bean of it in the CacheStoreImplementationWithCouchbaseRepositoryBean.java"
What documentation/tutorial are you following?
Please zip up and post your project and I will investigate.
I have a few tangential thoughts
you may which to just use Couchbase without Ignite, as the key-value api is really fast, with persistence being asynchronous and configurable.
There is a tutorial of spring data caching with couchbase at https://blog.couchbase.com/couchbase-spring-cache/ You can skip to "Adding Spring Boot Caching Capabilites".
There is tutorial Apache Ignite here - https://www.baeldung.com/apache-ignite-spring-data
Take a look here to see how store factories are configured: https://github.com/apache/ignite/tree/master/examples/src/main/java/org/apache/ignite/examples/datagrid/store
I recommend decoupling your project from CouchBase and/or spring-data to make it simpler, and then adding the appropriate components, one by one, to see where the failure is.
If you need one bean to be initialized before another bean consider using the appropriate annotations.
I found one work around for the issue I was having above. First I created one class to get the Spring Application Context
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
#Component
public class ApplicationContextProvider implements ApplicationContextAware {
private static ApplicationContext context;
/**
* Returns the Spring managed bean instance of the given class type if it
* exists. Returns null otherwise.
*
* #param beanClass
* #return
*/
public static <T extends Object> T getBean(Class<T> beanClass) {
return context.getBean(beanClass);
}
#Override
public void setApplicationContext(ApplicationContext context) throws BeansException {
// store ApplicationContext reference to access required beans later on
ApplicationContextProvider.context = context;
}
}
and then in the CacheStoreImplementationWithCouchbaseRepositoryBean.class I did this -
//Repository bean
private CouchbaseRepository repository;
#Override
public Employee load(Long key) {
repository = ApplicationContextProvider.getBean(CouchbaseRepository.class);
return repository.findById(key).orElse(null);
}
There are examples of using couchbase cache in
git clone git#github.com:spring-projects/spring-data-couchbase.git
cd spring-data-couchbase
./java/org/springframework/data/couchbase/cache/CouchbaseCacheCollectionIntegrationTests.java
./java/org/springframework/data/couchbase/cache/CouchbaseCacheIntegrationTests.java
In my Java app that based on Spring Boot, I am trying to implement a caching mechanism for the following service method:
#Override
public List<EmployeeDTO> findAllByCountry(Country country) {
final Map<Pair<UUID, String>, List<CountryTranslatable>> valueList
= countryRepository...
// code omitted for brevity
}
After several examples regarding to this issue, I decided on the approach mentioned on A Guide To Caching in Spring.
However, I am a little bit confused as it contains Spring and Spring Boot implementations and uses different annotation examples. I think I should start from 3.1. Using Spring Boot section as I use Spring Boot, but I am not sure about which Caching Annotation I should use (4.1. #Cacheable seems to be ok but I am not sure).
So, where should I put SimpleCacheCustomizer and how can I apply that approach for my service method above (findAllByCountry)? Any simple example would really be appreciated as I am new in Spring.
You don't need any customizations if you are a starter, and you want only the basics then do the following
#Configuration
#EnableCaching
public class CachingConfig {
#Bean
public CacheManager cacheManager() {
return new ConcurrentMapCacheManager();
}
}
The provided article states, return new ConcurrentMapCacheManager("addresses"); but you can use the default constructor and the relevant cache for adresses will be created later with #Cacheable("addresses"). So no need for this to be in configuration.
You also need
#Cacheable("employeesList")
#Override
public List<EmployeeDTO> findAllByCountry(Country country) {
final Map<Pair<UUID, String>, List<CountryTranslatable>> valueList
= countryRepository...
// code omitted for brevity
}
ready to go, that is the basic setup
If you want to customize the autoconfigured cachemanager then only you should implement CacheManagerCustomizer interface.
In usual cases you don't need to customize the autoconfigured cachemanager. The example has been given in the link you attached.
Your understanding on the cacheable annotation is also correct and it should work fine for you.
You can put that component class with other classes in the component scan range.
You should put your SimpleCacheCustomizer along with your others Spring configuration class. That way, your component will be scanned and loaded by Spring.
#Component
public class SimpleCacheCustomizer
implements CacheManagerCustomizer<ConcurrentMapCacheManager> {
#Override
public void customize(ConcurrentMapCacheManager cacheManager) {
cacheManager.setCacheNames(asList("employeesList", "otherCacheName"));
}
}
To use the cache with your service, add the annotation #Cacheable("employeesList")
#Cacheable("employeesList")
#Override
public List<EmployeeDTO> findAllByCountry(Country country) {
final Map<Pair<UUID, String>, List<CountryTranslatable>> valueList
= countryRepository...
// code omitted for brevity
}
If you want to verify the cache is working, just enable sql_query in your Spring configuration and check that findAllByCountry is no longer making any request to the DB.
I am very new to Micronauts and I have a fair bit of experience developing spring boot applications. With this background I was stumbled upon creating custom beans like how I used to create with #Bean annotations on Spring applications.
In my case I have a library that provides an Interface and its implementation class. I wanted to use the interface in my code and try to inject the implementation and it failes with below error
Caused by: io.micronaut.context.exceptions.NoSuchBeanException: No bean of type [io.vpv.saml.metadata.service.MetaDataParser] exists for the given qualifier: #Named('MetaDataParserImpl'). Make sure the bean is not disabled by bean requirements (enable trace logging for 'io.micronaut.context.condition' to check) and if the bean is enabled then ensure the class is declared a bean and annotation processing is enabled (for Java and Kotlin the 'micronaut-inject-java' dependency should be configured as an annotation processor).
Here is my code
#Singleton
public class ParseMetadataImpl implements ParseMetadata {
private Logger logger = LoggerFactory.getLogger(this.getClass());
#Inject
#Named("MetaDataParserImpl")
private MetaDataParser metaDataParser;
#Override
public IDPMetaData getIDPMetaData(URL url) throws IOException {
logger.info("Parsing {}", url);
logger.info("metaDataParser {}", metaDataParser);
return metaDataParser.parseIDPMetaData(url);
}
}
I am sure there is somehting wrong I am doing and need to understand what to do. I have this working by adding below code and removing annotations around metaDataParser.
#PostConstruct
public void initialize() {
//Want to Avoid This stuff
this.metaDataParser = new MetaDataParserImpl();
}
Using Spring Boot it would be possible to add a #Bean annotation to create some custom beans we can do #Autowired to inject it everywhere on our application. Is there an equivalent on Micronauths that I am missing. I went through the guide on https://docs.micronaut.io/2.0.0.M3/guide/index.html and was not able to get anything to get this working.
Can someone suggest how I can use the #Inject to inject custom beans?
Just incase you want to see this, here is the application on Github.
https://github.com/reflexdemon/saml-metadata-viewer
With the help from Deadpool and a bit of reading I got what I was looking for. The solution was creating #BeanFactory
See Javadoc here: https://docs.micronaut.io/latest/guide/ioc.html#builtInScopes
The #Prototype annotation is a synonym for #Bean because the default scope is prototype.
Thus here is an example that will match the the behavior of Spring framework
Here is the answer for anyone who also is looking for such a thing.
import io.micronaut.context.annotation.Factory;
import io.vpv.saml.metadata.service.MetaDataParser;
import io.vpv.saml.metadata.service.MetaDataParserImpl;
import javax.inject.Singleton;
#Factory
public class BeanFactory {
#Singleton
public MetaDataParser getMetaDataParser() {
return new MetaDataParserImpl();
}
}
I'm using the Spring Cloud Streams Documentation to try and work out how to connect my micro service to Kafka via the binder already downloaded in Gradle. I've tried creating a simple #Bean Function<String, String>() method within my Spring Boot Application class and have verified that it is able to talk to Kafka by using the command line to interact with the uppercase-in-0 and uppercase-out-0 topics, as is described in the beginning of the documentation confirming that the application is able to communicate with Kafka. At this point I attempted to create the following class with the expectation that it would load via auto discovery:
package com.yuknis.loggingconsumer;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
#SpringBootApplication
public class LoggingConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(LoggingConsumerApplication.class, args);
}
}
package com.yuknis.loggingconsumer.functions;
import java.util.function.Function;
public class CharCounter implements Function<String, Integer> {
/**
* Applies this function to the given argument.
*
* #param s the function argument
* #return the function result
*/
#Override
public Integer apply(String s) {
return s.length();
}
}
With the application.properties files as such:
spring.cloud.function.scan.packages:com.yuknis.loggingconsumer.functions
I'm not 100% sure what should happen now, but I'm assuming that it should see the class and automatically create a charcounter-out-0 and charcounter-in-0 topic which I could consume and publish to, with the data in those topics going through that function. That's isn't what's happening. What might I be missing? Is this class supposed to create the topic the same way that the #Bean would?
Even though each of the functions are loaded with spring.cloud.function.scan.packages set to a package and spring.cloud.function.scan.enabled set to true, it still doesn't create the topics. You'll still need to set spring.cloud.function.scan.definition to the Function, Consumer, or Supplier you'd like to have communicate with Kafka like so:
spring.cloud.function:
scan:
enabled: true
packages: com.yuknis.loggingconsumer.functions
definition: charCounter;lowercase;uppercase
After that, it will create the charCounter-in-0 and charCounter-out-0 topics, which can be mapped if necessary with the spring.cloud.function.charCounter-in-0 or spring.cloud.function.charCounter-out-0 expression property.
My application is on Spring boot 1.5.1
I have looked up all common problems related to caching not working in spring boot (calling a cacheable method from the same class, etc) and I still can't seem to pinpoint why my method is not cacheing. I am just trying to use the simple cache built into Spring (ie the concurrent hashmap).
Setup: in pom.xml I added this
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
My configuration class is the following:
#SpringBootApplication
#Configuration
#EnableCaching
#EnableScheduling
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
I have a controller class that has the following method to get a list of versions.
#RequestMapping(value = "/getVersionList", method = RequestMethod.GET)
public JSONObject getVersionList() {
JSONObject retJSON = new JSONObject();
List<String> versions = fetchVersionService.getVersionList();
retJSON.put("versions", versions);
return retJSON;
}
And the FetchVersionService class where I have the cacheable method to cache this versions list
#Cacheable("versions")
public List<String> getVersionList() {
System.out.println("If there is something in the cache, it should not hit here.");
return randomService.getVersions(); //another service class that gets versions from a remote location, which takes a long time
}
Whenever I make multiple GET calls to this function, it always goes inside the function even though I only expect to it execute the function once. For some reason it isn't cacheing the results.
Any ideas where I went wrong with my setup? Thanks a lot.
In the comments under this question #irfanhasan mentions that they had imported the wrong package but there was no certain response.
I faced the same problem. I tried to use #Cachable annotation but it wasn't work. I found that it was imported as:
import springfox.documentation.annotations.Cacheable
Instead of:
import org.springframework.cache.annotation.Cacheable;
Look carefully for your IDE imports.
In the comments under this question, #irfanhasan mentions that they had imported the wrong package and #xetra11 replied asking which package was wrong which there was no reply to. I am not #irfanhasan but it might have been the following mixup: "You can also use the standard JSR-107 (JCache) annotations (such as #CacheResult) transparently. However, we strongly advise you to not mix and match the Spring Cache and JCache annotations."