SPRING SERIES - LỖI IM LẶNG

Cache Key Và Đụng Độ Dữ Liệu

PROXY - XANH = TẦNG PHÓNG TO Đọc từ trái sang phải. Màu đậm dần nghĩa là gần cơ chế hơn. Dấu * đánh dấu điểm bài viết khai thác.

Failure mode A - annotation không có tác dụng

Không có cache advisor: target chạy như lời gọi Java thường.

Hai cổng cần táchInfrastructure quyết định có advice hay không. Cache name cộng key quyết định lấy đúng dữ liệu hay không.

Failure mode B - cache hit nhầm

Advice chạy đúng nhưng object trong cùng named cache bị nhận nhầm.

Mức 1 - đăng ký cache advice

@EnableCaching Mời Ai Vào?

Đường PROXY mặc định tạo advisor và interceptor

  1. @EnableCachingImport CachingConfigurationSelector.
  2. selectImports(PROXY)Chọn AutoProxyRegistrarProxyCachingConfiguration.
  3. AutoProxyRegistrarĐăng ký auto-proxy creator cho infrastructure advisor.
  4. cacheOperationSource()Đọc các cache annotation trên method đủ điều kiện.
  5. cacheInterceptor()Tạo và cấu hình CacheInterceptor.
  6. cacheAdvisor()Ghép operation source với interceptor thành advisor.
  7. CacheInterceptor.invoke()Lời gọi qua proxy đi vào advice rồi gọi CacheAspectSupport.execute().

Nếu infrastructure vắng: annotation không tự chặn lời gọi. Target vẫn có thể chạy bình thường, nên dấu hiệu dễ bị đọc nhầm thành cache miss.

Mức 2 - zoom luồng sync

CacheAspectSupport.execute()

Đường @Cacheable đồng bộ, không dùng sync=true

  1. Lấy cache operationsTra operation source bằng method và target class.
  2. new CacheOperationContexts(operations, method, args, target, targetClass)Resolve named caches, key generator và metadata.
  3. findCachedValue()Duyệt các CacheableOperation thỏa condition.
  4. context.generateKey(NO_RESULT)Dùng SpEL key hoặc key generator của operation.
  5. findInCaches() rồi doGet()Tra key trong từng named Cache đã resolve.
  6. HIT: evaluate(cacheHit, invoker, method, contexts)Unwrap giá trị và trả về; invokeOperation() không chạy.
  7. MISS: invokeOperation()Chạy target, gom put request, rồi performCachePut() gọi doPut().
Mức 3 - zoom danh tính key

CacheOperationContext.generateKey()

Điều method, class và return type không tự đóng góp

  1. Có SpEL keyevaluator.key(operationKey, methodKey, evaluationContext) tính key theo biểu thức.
  2. Không có: keyGenerator.generate(target, method, args)Mặc định đi vào SimpleKeyGenerator.generateKey().

Luật mặc định chính xác

  1. 0 arg: SimpleKey.EMPTY
  2. 1 arg non-null, non-array: arg đó
  3. Còn lại: new SimpleKey(params)Gồm nhiều arg, một arg null hoặc một arg array.
  4. users cộng 5L đụng độgetProfile(5L)getOrders(5L) tra cùng named cache, cùng key.
  5. Ai ghi trước quyết định cache hit sauObject cũ vượt qua contract method kia: có thể lỗi kiểu hoặc thành dữ liệu sai hợp lệ.

Phép kiểm: breakpoint tại SimpleKeyGenerator.generateKey(), gọi hai method cùng id, rồi đối chiếu cả cache name, key và stored value. Cùng key mới chỉ chứng minh nguy cơ nếu named cache cũng trùng.

Giải pháp an toàn

  • Một contract dữ liệu cho mỗi cache nameƯu tiên "userProfiles""userOrders".
  • Nếu phải dùng chung, đưa namespace vào keyDùng SpEL prefix hoặc custom KeyGenerator mang đủ operation identity.

Khi KHÔNG được kết luận

  • Chung cache name luôn là lỗiChỉ an toàn khi các method cố ý chia sẻ cùng contract key-to-value; cùng return type thôi chưa đủ.
  • @Cacheable không ăn chỉ vì thiếu @EnableCachingCòn phải loại trừ lời gọi nội bộ, method không proxy được và cấu hình advisor khác.