Developed an API to retrieve data from a table and put it into cache in spring boot from Eclipse. Integrated with an SAP cloud platform.
DB- SAP cloud HANA DB.
Step-1: Call an API from the postman.
Step-2: Method invoked. The method annotated with #Cacheable("dictionary")
** ** -->At this time the values from DB 4000+ records get cached
--> within 5 mins if postman executes, values are returning faster based on execution time as expected.
Step-3: Every 5 mins, #CacheEvict is scheduled.
** Before Eviction starts, updating a DB column "comments" in DB **
Step-4: Scheduled Eviction method executed.
#Service
public class CacheEvit {
#Autowired
private CacheManager cacheManager;
#Scheduled(cron = "0 0/5 * * * ?") // execure after every 5 min
public void clearCacheSchedule() {
System.err.println("inside clear scheduled--" + cacheManager.getCacheNames());
Cache c = cacheManager.getCache("dictionary");
for (String name : cacheManager.getCacheNames()) {
System.err.println("inside clear scheduled before clear in for loop" + cacheManager.getCacheNames());
cacheManager.getCache("dictionary").clear(); // clear cache by name
}
if (c != null) {
System.err.println("cleared succ -- " + cacheManager.getCacheNames());
} else {
System.err.println("cache is not null");
}
}
}
#Cacheable("dictionary")
public List<DictionaryEntity> getDictionaryData() {
System.err.println("inside getDictionaryData");
return dictionaryServiceImpl.getDictionary();
}
#EnableScheduling
#EnableCaching --> declared in Starter class.
#Bean
public CacheManager cacheManager() {
SimpleCacheManager cacheManager = new SimpleCacheManager();
cacheManager.setCaches(Arrays.asList(
new ConcurrentMapCache("dictionary")));
return cacheManager;
}
These both are in different java classes and different services.
Problem: As cache got cleared, The method with #Cachable is executed ( observed by printing log ) but the value of cache is not loaded newly as we edited comments, still its returning old values
For me the issue was that the cacheable method was in the same class, which you mentioned are not in your case. However, cacheable will not get called if the classes are part of the same bean. I separated them and it works fine for me. Hope it helps.
Related
I have spring boot app with multiple databases and in services I want to talk to those databases and combine info from 2 of them in the same method.
#Service
#Transactional("transactionManager1")
public class Service1 {
#Service
#Transactional("transactionManager2")
public class Service2 {
I have some methods in Service1 that also call methods from Service2 to get some data from other database.
It worked fine until I added threading. In Service1 I added CompletableFuture.supplyAsync(....) that calls method from Service1 that in turn eventually calls Service2 methods. And at some point it just throws me TransactionException.
To fix this I thought I would manually create transaction before CompletableFuture.supplyAsync(....) and commit it afterwards. When I was searching how to do that I got idea from CompletableFuture vs Spring Transactions and started writing the following code (I use PlatformTransactionManager instead):
#TimeLimiter(name = "method1")
public CompletableFuture<Void> method1Async(long arg1) {
TransactionDefinition txDef = new DefaultTransactionDefinition();
TransactionStatus txStatus = platformTransactionManager.getTransaction(txDef);
CompletableFuture<Void> x = null;
try {
x = CompletableFuture.supplyAsync(() -> {
Void y = this.method1(arg1);
platformTransactionManager.commit(txStatus);
return y;
})
.toCompletableFuture();
} catch (Exception e){
platformTransactionManager.rollback(txStatus);
}
return x;
}
But after still getting exception about transaction I realized that since I use multiple databases I probably need multiple transaction managers. But when I was trying to learn those transaction managers I didn't notice any way to tell for which database I want it.
How Do I create another transaction for transactionManager2?
I am trying to create and delete a cache using RedisCacheManager with spring-boot and want to use HSET programmatically but am unable to do it. I am able to do it as a simple SET but not as HSET.
This is the bean that I have created.
#Bean
public RedisCacheManager cacheManager(RedisConnectionFactory connectionFactory) {
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig() //
.entryTtl(Duration.ofHours(1)) //
.disableCachingNullValues();
return RedisCacheManager.builder(connectionFactory) //
.cacheDefaults(config) //
.build();
And even made the class where I am making the call as #RedisHash but no luck.
#Service
#Slf4j
#RedisHash(value = "CURRENT_CALLS")
public class CacheCleanupService implements Serializable {
#CacheEvict(value = "CURRENT_CALLS" ,key = "(#cacheKey)")
public void redisCacheNumberCleanup(String cacheKey) {
log.info("Key CLEANUP from the cache: {}", cacheKey);
}
#Cacheable(value = "CURRENT_CALLS", key = "(#cacheKey)")
public String redisCacheNumberStore(String cacheKey) {
log.info("Key Add from the cache: {}", cacheKey);
return cacheKey;
}
}
The o/p I am getting is this when calling these above methods from another #Service class.
127.0.0.1:6379> keys CURRENT_CALLS:*
1) "CURRENT_CALLS::+15109100689:+15134631989"
2) "CURRENT_CALLS::+15109100648:+15134631989"
3) "CURRENT_CALLS::+15109100688:+15134631988"
127.0.0.1:6379> get "CURRENT_CALLS::+15109100648:+15134631989"
"+15109100648:+15134631989"
However, I want the o/p like this
127.0.0.1:6379> keys CURRENT_CALLS
1) "CURRENT_CALLS"
127.0.0.1:6379> hgetall "CURRENT_CALLS"
1) "+15109100648:+15134631989"
2) "1"
3) "+15109100688:+15134631988"
4) "2"
5) "+15109100689:+15134631989"
6) "3"
7) "+17326667726:+17722915819"
8) "4"
How to achieve this through spring-boot annotations.
It seems not likely to play around Redis Hashes data type using the annotation based way.
In the Cache Abstraction section of Spring Framework documentation, all the usages are based on basic cache operation (get key and set key value, if in the context of Redis)
As you mentioned that you are marking your service class with #RedisHash, though,
#RedisHash is used on entities, i.e., the models to be persisted in Redis.
See also
another SO question Spring Boot Caching with Redis and Store value as Redis Hash
tut on developer.redis.com Caching REST Services with Redis
Does Spring and Hibernate supports Session sharing between two different (nested or sequenced) physical Transactions ?
I know that Spring supports nested transactions, but it is the same Physical Transaction just with save points, i.e. nested transaction is separated logically :
From Spring docs:
PROPAGATION_NESTED uses a single physical transaction with multiple savepoints that it can roll back to.
So, can i achive behavior similar to:
#Transactional
void invokeOuterTransaction() {
invokeInnerTransaction();
}
#Transactional
void invokeInnerTransaction() {
// here are the same Session as in 'invokeOuterTransaction',
// but this transaction is new PHYSICAL transaction
}
So, exploring this issue, using setup below, i discovered, that Hibernate Session "per request" and not "per transaction" is quite interesting.
Setup:
Hibernate 5, Spring 5, PostgreSQL
Below is a quick java-like pseudo-code for short:
#Controller {
#Inject FirstService fServ;
#Inject SecondService sServ;
#RequestMapping
handleHttpRequest() {
fServ.invokeFirstTransactional();
sServ.invokeSecondTransactional();
}
}
FirstService {
#Transactional
void invokeFirstTransactional() {
// Session object system hashcode = 187000543
// Thread object system hashcode = 167000522
// Transaction_ID in database = 650
}
}
SecondService {
#Transactional
void invokeSecondTransactional() {
// Session object system hashcode = 187000543
// Thread object system hashcode = 167000522
// Transaction_ID in database = 651
}
}
As you can see - same Hibernate Session, Same Thread, but DIFFERENT PHYSICAL transactions !
I am using Spring Boot 1.5.9 on Tomcat 9.0.2 and I am trying to cache lookups using spring #Cacheable scheduling a cache refresh job that runs on application startup and repeats every 24 hours as follows:
#Component
public class RefreshCacheJob {
private static final Logger logger = LoggerFactory.getLogger(RefreshCacheJob.class);
#Autowired
private CacheService cacheService;
#Scheduled(fixedRate = 3600000 * 24, initialDelay = 0)
public void refreshCache() {
try {
cacheService.refreshAllCaches();
} catch (Exception e) {
logger.error("Exception in RefreshCacheJob", e);
}
}
}
and the cache service is as follows:
#Service
public class CacheService {
private static final Logger logger = LoggerFactory.getLogger(CacheService.class);
#Autowired
private CouponTypeRepository couponTypeRepository;
#CacheEvict(cacheNames = Constants.CACHE_NAME_COUPONS_TYPES, allEntries = true)
public void clearCouponsTypesCache() {}
public void refreshAllCaches() {
clearCouponsTypesCache();
List<CouponType> couponTypeList = couponTypeRepository.getCoupons();
logger.info("######### couponTypeList: " + couponTypeList.size());
}
}
the repository code:
public interface CouponTypeRepository extends JpaRepository<CouponType, BigInteger> {
#Query("from CouponType where active=true and expiryDate > CURRENT_DATE order by priority")
#Cacheable(cacheNames = Constants.CACHE_NAME_COUPONS_TYPES)
List<CouponType> getCoupons();
}
later in my webservice, when trying to get the lookup as follows:
#GET
#Produces(MediaType.APPLICATION_JSON + ";charset=utf-8")
#Path("/getCoupons")
#ApiOperation(value = "")
public ServiceResponse getCoupons(#HeaderParam("token") String token, #HeaderParam("lang") String lang) throws Exception {
try {
List<CouponType> couponsList = couponRepository.getCoupons();
logger.info("###### couponsList: " + couponsList.size());
return new ServiceResponse(ErrorCodeEnum.SUCCESS_CODE, resultList, errorCodeRepository, lang);
} catch (Exception e) {
logger.error("Exception in getCoupons webservice: ", e);
return new ServiceResponse(ErrorCodeEnum.SYSTEM_ERROR_CODE, errorCodeRepository, lang);
}
}
The first call it gets the lookup from the database and the subsequent calls it gets it from the cache, while it should get it from the cache in the first call in the web service?
Why am I having this behavior, and how can I fix it?
The issue was fixed after upgrading to Tomcat 9.0.4
While it's not affecting the scheduled task per se, when refreshAllCaches() is invoked in the CacheService, #CacheEvict on clearCouponsTypesCache() is bypassed since it's invoked from the same class (see this answer). It will lead to cache not being purged before
List<CouponType> couponTypeList = couponTypeRepository.getCoupons();
is invoked. This means that the #Cacheable getCoupons() method will not query the database, but will instead return values from the cache.
This makes the scheduled cache refresh action to do its work properly only once, when the cache is empty. After that it's useless.
The #CacheEvict annotation should be moved to refreshAllCaches() method and add beforeInvocation=true parameter to it, so the cache is purged before being populated, not after.
Also, when using Spring 4 / Spring Boot 1.X, these bugs should be taken into consideration:
https://github.com/spring-projects/spring-boot/issues/8331
https://jira.spring.io/browse/SPR-15271
While this bug doesn't seem to affect this specific program, it might be a good idea to separate #Cacheable annotation from JpaRepository interface until migration to Spring 5 / Spring Boot 2.X.
#CacheEvict won't be invoked when called within the same service. This is because Spring creates a proxy around the service and only calls from "outside" go through the cache proxy.
The solution is to either add
#CacheEvict(cacheNames = Constants.CACHE_NAME_COUPONS_TYPES, allEntries = true)
to refreshAllCaches too, or to move refreshAllCaches into a new service that calls ICacheService.clearCouponsTypeCache.
We are using Spring cache for Caching few elements. So whenever user requests same key of element, it goes to cache and check if it is available or not. If it is available it fetches from cache otherwise it executes the method. But before all this I want to implement one more functionality in my cache.
Requirement : On hourly basis my spring cache will check, if any element in the cache exists for more than an hour, it will remove it.
I searched on google but did not find any satisfactory link. Can someone help me or provide me a link for same ?
You need to set the time to live(TTL) for your cache. How you do this depends on your cash provider. A couple examples can be found here:
Can I set a TTL for #Cacheable
#EnableCaching
#Configuration
public class CacheConfiguration implements CachingConfigurer {
#Override
public CacheManager cacheManager() {
ConcurrentMapCacheManager cacheManager = new ConcurrentMapCacheManager() {
#Override
protected Cache createConcurrentMapCache(final String name) {
return new ConcurrentMapCache(name,
CacheBuilder.newBuilder().expireAfterWrite(30, TimeUnit.MINUTES).maximumSize(100).build().asMap(), false);
}
};
return cacheManager;
}
#Override
public KeyGenerator keyGenerator() {
return new DefaultKeyGenerator();
}
}