java 40 lines · 8 steps

How Spring caching layers a product lookup

A Spring service caches product reads, guards what gets stored, and evicts stale entries with declarative annotations.

Explained by highlit
1@Service
2public class ProductLookupService {
3 
4 private final ProductRepository productRepository;
5 private final ExternalCatalogClient catalogClient;
6 
7 public ProductLookupService(ProductRepository productRepository, ExternalCatalogClient catalogClient) {
8 this.productRepository = productRepository;
9 this.catalogClient = catalogClient;
10 }
11 
12 @Cacheable(cacheNames = "products", key = "#sku", unless = "#result == null")
13 public ProductDto findBySku(String sku) {
14 return productRepository.findBySku(sku)
15 .map(ProductDto::from)
16 .orElseGet(() -> fetchFromCatalog(sku));
17 }
18 
19 @Cacheable(cacheNames = "productAvailability", key = "#sku", unless = "!#result.stocked")
20 public Availability checkAvailability(String sku) {
21 ProductDto product = findBySku(sku);
22 if (product == null) {
23 return Availability.unknown(sku);
24 }
25 return catalogClient.availability(sku);
26 }
27 
28 @CacheEvict(cacheNames = {"products", "productAvailability"}, key = "#sku")
29 public void evict(String sku) {
30 }
31 
32 private ProductDto fetchFromCatalog(String sku) {
33 return catalogClient.lookup(sku)
34 .map(remote -> {
35 Product saved = productRepository.save(remote.toEntity());
36 return ProductDto.from(saved);
37 })
38 .orElse(null);
39 }
40}
01 / 01
STEP 01

Walkthrough

Space play step click any line
Three takeaways
  1. 1Declarative caching with @Cacheable keeps the caching logic out of your method bodies entirely.
  2. 2The unless attribute lets you skip caching for results that would poison the cache, like nulls or out-of-stock items.
  3. 3Pair every cache with an eviction path so stale entries can be invalidated on demand.

Related explainers

typescript
import { registerLocaleData } from '@angular/common';
import localeFr from '@angular/common/locales/fr';
import localeFrExtra from '@angular/common/locales/extra/fr';
import localeDe from '@angular/common/locales/de';

Locale-aware bootstrapping in Angular

i18n localization dependency-injection
Intermediate 8 steps
typescript
import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import * as Joi from 'joi';
 

Validating env config at boot in NestJS

configuration schema-validation environment-variables
Intermediate 8 steps
java
@Component
@Converter
public class EncryptedStringConverter implements AttributeConverter<String, String> {
 

Transparent column encryption in Spring & JPA

encryption aes-gcm jpa-converter
Advanced 10 steps
typescript
import { Inject, Injectable, Logger } from '@nestjs/common';
import { CACHE_MANAGER } from '@nestjs/cache-manager';
import { Cache } from 'cache-manager';
import { InjectRepository } from '@nestjs/typeorm';

A cache-aside country lookup in NestJS

cache-aside dependency-injection batch-lookup
Intermediate 8 steps
java
package com.acme.billing.config;
 
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.ConfigurationProperties;

Feature-flagged beans with Spring @ConditionalOnProperty

feature-flags conditional-beans strategy-pattern
Intermediate 5 steps
typescript
import { Injectable, effect, signal, computed } from '@angular/core';
 
interface Preferences {
  theme: 'light' | 'dark';

A signal-based preferences store in Angular

signals state-management persistence
Intermediate 7 steps

Share this explainer

Here's the card — post it anywhere.

How Spring caching layers a product lookup — share card
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code