Step 12 of 51
@Cacheable, cache strategies (TTL, LRU, write-through), when to cache, cache invalidation patterns.
Caching — เร็วขึ้นด้วย Redis/Memcached
Every database query costs time and resources. If the same data is read frequently and rarely changes, cache it. A cache hit returns in microseconds; a DB round trip takes milliseconds.
Diagram: Cache-aside pattern where a request checks the cache first, returns immediately on a hit, or queries the database and stores the result in cache on a miss.
Loading diagram...
Your application checks the cache first. On a miss, it queries the database and stores the result in cache for next time.
@Configuration
@EnableCaching
public class CacheConfig {
@Bean
public RedisCacheManager cacheManager(RedisConnectionFactory factory) {
var config = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofMinutes(10))
.serializeValuesWith(RedisSerializationContext
.SerializationPair
.fromSerializer(new GenericJackson2JsonRedisSerializer()));
return RedisCacheManager.builder(factory)
.cacheDefaults(config)
.build();
}
}
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
spring:
data:
redis:
host: localhost
port: 6379
@Service
@RequiredArgsConstructor
public class UserService {
private final UserRepository repository;
@Cacheable(value = "users", key = "#id")
public UserResponse getUser(Long id) {
var user = repository.findById(id)
.orElseThrow(() -> new ResourceNotFoundException("User not found"));
return new UserResponse(user.getId(), user.getName(), user.getEmail());
}
@Cacheable(value = "user-profiles", key = "#username")
public UserProfile getProfile(String username) {
return repository.findProfileByUsername(username);
}
}
On first call, the method executes and the result is cached. On subsequent calls with the same key, the cached value is returned — the method body is not executed.
@CachePut(value = "users", key = "#result.id")
public UserResponse updateUser(Long id, UserRequest request) {
var user = repository.findById(id)
.orElseThrow(() -> new ResourceNotFoundException("User not found"));
user.setName(request.name());
user.setEmail(request.email());
var saved = repository.save(user);
return new UserResponse(saved.getId(), saved.getName(), saved.getEmail());
}
@CachePut always executes the method and updates the cache with the result. Use it after updates to keep cache and database in sync.
@CacheEvict(value = "users", key = "#id")
public void deleteUser(Long id) {
repository.deleteById(id);
}
@CacheEvict(value = "users", allEntries = true)
public void evictAllUsers() {
// Used after bulk updates or data migrations
}
@Configuration
@EnableCaching
public class CacheConfig {
@Bean
public RedisCacheManager cacheManager(RedisConnectionFactory factory) {
var userConfig = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofMinutes(30));
var profileConfig = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofHours(2));
return RedisCacheManager.builder(factory)
.withCacheConfiguration("users", userConfig)
.withCacheConfiguration("user-profiles", profileConfig)
.build();
}
}
@Cacheable on expensive queries — measure the impact@CachePut after writes to keep cache warm#id or #user.name()