0

[Java Backend Zero to Hello] [Phase 5] BÀI 5.11: CACHING

📚 Series: Java Backend Zero to Hello 📂 Phân đoạn: Phase 5: Spring Framework & Spring Boot 📖 Nội dung: BÀI 5.11: CACHING 💡 Khóa học lập trình Backend Java & Spring Boot chuẩn doanh nghiệp từ con số 0.


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 bài học

⭐ Hãy bookmark (clip) lại series để tiện theo dõi các bài học tiếp theo nhé!


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí