0

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

Java Backend Zero to Hello

📚 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

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í