or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

annotation-config.mdaot.mdcaching.mdcontext-lifecycle.mdevents.mdformatting.mdi18n.mdindex.mdjmx.mdresilience.mdscheduling.mdstereotypes.mdvalidation.md
tile.json

caching.mddocs/

Caching

@EnableCaching

@Configuration
@EnableCaching
public class CacheConfig {

    @Bean
    public CacheManager cacheManager() {
        SimpleCacheManager manager = new SimpleCacheManager();
        manager.setCaches(Arrays.asList(
            new ConcurrentMapCache("users"),
            new ConcurrentMapCache("orders"),
            new ConcurrentMapCache("products")
        ));
        return manager;
    }

    // Or use ConcurrentMapCacheManager
    @Bean
    public CacheManager cacheManager() {
        return new ConcurrentMapCacheManager("users", "orders");
    }
}

@Cacheable

@Service
public class UserService {

    // Cache with key = id
    @Cacheable("users")
    public User getUser(Long id) {
        return database.findUser(id);
    }

    // Custom key with SpEL
    @Cacheable(value = "users", key = "#username")
    public User findByUsername(String username) {
        return database.findByUsername(username);
    }

    // Composite key
    @Cacheable(value = "searches", key = "{#criteria, #page, #size}")
    public Page<User> search(SearchCriteria criteria, int page, int size) {
        return database.search(criteria, page, size);
    }

    // Conditional caching
    @Cacheable(value = "users", condition = "#id > 0")
    public User getUserConditional(Long id) {
        return database.findUser(id);
    }

    // Unless (skip caching if result matches condition)
    @Cacheable(value = "users", unless = "#result == null")
    public User getUserUnlessNull(Long id) {
        return database.findUser(id);
    }

    // Sync for single-flight pattern
    @Cacheable(value = "expensive", sync = true)
    public ExpensiveResult expensiveOperation(String param) {
        return compute(param);
    }

    // Custom key generator
    @Cacheable(value = "users", keyGenerator = "customKeyGenerator")
    public User customKey(String username, String tenant) {
        return database.find(username, tenant);
    }
}

@CachePut

@Service
public class UserService {

    // Update cache after method execution
    @CachePut(value = "users", key = "#user.id")
    public User updateUser(User user) {
        return database.update(user);
    }

    // Conditional update
    @CachePut(value = "users", key = "#result.id", condition = "#result.active")
    public User saveUser(User user) {
        return database.save(user);
    }
}

@CacheEvict

@Service
public class UserService {

    // Evict single entry
    @CacheEvict(value = "users", key = "#id")
    public void deleteUser(Long id) {
        database.delete(id);
    }

    // Evict all entries
    @CacheEvict(value = "users", allEntries = true)
    public void deleteAllUsers() {
        database.deleteAll();
    }

    // Evict before method execution
    @CacheEvict(value = "users", beforeInvocation = true)
    public void clearCache() {
        // Cache cleared even if method throws exception
    }

    // Evict multiple caches
    @CacheEvict(value = {"users", "userStats"}, key = "#id")
    public void removeUserData(Long id) {
        database.delete(id);
    }
}

@Caching (Multiple Operations)

@Service
public class UserService {

    @Caching(
        cacheable = {
            @Cacheable(value = "users", key = "#id")
        },
        put = {
            @CachePut(value = "userCache", key = "#result.username"),
            @CachePut(value = "emailCache", key = "#result.email")
        }
    )
    public User getUser(Long id) {
        return database.findUser(id);
    }

    @Caching(
        evict = {
            @CacheEvict(value = "users", key = "#id"),
            @CacheEvict(value = "userStats", allEntries = true)
        }
    )
    public void deleteUser(Long id) {
        database.delete(id);
    }
}

@CacheConfig (Class-level)

@Service
@CacheConfig(cacheNames = "users")
public class UserService {

    @Cacheable  // Uses "users" cache
    public User getUser(Long id) {
        return database.findUser(id);
    }

    @CacheEvict  // Evicts from "users" cache
    public void deleteUser(Long id) {
        database.delete(id);
    }
}

@Service
@CacheConfig(cacheNames = "users", keyGenerator = "customKeyGenerator")
public class UserServiceWithCustomKey {

    @Cacheable
    public User getUser(String username, String tenant) {
        return database.find(username, tenant);
    }
}

Custom KeyGenerator

@Configuration
public class CacheConfig {

    @Bean("customKeyGenerator")
    public KeyGenerator keyGenerator() {
        return (target, method, params) -> {
            StringBuilder key = new StringBuilder();
            key.append(target.getClass().getSimpleName());
            key.append(".");
            key.append(method.getName());
            for (Object param : params) {
                key.append(".").append(param);
            }
            return key.toString();
        };
    }
}

CacheResolver

@Configuration
public class CacheConfig {

    @Bean
    public CacheResolver cacheResolver(CacheManager cacheManager) {
        return context -> {
            Collection<String> cacheNames = new ArrayList<>();

            // Dynamic cache selection based on method or parameters
            String tenant = extractTenant(context);
            cacheNames.add("cache-" + tenant);

            return cacheNames.stream()
                .map(cacheManager::getCache)
                .filter(Objects::nonNull)
                .collect(Collectors.toList());
        };
    }
}

@Service
public class MultiTenantService {

    @Cacheable(cacheResolver = "cacheResolver")
    public Data getData(String tenant, Long id) {
        return database.find(tenant, id);
    }
}

CacheManager Implementations

// Concurrent Map (Simple)
@Bean
public CacheManager concurrentMapCacheManager() {
    return new ConcurrentMapCacheManager("users", "orders");
}

// Caffeine (High-performance)
@Bean
public CacheManager caffeineCacheManager() {
    CaffeineCacheManager manager = new CaffeineCacheManager("users", "orders");
    manager.setCaffeine(Caffeine.newBuilder()
        .expireAfterWrite(10, TimeUnit.MINUTES)
        .maximumSize(1000));
    return manager;
}

// Composite (Multiple backends)
@Bean
public CacheManager compositeCacheManager() {
    CompositeCacheManager manager = new CompositeCacheManager();
    manager.setCacheManagers(Arrays.asList(
        caffeineCacheManager(),
        redisCacheManager()
    ));
    manager.setFallbackToNoOpCache(true);
    return manager;
}

Programmatic Cache Access

@Service
public class CacheService {

    @Autowired
    private CacheManager cacheManager;

    public void manualCacheOperations() {
        Cache cache = cacheManager.getCache("users");

        // Put value
        cache.put("user1", new User(1L, "John"));

        // Get value
        Cache.ValueWrapper wrapper = cache.get("user1");
        if (wrapper != null) {
            User user = (User) wrapper.get();
        }

        // Get with type
        User user = cache.get("user1", User.class);

        // Get with value loader (compute if absent)
        User computed = cache.get("user1", () -> database.findUser(1L));

        // Evict single entry
        cache.evict("user1");

        // Clear all
        cache.clear();

        // Atomic putIfAbsent
        Cache.ValueWrapper existing = cache.putIfAbsent("user1", new User(1L, "John"));
    }
}