Ответ 1
Проблема была исправлена после обновления до Tomcat 9.0.4
Я использую 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);
}
}
При первом вызове он получает поиск из базы данных, а при последующих вызовах он получает его из кэша, в то время как он должен получить его из кэша при первом вызове в веб-службе?
Почему у меня такое поведение, и как я могу это исправить?
Проблема была исправлена после обновления до Tomcat 9.0.4
Пока он не влияет на запланированную задачу как таковую, когда 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.
@CacheEvict
не будет вызываться при вызове в пределах одной службы. Это связано с тем, что Spring создает прокси-сервер вокруг службы, и только вызовы от "снаружи" проходят через прокси-сервер кэша.
Решение состоит в том, чтобы либо добавить
@CacheEvict(cacheNames = Constants.CACHE_NAME_COUPONS_TYPES, allEntries = true)
to refreshAllCaches
или переместить refreshAllCaches
в новую службу, которая вызывает ICacheService.clearCouponsTypeCache
.