| name | tucache-caching |
| description | Use this skill when writing or modifying Spring Boot application code that should use TuCache for method-result caching or cache invalidation with `@TuCache` and `@TuCacheClear`. |
TuCache Caching
Use this skill when the codebase already uses TuCache, or when a Spring Boot service/controller needs lightweight annotation-based caching.
Read README.md first for public usage, then confirm behavior from source when making non-trivial changes.
What TuCache Does
TuCache caches method return values with @TuCache and clears exact or wildcard keys with @TuCacheClear.
Core behavior from source:
@TuCache only applies when the method returns a value. void methods are never cached.
- If a cache read fails, the original method still executes.
null results are not written to cache.
- If no
key/value is provided, the default key is fully.qualified.ClassName:methodName:arg1:arg2....
condition is raw SpEL and must evaluate to boolean.
key/value support SpEL templates with #{...}.
keys in @TuCacheClear deletes exact keys unless the value contains *.
@TuCacheClear clears after successful method invocation by default. Use beforeInvocation = true only when pre-invocation clearing is required.
Primary implementation points:
Before Writing Code
Check these first:
- The project is Spring Boot based.
- TuCache starter dependency is available:
<dependency>
<groupId>io.github.tri5m</groupId>
<artifactId>tucache-spring-boot-starter</artifactId>
<version>1.0.6</version>
</dependency>
tucache.enabled is not disabled.
- A cache backend is available.
Backend selection is auto-configured in this order:
- custom
TuCacheService
RedissonClient
RedisTemplate
- local in-memory cache
Relevant source:
Default Coding Strategy
When adding caching to business code, prefer this order:
- Put
@TuCache on service methods that are read-heavy, deterministic for the same input, and side-effect free.
- Put
@TuCacheClear on the write path that changes the same data.
- Use explicit, stable, namespaced keys instead of relying on the default key.
- Use SpEL only for the minimal identifying fields.
- Set a finite
timeout unless the data is effectively immutable.
Default key format to generate manually:
@TuCache(key = "user:detail:#{#userId}", timeout = 5, timeUnit = TimeUnit.MINUTES)
public UserDTO getUserDetail(Long userId) {
...
}
This is better than the implicit key because it is readable, stable across refactors, and easier to invalidate.
Key Design Rules
When generating keys:
- Start with a business namespace like
user:detail, order:list, product:search.
- Append only the fields that actually affect the result.
- Keep the same namespace family for reads and invalidation.
- Prefer scalar identifiers over serializing whole objects into the key.
- If multiple methods share the same cached resource, standardize one key format.
Good examples:
@TuCache(key = "user:detail:#{#id}", timeout = 10, timeUnit = TimeUnit.MINUTES)
public User getById(Long id) { ... }
@TuCache(key = "order:list:#{#customerId}:#{#status}", timeout = 30, timeUnit = TimeUnit.SECONDS)
public List<Order> listOrders(Long customerId, String status) { ... }
Avoid:
- keys based on
toString() of complex request objects
- keys that omit a result-shaping argument
- opaque keys like
"cache1" with no namespace
Invalidation Rules
Use exact deletion when the changed data maps to a specific cache entry.
@TuCacheClear(key = "user:detail:#{#id}")
public void updateUser(Long id, UpdateUserCommand command) { ... }
Use wildcard deletion with keys when one write affects a family of cache entries.
@TuCacheClear(
key = "user:detail:#{#id}",
keys = "user:list:*",
async = true
)
public void deleteUser(Long id) { ... }
Important limitation:
- Use explicit wildcard patterns such as
user:list:* or user:list:tenantA:*.
- Without
*, keys deletes only the exact key.
- Do not rely on partial fragment matching like
user:li.
Relevant source:
SpEL Rules
TuCache supports:
- method parameters:
#{#id}
- nested fields:
#{#user.name}
- current object:
#{#this.methodName()}
- Spring beans:
#{@someBean.method()}
For condition, write plain SpEL without #{}:
@TuCache(
key = "user:search:#{#keyword}",
condition = "#keyword != null && #keyword.length() >= 2",
timeout = 1,
timeUnit = TimeUnit.MINUTES
)
public List<User> search(String keyword) { ... }
Prefer condition when:
- very short or invalid input should bypass cache
- per-user highly random requests should not be cached
- the method is only worth caching for part of the input domain
Async And Expiration
Use async = true only when eventual cache write/delete is acceptable.
@TuCache(async = true) makes cache writes asynchronous.
@TuCacheClear(async = true) makes invalidation asynchronous.
@TuCacheClear(beforeInvocation = true) clears before the method runs; the default clears only after the method succeeds.
Use resetExpire = true only for hot keys that should stay alive on repeated reads.
@TuCache(
key = "config:public",
timeout = 10,
timeUnit = TimeUnit.MINUTES,
resetExpire = true
)
public ConfigDTO getPublicConfig() { ... }
Backend Guidance
Prefer Redis or Redisson for shared caches across instances.
Use local cache only when all of these are true:
- single-node deployment is acceptable, or per-node cache divergence is acceptable
- data volume is limited
- wildcard invalidation volume is small
If Redis is used, prefer a RedisTemplate<String, Object> with string keys and JSON-friendly value serialization, as shown in README.md.
Code Generation Checklist
When asked to add caching, do this:
- Find the read method.
- Confirm it is safe to cache.
- Design a readable key namespace.
- Choose a timeout.
- Add
@TuCache.
- Find the write/delete path that mutates the same data.
- Add matching
@TuCacheClear.
- If the codebase has
tucache.profiles.cache-prefix, do not duplicate the global prefix inside annotation keys.
Patterns To Prefer
Cache detail queries:
@TuCache(key = "article:detail:#{#id}", timeout = 15, timeUnit = TimeUnit.MINUTES)
public ArticleDTO getArticle(Long id) { ... }
Cache paged or filtered lists only when the filter set is explicit and bounded:
@TuCache(
key = "article:list:#{#category}:#{#page}:#{#size}",
timeout = 30,
timeUnit = TimeUnit.SECONDS
)
public Page<ArticleDTO> listArticles(String category, int page, int size) { ... }
Clear both detail and list caches after mutation:
@TuCacheClear(
key = "article:detail:#{#command.id}",
keys = "article:list:*",
async = true
)
public void updateArticle(UpdateArticleCommand command) { ... }
Patterns To Avoid
Do not generate TuCache code in these cases:
- methods with side effects
- highly volatile values where stale reads are unacceptable
- methods that often return
null if you expect negative caching, because TuCache does not cache null
- keys derived from unstable object string representations
- broad
keys invalidation on very large keyspaces without a strong reason
If You Need To Go Deeper
Inspect these files before changing framework-level behavior: