Кэширование запросов при запуске приложения не работает

Я использую Spring Boot 1.5.9 на Tomcat 9.0.2 и пытаюсь кэшировать запросы с помощью spring @Cacheable, планируя задание обновления кэша, которое запускается при запуске приложения и повторяется каждые 24 часа следующим образом:

@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);
        }
    }

}

и служба кэширования выглядит следующим образом:

@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());
    }
}

код репозитория:

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();
}

позже в моем веб-сервисе, при попытке получить поиск следующим образом:

@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);
    }
}

При первом вызове он получает поиск из базы данных, а при последующих вызовах он получает его из кэша, в то время как он должен получить его из кэша при первом вызове в веб-службе?

Почему у меня такое поведение, и как я могу это исправить?

Ответ 1

Проблема была исправлена ​​после обновления до Tomcat 9.0.4

Ответ 2

Пока он не влияет на запланированную задачу как таковую, когда refreshAllCaches() вызывается в CacheService, @CacheEvict on clearCouponsTypesCache() обходит, поскольку он вызывается из того же класса (см. этот ответ). Это приведет к тому, что кеш не будет очищен до

List<CouponType> couponTypeList = couponTypeRepository.getCoupons();
Вызывается

. Это означает, что метод @Cacheable getCoupons() не будет запрашивать базу данных, а вместо этого будет возвращать значения из кеша.

Это заставляет запланированное обновление кеша выполнять свою работу только один раз, когда кеш пуст. После этого это бесполезно.

Аннотацию @CacheEvict следует перенести в метод refreshAllCaches() и добавить к ней параметр beforeInvocation=true, поэтому кеш очищается до заполнения, а не после.


Кроме того, при использовании Spring 4/Spring Boot 1.X эти ошибки следует учитывать:

Хотя эта ошибка не влияет на эту конкретную программу, может быть хорошей идеей отделить аннотацию @Cacheable от интерфейса JpaRepository до перехода на Spring 5/Spring Boot 2.X.

Ответ 3

@CacheEvict не будет вызываться при вызове в пределах одной службы. Это связано с тем, что Spring создает прокси-сервер вокруг службы, и только вызовы от "снаружи" проходят через прокси-сервер кэша.

Решение состоит в том, чтобы либо добавить

@CacheEvict(cacheNames = Constants.CACHE_NAME_COUPONS_TYPES, allEntries = true)

to refreshAllCaches или переместить refreshAllCaches в новую службу, которая вызывает ICacheService.clearCouponsTypeCache.