Spring cacheable asynchronous update while returns old cache

Viewed 390

there are rest controller, which calls @Cacheable method from @Service. My CacheManager has expireAfterWrite timeout, so when used rest -> service method after timeout, cache must be rewritten. But issue is if there are many rest calls at the same time, all of calls' threads go to service method and rewrite cache. I need do service method sync via @Cacheable(sync=true), but other calls must return old cache while first call creating new one.

package application.system.config;

import com.google.common.cache.CacheBuilder;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.cache.concurrent.ConcurrentMapCache;
import org.springframework.cache.concurrent.ConcurrentMapCacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.concurrent.TimeUnit;

@Configuration
public class CacheManagerConfiguration {

    @Value("${cache.timer.timeout}")
    private int cacheTimeout;

    @Bean("timeOutCacheManager")
    public CacheManager cacheManager() {
        return new ConcurrentMapCacheManager() {
            @Override
            protected Cache createConcurrentMapCache(String name) {
                return new ConcurrentMapCache(
                        name,
                        CacheBuilder.newBuilder()
                                .expireAfterWrite(cacheTimeout, TimeUnit.SECONDS)
                                .build().asMap(), false);
            }
        };
    }
}

@GetMapping(RestEndpoints.GET_SCHEDULE)
public ResponseEntity<SummaryReport> getSchedule(@RequestParam(required = false) String department,
                                                 @RequestParam(required = false) Long group) throws InterruptedException {
    
    return ResponseEntity.ok(scheduleService.getSummaryReport(department, group));
}
    @Transactional
    @Cacheable(value = Caches.SUMMARY_REPORT, condition = "#root.target.isCacheEnable", sync = true)
    public SummaryReport getSummaryReport(String department, Long group) {

        SummaryReport report = new SummaryReport() {{
            setDepartments(new ArrayList<>());
        }};
        
        List<DepartmentTable> departmentEntities = getDepartmentsData(department);
        List<ScheduleTable> scheduleEntities = getSchedulesData(department, group);
        List<ConstraintTable> constraintEntities = getConstraintsData(department, group);

        departmentEntities.forEach(departmentEntity -> {

            List<ScheduleTable> departmentsSchedules = scheduleEntities
                    .stream()
                    .filter(schedule -> schedule.getEnable() == 1)
                    .filter(schedule -> schedule.getDepartmentTableRef().getId().equals(departmentEntity.getId()))
                    .collect(Collectors.toList());

            List<ConstraintTable> departmentsConstraintsEntities = constraintEntities
                    .stream()
                    .filter(entity -> entity.getDepartmentTableRef().getId().equals(departmentEntity.getId()))
                    .collect(Collectors.toList());

            reportHandler.addDepartmentToReport(report, departmentEntity, departmentsSchedules, departmentsConstraintsEntities);
        });

        LOGGER.info("Caches were expired, actual data was obtained by {}", getCurrentApp());
        return report;
    }
0 Answers
Related