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
@EnableCachingImportCachingConfigurationSelector.selectImports(PROXY)ChọnAutoProxyRegistrarvàProxyCachingConfiguration.AutoProxyRegistrarĐăng ký auto-proxy creator cho infrastructure advisor.cacheOperationSource()Đọc các cache annotation trên method đủ điều kiện.cacheInterceptor()Tạo và cấu hìnhCacheInterceptor.cacheAdvisor()Ghép operation source với interceptor thành advisor.CacheInterceptor.invoke()Lời gọi qua proxy đi vào advice rồi gọiCacheAspectSupport.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
- Lấy cache operationsTra operation source bằng method và target class.
new CacheOperationContexts(operations, method, args, target, targetClass)Resolve named caches, key generator và metadata.findCachedValue()Duyệt cácCacheableOperationthỏacondition.context.generateKey(NO_RESULT)Dùng SpELkeyhoặc key generator của operation.findInCaches()rồidoGet()Tra key trong từng namedCacheđã resolve.- HIT:
evaluate(cacheHit, invoker, method, contexts)Unwrap giá trị và trả về;invokeOperation()không chạy. - MISS:
invokeOperation()Chạy target, gom put request, rồiperformCachePut()gọidoPut().
Mức 3 - zoom danh tính key
CacheOperationContext.generateKey()
Điều method, class và return type không tự đóng góp
- Có SpEL
keyevaluator.key(operationKey, methodKey, evaluationContext)tính key theo biểu thức. - Không có:
keyGenerator.generate(target, method, args)Mặc định đi vàoSimpleKeyGenerator.generateKey().
Luật mặc định chính xác
- 0 arg:
SimpleKey.EMPTY - 1 arg non-null, non-array: arg đó
- Còn lại:
new SimpleKey(params)Gồm nhiều arg, một argnullhoặc một arg array. userscộng5Lđụng độgetProfile(5L)vàgetOrders(5L)tra cùng named cache, cùng key.- 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"và"userOrders". - Nếu phải dùng chung, đưa namespace vào keyDùng SpEL prefix hoặc custom
KeyGeneratormang đủ 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 đủ.
@Cacheablekhô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.