[Java Backend Zero to Hello] BÀI 5.11: CACHING
📚 Bài viết thuộc series Java Backend Zero to Hello 📌 Phần: Phase 5: Spring Framework & Spring Boot | Bài 57/86
BÀI 5.11: CACHING
Mục tiêu
- Hiểu về caching
- Sử dụng Spring Cache
- Tích hợp Redis
- Best practices
1. CACHING LÀ GÌ?
Cache lưu trữ dữ liệu tạm thời để truy cập nhanh hơn, giảm tải cho database.
Request → Cache (hit) → Response
↓ (miss)
Database → Cache → Response
Lợi ích
- Tăng tốc độ response
- Giảm tải database
- Cải thiện UX
2. SPRING CACHE
2.1 Dependency
<!-- Simple in-memory cache -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<!-- Redis cache -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
2.2 Enable Caching
@SpringBootApplication
@EnableCaching
public class Application { }
2.3 Cấu hình
spring:
cache:
type: redis
redis:
time-to-live: 60000 # 60s
cache-null-values: true
use-key-prefix: true
key-prefix: "app:"
3. CÁC ANNOTATION
3.1 @Cacheable
Cache kết quả method.
@Cacheable(value = "users", key = "#id")
public User findById(Long id) {
return userRepository.findById(id).orElseThrow();
}
@Cacheable(value = "users", key = "#email", unless = "#result == null")
public User findByEmail(String email) {
return userRepository.findByEmail(email).orElse(null);
}
3.2 @CachePut
Cập nhật cache với kết quả mới.
@CachePut(value = "users", key = "#user.id")
public User update(User user) {
return userRepository.save(user);
}
3.3 @CacheEvict
Xóa cache.
@CacheEvict(value = "users", key = "#id")
public void delete(Long id) {
userRepository.deleteById(id);
}
// Xóa tất cả
@CacheEvict(value = "users", allEntries = true)
public void clearCache() { }
// Xóa sau khi method chạy xong
@CacheEvict(value = "users", key = "#id", beforeInvocation = false)
3.4 @Caching
Kết hợp nhiều annotation.
@Caching(
cacheable = @Cacheable("users", key = "#id"),
evict = @CacheEvict("userList", allEntries = true)
)
public User findById(Long id) { ... }
4. SPEL (SPRING EXPRESSION LANGUAGE)
// Tham số đơn
@Cacheable("users", key = "#id")
// Nhiều tham số
@Cacheable("users", key = "#email + '-' + #name")
// Nested property
@Cacheable("users", key = "#user.id")
// Condition
@Cacheable(value = "users", key = "#id", condition = "#id > 0")
// Unless (không cache khi)
@Cacheable(value = "users", key = "#id", unless = "#result == null")
5. REDIS
5.1 Cài đặt
docker run --name redis -p 6379:6379 -d redis:7
5.2 Cấu hình
spring:
data:
redis:
host: localhost
port: 6379
password: password
timeout: 2000
5.3 Custom Redis Cache Manager
@Configuration
@EnableCaching
public class RedisCacheConfig {
@Bean
public RedisCacheManagerBuilderCustomizer cacheManagerCustomizer() {
return builder -> builder
.withCacheConfiguration("users",
defaultConfig().entryTtl(Duration.ofMinutes(10)))
.withCacheConfiguration("products",
defaultConfig().entryTtl(Duration.ofHours(1)));
}
private RedisCacheConfiguration defaultConfig() {
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new JavaTimeModule());
mapper.activateDefaultTyping(
mapper.getPolymorphicTypeValidator(),
ObjectMapper.DefaultTyping.NON_FINAL
);
Jackson2JsonRedisSerializer<Object> serializer =
new Jackson2JsonRedisSerializer<>(mapper, Object.class);
return RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofMinutes(5))
.serializeValuesWith(
RedisSerializationContext.SerializationPair.fromSerializer(serializer)
);
}
}
6. CUSTOM CACHE KEY
@Configuration
public class CacheConfig implements CachingConfigurer {
@Bean
@Override
public KeyGenerator keyGenerator() {
return new KeyGenerator() {
@Override
public Object generate(Object target, Method method,
Object... params) {
return target.getClass().getSimpleName() + "_" +
method.getName() + "_" +
Arrays.toString(params);
}
};
}
}
7. MULTI-LEVEL CACHE
@Configuration
@EnableCaching
public class CacheConfig {
@Bean
public CacheManager cacheManager() {
// Caffeine in-memory + Redis
CaffeineCacheManager caffeine = new CaffeineCacheManager();
caffeine.setCaffeine(Caffeine.newBuilder()
.maximumSize(1000)
.expireAfterWrite(Duration.ofMinutes(5)));
// Composite cache manager
CompositeCacheManager composite = new CompositeCacheManager();
composite.setCacheManagers(List.of(caffeine, redisCacheManager()));
return composite;
}
}
8. CACHE PATTERNS
8.1 Cache-Aside (Lazy Loading)
public User findById(Long id) {
return cache.get(id, () -> userRepository.findById(id).orElseThrow());
}
8.2 Write-Through
@CachePut(value = "users", key = "#user.id")
public User save(User user) {
return userRepository.save(user);
}
8.3 Write-Behind
@Async
@CacheEvict(value = "users", key = "#user.id")
public void updateAsync(User user) {
userRepository.save(user);
}
9. BEST PRACTICES
9.1 ✅ NÊN
// Cache dữ liệu ít thay đổi
@Cacheable("countries")
public List<Country> getAllCountries() { ... }
// Set TTL phù hợp
.entryTtl(Duration.ofMinutes(10))
// Dùng key rõ ràng
@Cacheable(value = "users", key = "#id")
9.2 ❌ TRÁNH
// Cache dữ liệu thay đổi liên tục
@Cacheable("stockPrices") // ❌
// Cache dữ liệu lớn
@Cacheable("allUsers") // ❌
// Quên evict khi update
@Cacheable("users") // Cache sẽ stale
public User findById() { ... }
10. BÀI TẬP THỰC HÀNH
Bài 1: Cache User
Cache thông tin user với Redis.
Bài 2: Cache Eviction
Đảm bảo cache được cập nhật khi user thay đổi.
Bài 3: Multi-level Cache
Kết hợp Caffeine (L1) và Redis (L2).
11. TÓM TẮT
| Annotation | Mô tả |
|---|---|
| @Cacheable | Cache kết quả |
| @CachePut | Cập nhật cache |
| @CacheEvict | Xóa cache |
| @Caching | Kết hợp nhiều annotation |
| @EnableCaching | Bật caching |
| Redis | Distributed cache |
| Caffeine | In-memory cache |
| TTL | Thời gian sống |
Bài tiếp theo: 5.12 AOP
🧭 Điều Hướng Series
⬅️ Bài trước: BÀI 5.10: SPRING BOOT ACTUATOR
📋 Lộ trình tổng quan: Xem Toàn Bộ Series
➡️ Bài tiếp theo: BÀI 5.12: AOP (ASPECT-ORIENTED PROGRAMMING)
All rights reserved