Caching lookups on application startup doesn't work - java

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.

Related

Bean failiing to initialize in a not deterministic way

I have a Spring Boot application that has the following bean:
#Configuration
public class ConfigStatus {
#Value("${olt.id}")
private int olt_id;
#Bean
Status createStatus() {
return new Status(olt_id);
}
}
I launch several containers with this application (20 to be exact) and in some of them the bean fails to be initialized and I don't understand why. The bean is successfuly initialized consistently in 18 of 20 containers. Memory is not an issued as the system I am using has 125gb of ram and at the time of this issue is has 90gb of free memory.
This is the class that makes use of the bean:
#DependsOn("createStatus")
#Service
public class ReceiveMessageHandler {
Logger log = LoggerFactory.getLogger(this.getClass().getName());
private static final Gson converter = new Gson();
#Autowired private Status currentStatus;
public void handleMessage(String body) {
OltRequest request = converter.fromJson(body, OltRequest.class);
request.setStartedBeingProcessedAtOlt(new Date().getTime());
Response r = new Response(request.getId(), 200, new Date().getTime());
r.setRequestEnqueuedAtOlt(currentStatus.getEnqueuedAtWorkerTimes().get(request.getId())); // line that fails due to the bean being equal to null
r.setRequestDequeuedAtOlt(new Date().getTime());
log.info("Processing request: " + request.getId());
try {
Thread.sleep(request.getDuration());
} catch(InterruptedException e) {
e.printStackTrace();
}
request.setEndedBeingProcessedAtOlt(new Date().getTime());
log.info("Finished processing request: " + request.getId());
r.setEndedHandling(new Date().getTime());
Worker.send_response(r, request.getOriginMessage().getWorker());
}
}
The error messages sais that when the bean was accessed it was null.
In the environment I have another Spring Boot app with 30 running docker containers in which this problem does not occurr, ence my disbelief :D.
I believe you should do:
#Configuration
public class ConfigStatus {
#Bean
Status currentStatus(#Value("${olt.id}") int olt_id) {
return new Status(olt_id);
}
}
I'm not 100% confident if Spring prioritises #Value over #Bean in its instantiation order, so doing this guarantees the ordering. It may be that some of your instances are trying to load the bean before the value is there, so bails out on some NPE.
Also, you should be able to drop the line: #DependsOn("createStatus") in ReceiveMessageHandler.java. Spring runs #Configuration classes before #Service classes, and #Autowired matches on type.

Load data to Ehcache 3 on Spring Boot start up

I would like to load data in cache when my Spring Boot application starts.
I know there is an integrated way using BootstrapCacheLoader in Ehcache2.
How to load data from database to Ehcache when the application starts
But I don't see this in Ehcache3.
I still can do it manually within a #postConstruct method.
But I was wondering if there is an integrating solution (Spring 5, Ehcache 3)
Thank you.
I've ended up doing it after the Spring context has been initialized.
For each element in the DB collection, I call getResourceById() which has the #Cacheable annotation, thus populating the cache for the whole DB collection.
I don't recommend to run this code in a #PostConstruct as proxies may not have been created yet and annotations like #Cacheable may not be working yet.
Running this code when a ContextRefreshedEvent event is triggered (after initialization or after refresh) is a more appropriate place to load the cache.
public class CacheLoader {
private final Logger logger = LoggerFactory.getLogger(CacheLoader.class);
#Autowired
private ResourcePermissionRepository resourcePermissionRepository;
#Autowired
private ResourcePermissionService resourcePermissionService;
#EventListener
public void onApplicationEvent(ContextRefreshedEvent event) {
logger.info("Loading cache following start/refresh event");
for (PermissionGroup permissionGroup : permissionGroupRepository.findAll()) {
permissionGroupService.getGroupById(permissionGroup.getGroupName());
}
for(ResourcePermission resourcePermission: resourcePermissionRepository.findAll()) {
resourcePermissionService.getResourceById(resourcePermission.getResourceId());
}
logger.info("Finished loading cache");
}
public class ResourcePermissionService {
private final Logger logger = LoggerFactory.getLogger(ResourcePermissionService.class);
#Autowired
private ResourcePermissionRepository resourcePermissionRepository;
#Cacheable(value = "resources", sync = true)
public ResourcePermission getResourceById(String resourceId) {
logger.info("Cache miss for resource " + resourceId);
return resourcePermissionRepository.findById(resourceId).orElse(new NullResourcePermission());
}
#CachePut(value = "resources", key = "#result.resourceId")
public ResourcePermission addResourcePermission(ResourcePermission resourcePermission) {
return resourcePermissionRepository.save(resourcePermission);
}
#CacheEvict(value = "resources")
public void deleteById(String resourceId) {
resourcePermissionRepository.deleteById(resourceId);
}
}

REST service based on Spring boot , spring caching with hazelcast failing to handle cache error

I am using Spring boot and hazelcast for my REST service for caching.
I am caching at service layer (API) using spring #Cachable annotation with custom key generator. Every thing works fine except when it throws RuntimeException from custom key generator function it is not handled by custom error class which I have added to handle error scenario.
The custom error class (CacheErrorHandler) that extends org.springframework.cache.annotation.CachingConfigurerSupport overrides all methods to handle GET,PUT,EVICT errors.
In my case I am expecting it to break into handleCacheGetError function if the custom customKeyGenerator throw RuntimeException.
Can any body point me what I am missing here or help to explain is the right way to handle caching error for cache (like HAZELCAST or REDIS etc) with spring annotation (i.e. using #Cachable).
As sample , here is what my API caching looks like
#Cacheable(cacheNames = "TestCache",keyGenerator = "customKeyGenerator")
public Response getAPIResponse(Integer param1){
...
}
Similarly , my cache configuration class which extends CachingConfigurerSupport, looks like this
#Configuration
public class CacheConfiguration extends CachingConfigurerSupport {
...
#Override
public CacheErrorHandler errorHandler() {
return new CustomHZCacheErrorHandler();
}
...
}
Here CustomHZCacheErrorHandler looks like
public class CustomHZCacheErrorHandler implements CacheErrorHandler {
private static final Logger logger = LoggerFactory.getLogger(CustomHZCacheErrorHandler.class);
public CustomHZCacheErrorHandler() {
}
public void handleCacheGetError(RuntimeException exception, Cache cache, Object key) {
logger.warn("Error while getting cache " + cache.getName() + " for Key " + key);
}
public void handleCachePutError(RuntimeException exception, Cache cache, Object key, Object value) {
logger.warn("Error while putting cache " + cache.getName() + " for Key " + key);
}
public void handleCacheEvictError(RuntimeException exception, Cache cache, Object key) {
logger.warn("Error while evicting cache " + cache.getName() + " for Key " + key);
}
public void handleCacheClearError(RuntimeException exception, Cache cache) {
logger.warn("Error while clearing cache " + cache.getName());
}
}
#NRA, according to Spring Doc, https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/cache/interceptor/CacheErrorHandler.html, CacheErrorHandler used only exceptions thrown by the cache provider, nothing else. Not for Key Generator or not even for the exceptions thrown by the annotated method.
Please see: https://github.com/spring-projects/spring-framework/blob/master/spring-context/src/main/java/org/springframework/cache/interceptor/AbstractCacheInvoker.java#L71
For MVC Controller, Spring has an annotation to help you handle errors: #ErrorHandler. You can find more details here: https://spring.io/blog/2013/11/01/exception-handling-in-spring-mvc
If not using MVC, you can define an Aspect Bean & handle all errors for that method/class/package. The trick is, KeyGenerator called using an interceptor, before the method, so you need to put an aspect to that KeyGenerator class as well to catch it.
Please see this working example: https://gist.github.com/gokhanoner/026c2b90fe3a61b93626383a61932395
Note: I didn't test throwing exception from Cache provider side, you most probably need to define CacheErrorHandler as well for that part.

cache implementation on DAO with custom refresh and evictions java

In my application, I have a scenario where I have to refresh cache each 24hrs.
I'm expecting database downtime so I need to implement a use case to refresh cache after 24hrs only if the database is up running.
I'm using spring-ehache and I did implement simple cache to refresh for each 24 hrs, but unable to get my head around to make the retention possible on database downtime .
Conceptually you could split the scheduling and cache eviction into two modules and only clear your cache if certain condition (in this case, database's healthcheck returns true) is met:
SomeCachedService.java:
class SomeCachedService {
#Autowired
private YourDao dao;
#Cacheable("your-cache")
public YourData getData() {
return dao.queryForData();
}
#CacheEvict("your-cache")
public void evictCache() {
// no body needed
}
}
CacheMonitor.java
class CacheMonitor {
#Autowired
private SomeCachedService service;
#Autowired
private YourDao dao;
#Scheduled(fixedDelay = TimeUnit.DAYS.toMillis(1))
public conditionallyClearCache() {
if (dao.isDatabaseUp()) {
service.evictCache();
}
}
}
Ehcache also allows you to create a custom eviction algorithm but the documentation doesn't seem too helpful in this case.

Combining JBehave with SpringJUnit4ClassRunner to enable transaction rollback

Essence:
How can I auto-rollback my hibernate transaction in a JUnit Test run with JBehave?
The problem seems to be that JBehave wants the SpringAnnotatedEmbedderRunner but annotating a test as #Transactional requires the SpringJUnit4ClassRunner.
I've tried to find some documentation on how to implement either rollback with SpringAnnotatedEmbedderRunner or to make JBehave work using the SpringJUnit4ClassRunner but I couldn't get either to work.
Does anyone have a (preferably simple) setup that runs JBehave storries with Spring and Hibernate and transaction auto-rollback?
Further infos about my setup so far:
Working JBehave with Spring - but not with auto-rollback:
#RunWith(SpringAnnotatedEmbedderRunner.class)
#Configure(parameterConverters = ParameterConverters.EnumConverter.class)
#UsingEmbedder(embedder = Embedder.class, generateViewAfterStories = true, ignoreFailureInStories = false, ignoreFailureInView = false)
#UsingSpring(resources = { "file:src/main/webapp/WEB-INF/test-context.xml" })
#UsingSteps
#Transactional // << won't work
#TransactionConfiguration(...) // << won't work
// both require the SpringJUnit4ClassRunner
public class DwStoryTests extends JUnitStories {
protected List<String> storyPaths() {
String searchInDirectory = CodeLocations.codeLocationFromPath("src/test/resources").getFile();
return new StoryFinder().findPaths(searchInDirectory, Arrays.asList("**/*.story"), null);
}
}
In my test steps I can #Inject everything nicely:
#Component
#Transactional // << won't work
public class PersonServiceSteps extends AbstractSmockServerTest {
#Inject
private DatabaseSetupHelper databaseSetupHelper;
#Inject
private PersonProvider personProvider;
#Given("a database in default state")
public void setupDatabase() throws SecurityException {
databaseSetupHelper.createTypes();
databaseSetupHelper.createPermission();
}
#When("the service $service is called with message $message")
public void callServiceWithMessage(String service, String message) {
sendRequestTo("/personService", withMessage("requestPersonSave.xml")).andExpect(noFault());
}
#Then("there should be a new person in the database")
public void assertNewPersonInDatabase() {
Assert.assertEquals("Service did not save person: ", personProvider.count(), 1);
}
(yes, the databaseSetupHelper methods are all transactional)
PersonProvider is basicly a wrapper around org.springframework.data.jpa.repository.support.SimpleJpaRepository. So there is access to the entityManager but taking control over the transactions (with begin/rollback) didn't work, I guess because of all the #Transactionals that are done under the hood inside that helper class.
Also I read that JBehave runs in a different context?session?something? which causes loss of controll over the transaction started by the test? Pretty confusing stuff..
edit:
Editet the above rephrasing the post to reflect my current knowledge and shortening the whole thing so that the question becomes more obvious and the setup less obstrusive.
I think you can skip the SpringAnnotatedEmbedderRunner and provide the necessary configuration to JBehave yourself. For example instead of
#UsingEmbedder(embedder = Embedder.class, generateViewAfterStories = true, ignoreFailureInStories = false, ignoreFailureInView = false)
you can do
configuredEmbedder()
.embedderControls()
.doGenerateViewAfterStories(true)
.doIgnoreFailureInStories(false)
.doIgnoreFailureInView(false);
Besides: why do you want to rollback the transaction? Typically you are using JBehave for acceptance tests, which run in a production-like environment. For example you first setup some data in the database, access it via Browser/Selenium and check for the results. For that to work the DB transaction has to be committed. Do you need to clean-up manually after your tests, which you can do in #AfterStories or #AfterScenario annotated methods.
I made it work by controlling transaction scope manually, rolling it back after each scenario. Just follow the official guide how to use Spring with JBehave and then do the trick as shown below.
#Component
public class MySteps
{
#Autowired
MyDao myDao;
#Autowired
PlatformTransactionManager transactionManager;
TransactionStatus transaction;
#BeforeScenario
public void beforeScenario() {
transaction = transactionManager.getTransaction(new DefaultTransactionDefinition());
}
#AfterScenario
public void afterScenario() {
if (transaction != null)
transactionManager.rollback(transaction);
}
#Given("...")
public void persistSomething() {
myDao.persist(new Foo());
}
}
I'm not familiar with JBehave, but it appears you're searching for this annotation.
#TransactionConfiguration(transactionManager = "transactionManager", defaultRollback = true).
You could also set defaultRollback to true in your testContext.

Categories