I'm using spring-boot-starter-parent version 2.0.1
these are the application.properties
spring.cache.type=redis
spring.cache.cache-names=edges
spring.cache.redis.cache-null-values=false
spring.cache.redis.time-to-live=60000000
spring.cache.redis.key-prefix=true
spring.redis.host=localhost
spring.redis.port=6379
This is the main class.
#SpringBootApplication
#EnableAsync
#EnableCaching
public class JanusApplication {
public static void main(String[] args) {
SpringApplication.run(JanusApplication.class, args);
}
}
This's the java method that I want to cache result of it.
#Service
public class GremlinService {
#Cacheable(value = "edges")
public String getEdgeId(long fromId, long toId, String label) {
// basically finds an edge in graph database
}
public Edge createEdge(Vertex from, Vertex to, String label){
String edgeId = getEdgeId((Long) from.id(), (Long) to.id(), label);
if (!Util.isEmpty(edgeId)) {
// if edge created before, use its id to query it again
return getEdgeById(edgeId);
} else {
return createNewEdge((Long) from.id(), (Long) to.id(), label);
}
}
}
I don't have any other configuration for redis or cache. Although it does not throw any error, it does not cache anything. I check with redis-cli.
In order for the caching to work, the function to be cached must be called from an external class.
That's because Spring creates a proxy for your bean and resolves the caching when the method call passes through that proxy.
If the function call is done internally, it doesn't pass the proxy and thus the caching is not applied.
Here's another answer that adresses this question: Spring cache #Cacheable method ignored when called from within the same class
Related
I would like to initiate a simple POJO that generates an array of random Strings when initiated (or when the Spring Boot application starts). This array of Strings has to be shared across every controller in the Spring Boot application, and it cannot be different for different controllers. The class and methods (of this shared POJO) are internal to a Spring Boot application and are accessed by calling the getters in the controller methods (only).
Furthermore, I would like to avoid using the application.properties. The best solution would be java-only (no database such as H2 or offloading the POJO onto a file). Also, using the sessions will not help.
Something like this would help:
http://www.masterspringboot.com/security/authentication/securing-spring-boot-with-in-memory-basic-authentication
How can I accomplish such a design ?
My idea is to simply use a micro-service and launch it separately, but I would like to confirm if there is something else I can do within the single Spring Boot application.
You can create a singleton class that holds your values:
public class Main {
public static void main(String[] args) {
DataHolder dataHolder = DataHolder.getInstance();
String[] arr = dataHolder.getArr();
}
}
class DataHolder {
private static DataHolder instance = null;
private String[] arr = new String[10];
public static DataHolder getInstance() {
if (instance == null)
instance = new DataHolder();
return instance;
}
private DataHolder() {
fillArray();
}
private void fillArray() {
// use this method to fill your array
}
public String[] getArr() {
return arr;
}
}
To answer my own question, the solution was to use the in-memory provided by the mapDB, and generate all the data during the spring-boot initiating itself...
https://mvnrepository.com/artifact/org.mapdb/mapdb
The source code example can be found using the search engines...
I am calling a function which has CacheEvict annotation on it. This is being called from a function that is itself executed asynchronously.
It seems that the cache is not being evicted after the function has been executed.
Here is sample code
#Async("executor1")
public void function1()
{
// do something
anotherFunction("name", 123, 12);
// do something more
}
#CacheEvict(cacheNames = {"cache1", "cache2", "cache3"}, key = "#testId")
public List<Integer> anotherFunction(String name, int testId, int packageId)
{
// some code here
}
What I want is that entries corresponding to testId should be cleared from all the caches.
However, in another call, I can see old entries of cache1. function1 is being called from the controller. Both these functions are present inside the service. Now, Is this configuration correct? If yes, What may be the possible reasons that cache is not being cleared?
Any help appreciated. Thanks in advance.
I think your problem is that Spring proxies are not reentrant. To implement Async and CacheEvict, Spring creates a proxy. So, in your example, the call stack will be:
A -> B$$proxy.function1() -> B.function1() -> B.anotherFunction()
B$$proxy contains the logic for async and eviction. Which won't apply when calling directly anotherFunction. In fact, even if you remove the #Async, it will still don't work.
A trick you can use is to inject the proxied bean into the class. To delegate to the proxy of the class instead this.
public class MyClass {
private MyClass meWithAProxy;
#Autowired
ApplicationContext applicationContext;
#PostConstruct
public void init() {
meWithAProxy = applicationContext.getBean(MyClass.class);
}
#Async("executor1")
public void function1() {
meWithAProxy.anotherFunction("name", 123, 12);
}
#CacheEvict(cacheNames = "cache1", key = "#testId")
public List<Integer> anotherFunction(String name, int testId, int packageId) {
return Collections.emptyList();
}
}
It works. But there's a catch. If you now call anotherFunction directly, it won't work. I consider this to be a Spring bug and will file it as is.
I would like to use Oracle NoSQL database together with Spring data. The aim is to access the data over spring data repositories and even use spring data rest on top of it.
So I think the spring-data-keyvalue project would help me, to implement an adapter for Oracle NoSQL KV.
I tried to understand the documentation of spring-data-keyvalue (http://docs.spring.io/spring-data/keyvalue/docs/current/reference/html/#key-value.core-concepts), but didn't get the idea.
An example/tutorial about how to implement an adapter from scratch would be very helpful.
What I have is this configuration class where I provide a custom KeyValueAdapter. Now if I use CrudRepository methods it uses my custom adapter.
#Configuration
#EnableMapRepositories
public class KeyValueConfig {
#Bean
public KeyValueOperations keyValueTemplate() {
return new KeyValueTemplate(new OracleKeyValueAdapter());
}
}
The OracleKeyValueAdapter is an implementation of KeyValueAdapter. I got this from the spring-data-keyvalue-redis project (https://github.com/christophstrobl/spring-data-keyvalue-redis/blob/master/src/main/java/org/springframework/data/keyvalue/redis/RedisKeyValueAdapter.java)
public class OracleKeyValueAdapter extends AbstractKeyValueAdapter {
private KVStore store;
public OracleKeyValueAdapter() {
String storeName = "kvstore";
String hostName = "localhost";
String hostPort = "5000";
store = KVStoreFactory.getStore
(new KVStoreConfig(storeName, hostName + ":" + hostPort));
}
//Custom implementations:
#Override
public Object put(Serializable serializable, Object o, Serializable
serializable1) {
return null;
}
#Override
public boolean contains(Serializable serializable, Serializable
serializable1) {
return false;
}
.
.
.
Now I'm trying to implement this OracleKeyValueAdapter, but i don't know if that does even make sense.
Can you help me?
You might want to start with how spring-data-keyvalue is implemented over Redis, the link here should be a good starting point - http://docs.spring.io/spring-data/data-keyvalue/docs/1.0.0.BUILD-SNAPSHOT/reference/redis.html
Let me know how that goes, I am interested in what you are trying to accomplish.
The following configuration should work (tested on v2.4.3)
#Configuration
#EnableMapRepositories
public class Configuration {
#Bean
public KeyValueOperations mapKeyValueTemplate() {
return new KeyValueTemplate(keyValueAdapter());
}
#Bean
public KeyValueAdapter keyValueAdapter() {
return new YourKeyValueAdapter();
}
}
The name (mapKeyValueTemplate) of the KeyValueOperations bean is important here but it can also be changed as followed:
#Configuration
#EnableMapRepositories(keyValueTemplateRef = "foo")
public class Configuration {
#Bean
public KeyValueOperations foo() {
return new KeyValueTemplate(keyValueAdapter());
}
#Bean
public KeyValueAdapter keyValueAdapter() {
return new YourKeyValueAdapter();
}
}
I saw sources of Spring KeyValue Repository:
https://github.com/spring-projects/spring-data-keyvalue
I recomend to understand, how Spring Repository work inside.
If you want to realise own repository (CustomKeyValueRepository), you must create at least 6 classes:
EnableCustomKeyValueRepositories - annotation to enable repository type in your project.
CustomKeyValueRepositoriesRegistrar - registrator for this annotaion.
CustomKeyValueRepository - repository
CustomKeyValueRepositoryConfigurationExtension - implementation of Spring ConfigurationExtension.
CustomKeyValueAdapter - implementation of custom adapter for your data store.
CustomKeyValueConfiguration - configuration of beans Adapter and Template.
I code Infinispan KeyValue Repository by this way:
https://github.com/OsokinAlexander/infinispan-spring-repository
I also write article about this:
https://habr.com/ru/post/535218/
In Chrome you can translate it to your language.
The simplest way you can try implement only CustomKeyValueAdapter and Configuration. In Configuration you must redefine Spring KeyValueAdapter bean and KeyValueTemplate (it is very important that the name of the bean is with a lowercase letter, that's the only way it worked for me):
#Configuration
public class CustomKeyValueConfiguration extends CachingConfigurerSupport {
#Autowired
private ApplicationContext applicationContext;
#Bean
public CustomKeyValueAdapter getKeyValueAdapter() {
return new CustomKeyValueAdapter();
}
#Bean("keyValueTemplate")
public KeyValueTemplate getKeyValueTemplate() {
return new KeyValueTemplate(getKeyValueAdapter());
}
}
I have problems with save some values in #Service method.
My code:
#Service(value = "SettingsService")
public class SettingsService {
...
public String getGlobalSettingsValue(Settings setting) {
getTotalEhCacheSize();
if(!setting.getGlobal()){
throw new IllegalStateException(setting.name() + " is not global setting");
}
GlobalSettings globalSettings = globalSettingsRepository.findBySetting(setting);
if(globalSettings != null)
return globalSettings.getValue();
else
return getGlobalEnumValue(setting)
}
#Cacheable(value = "noTimeCache", key = "#setting.name()")
public String getGlobalEnumValue(Settings setting) {
return Settings.valueOf(setting.name()).getDefaultValue();
}
My repository class:
#Repository
public interface GlobalSettingsRepository extends CrudRepository<GlobalSettings, Settings> {
#Cacheable(value = "noTimeCache", key = "#setting.name()", unless="#result == null")
GlobalSettings findBySetting(Settings setting);
It should work like this:
get value form DB if data exist,
if not save value from enum.
but it didn't save any data from DB or enum.
My cache config:
#Configuration
#EnableCaching
public class CacheConfig {
#Bean
public EhCacheCacheManager cacheManager(CacheManager cm) {
return new EhCacheCacheManager(cm);
}
#Bean
public EhCacheManagerFactoryBean ehcache() {
EhCacheManagerFactoryBean ehCacheManagerFactoryBean = new EhCacheManagerFactoryBean();
ehCacheManagerFactoryBean.setConfigLocation(new ClassPathResource("ehcache.xml"));
return ehCacheManagerFactoryBean;
}
}
I have some example to make sure that cache is working in my project in rest method:
#RequestMapping(value = "/system/status", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<?> systemStatus() {
Object[] list = userPuzzleRepository.getAverageResponseByDateBetween(startDate, endDate);
...
}
public interface UserPuzzleRepository extends CrudRepository<UserPuzzle, Long> {
#Cacheable(value = "averageTimeAnswer", key = "#startDate")
#Query("select AVG(case when up.status='SUCCESS' OR up.status='FAILURE' OR up.status='TO_CHECK' then up.solvedTime else null end) from UserPuzzle up where up.solvedDate BETWEEN ?1 AND ?2")
Object[] getAverageResponseByDateBetween(Timestamp startDate, Timestamp endDate);
and it's work well.
What am I doing wwrong?
You have two methods in your SettingsService, one that is cached (getGlobalEnumValue(...)) and another one that isn't cached, but calls the other method (getGlobalSettingsValue(...)).
The way the Spring cache abstraction works however is by proxying your class (using Spring AOP). However, calls to methods within the same class will not call the proxied logic, but the direct business logic beneath. This means caching does not work if you're calling methods in the same bean.
So, if you're calling getGlobalSettingsValue(), it will not populate, nor use the cache when that method calls getGlobalEnumValue(...).
The possible solutions are:
Not calling another method in the same class when using proxies
Caching the other method as well
Using AspectJ rather than Spring AOP, which weaves the code directly into the byte code at compile time, rather than proxying the class. You can switch the mode by setting the #EnableCaching(mode = AdviceMode.ASPECTJ). However, you'll have to set up load time weaving as well.
Autowire the service into your service, and use that service rather than calling the method directly. By autowiring the service, you inject the proxy into your service.
The problem is in the place you call your cacheable method from. When you call your #Cacheable method from same class, you just call it from this reference, which means it doesn't wrapped by Spring's proxy, so Spring can't catch your invocation to handle it.
One on ways to solve this problem is to #Autowired service to itself and just call methods you expected spring have to handle by this reference:
#Service(value = "SettingsService")
public class SettingsService {
//...
#Autowired
private SettingsService settingsService;
//...
public String getGlobalSettingsValue(Settings setting) {
// ...
return settingsSerive.getGlobalEnumValue(setting)
//-----------------------^Look Here
}
#Cacheable(value = "noTimeCache", key = "#setting.name()")
public String getGlobalEnumValue(Settings setting) {
return Settings.valueOf(setting.name()).getDefaultValue();
}
}
But if you have such problems it means your classes are take on too much and aren't comply with the principle of "single class - single responsibility". The better solution would be to move method with #Cacheable to dedicated class.
I've Spring cache implemented as below
#Component
public class KPCacheExample {
private static final Logger LOG = LoggerFactory.getLogger(KPCacheExample.class);
#CachePut(value="kpCache")
public String saveCache(String userName, String password){
LOG.info("Called saveCache");
return userName;
}
#Cacheable(value="kpCache")
public String getCache(String userName, String password){
LOG.info("Called getCache");
return "kp";
}
}
And Java Config file
#Configuration
#ComponentScan(basePackages={"com.kp"})
public class GuavaCacheConfiguration {
#Bean
public CacheManager cacheManager() {
GuavaCacheManager guavaCacheManager = new GuavaCacheManager("kpCache");
guavaCacheManager.setCacheBuilder(CacheBuilder.newBuilder().expireAfterAccess(2000, TimeUnit.MILLISECONDS).removalListener(new KPRemovalListener()));
return guavaCacheManager;
}
}
By default the spring uses put method in the cache interface to update/put values in the cache. How can I force the spring to use putifabsent method to be invoked, such that I can get null value if cache is missed or in other wards first request to the method with unique username and password should return null and subsequent request to that username and password should return username.
Well, looking through Spring's Cache Abstraction source, there does not appear to be a configuration setting (switch) to default the #CachePut to use the "atomic" putIfAbsent operation.
You might be able to simulate the "putIfAbsent" using the unless (or condition) attribute(s) of the #CachePut annotation, something like (based on the Guava impl)...
#CachePut(value="Users", key="#user.name" unless="#root.caches[0].getIfPresent(#user.name) != null")
public User save(User user){
return userRepo.save(user);
}
Also note, I did not test this expression, and it would not be "atomic" or portable using a different Cache impl. The expression ("#root.caches[0].get(#user.name) != null") maybe more portable.
Giving up the "atomic" property may not be desirable so you could also extend the (Guava)CacheManager to return a "custom" Cache (based on GuavaCache) that overrides the put operation to delegate to "putIfAbsent" instead...
class CustomGuavaCache extends GuavaCache {
CustomGuavaCache(String name, com.google.common.cache.Cache<Object, Object> cache, boolean allowNullValues) {
super(name, cache, allowNullValues);
}
#Override
public void put(Object key, Object value) {
putIfAbsent(key, value);
}
}
See the GuavaCache class for more details.
Then...
class CustomGuavaCacheManager extends GuavaCacheManager {
#Override
protected Cache createGuavaCache(String name) {
return new CustomGuavaCache(name, createNativeGuavaCache(name), isAllowNullValues());
}
}
See GuavaCacheManager for further details, and specifically, have a look at line 93 and createGuavaCache(String name).
Hope this helps, or at least gives you some more ideas.