TGViewer
Библиотека джависта | Java, Spring, Maven, Hibernate Библиотека джависта | Java, Spring, Maven, Hibernate @javaproglib · 22K subscribers
Post #7464 2.89K
🔧 Spring Cache + Redis: настройка которая не сломается в production

@Cacheable выглядит просто. До первого падения сериализации в production или гонки при cache stampede.

🔹 Решение

▪️ Конфигурация RedisCacheManager

@Configuration
@EnableCaching
public class CacheConfig {

@Bean
public RedisCacheManager cacheManager(RedisConnectionFactory factory) {
RedisCacheConfiguration defaults = RedisCacheConfiguration
.defaultCacheConfig()
.entryTtl(Duration.ofMinutes(10))
.serializeKeysWith(
RedisSerializationContext.SerializationPair.fromSerializer(
new StringRedisSerializer()))
.serializeValuesWith(
RedisSerializationContext.SerializationPair.fromSerializer(
new GenericJackson2JsonRedisSerializer()))
.disableCachingNullValues(); // null не кешируем

Map<String, RedisCacheConfiguration> configs = Map.of(
"users", defaults.entryTtl(Duration.ofHours(1)),
"products", defaults.entryTtl(Duration.ofMinutes(5)),
"sessions", defaults.entryTtl(Duration.ofDays(1))
);

return RedisCacheManager.builder(factory)
.cacheDefaults(defaults)
.withInitialCacheConfigurations(configs)
.build();
}
}


▪️ Использование с явным ключом

@Cacheable(value = "users", key = "#userId", unless = "#result == null")
public User findById(Long userId) { ... }

@CacheEvict(value = "users", key = "#user.id")
public User update(User user) { ... }

// Evict всего кеша при массовых операциях
@CacheEvict(value = "users", allEntries = true)
public void importUsers(List<User> users) { ... }


▪️ Защита от cache stampede через @CachePut + Lock

@Service
public class ProductService {

private final Map<Long, Object> locks = new ConcurrentHashMap<>();

public Product getProduct(Long id) {
Product cached = cacheManager.getCache("products").get(id, Product.class);
if (cached != null) return cached;

// только один поток пересчитывает, остальные ждут
Object lock = locks.computeIfAbsent(id, k -> new Object());
synchronized (lock) {
// double-check после захвата лока
cached = cacheManager.getCache("products").get(id, Product.class);
if (cached != null) return cached;

Product product = repository.findById(id).orElseThrow();
cacheManager.getCache("products").put(id, product);
return product;
}
}
}


Сериализация через GenericJackson2JsonRedisSerializer сохраняет информацию о типе в JSON (@class поле). Это позволяет десериализовать полиморфные структуры, но требует чтобы все классы в графе объекта были сериализуемы Jackson'ом. Проблема обычно вылезает при кешировании Hibernate entity с lazy коллекциями — они не сериализуются и роняют приложение.

⚠️ Никогда не кешируйте Hibernate entity напрямую. Создавайте DTO, сериализуйте его.

══════ Навигация ══════
Вакансии • Задачи • Собесы

🐸 Библиотека джависта

#Enterprise
  • 👍 7
  • ❤ 1
  • 🔥 1
  • 😁 1
More from @javaproglib
  1. Oct 17, 2026☕️ Скользящие окна в Stream без ручного буфера Нужно сравнить соседние измерения или обраб…
  2. Oct 16, 2026🧑‍💻 Какие навыки помогают вам в разработке в 2026 году? Что вы чаще используете в работе…
  3. Oct 16, 2026🧩 Неизменяемый список всё ещё может измениться Collections.unmodifiableList() запрещает м…
  4. Oct 15, 2026💡 Logpoints: логи без правки кода и нового деплоя Чтобы увидеть значение переменной, необ…
  5. Oct 14, 2026Post #7897
  6. Oct 14, 2026Почему разработчик планирует отдавать больше задач Claude Винсент Шмальбах описал план пер…
Threads Profile ViewerView any public Threads profile without an account.Open ThreadLook →Writing with AI? Make it sound human.Metric37 rewrites AI drafts so they read naturally. Free AI detector, 1,500 words free.Try Metric37 →