Cache Database Data in Redis with Custom SpringBoot AOP Annotations
This guide demonstrates how to add a custom AOP‑based caching layer to a SpringBoot application, using Redis to store frequently queried MySQL table data, defining @AopCacheEnable and @AopCacheEvict annotations, implementing an aspect, handling key generation, expiration, and cache eviction on CRUD operations.
The article uses a RuoYi Vue + SpringBoot project that stores data in MySQL and caches it in Redis. When a table (e.g., bus_student) is queried frequently—such as by a scheduled task—the author proposes caching the result in Redis to avoid repeated database hits.
First, the Maven spring-aspects dependency (version 4.3.14.RELEASE) is added to enable AOP support.
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>4.3.14.RELEASE</version>
</dependency>Two custom annotations are created:
package com.ruoyi.system.redisAop;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/** Annotation to enable Redis caching */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface AopCacheEnable {
// Redis cache key
String[] key();
// Expiration time in seconds (default 3600)
long expireTime() default 3600;
} package com.ruoyi.system.redisAop;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/** Annotation to evict Redis cache */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AopCacheEvict {
// Redis key to delete
String[] key();
}The core aspect CacheEnableAspect defines two pointcuts—one for methods annotated with @AopCacheEnable and another for @AopCacheEvict. The @Around advice for the cache‑enable pointcut builds the Redis key (supporting SpEL‑style # placeholders), checks the cache, proceeds to the database if the cache is empty, and stores the result with the configured expiration. The eviction advice runs the original method first, then deletes the specified keys from Redis.
@Aspect
@Component
public class CacheEnableAspect {
@Autowired
public RedisTemplate redisCache;
@Pointcut("@annotation(com.ruoyi.system.redisAop.AopCacheEnable)")
public void queryCache() {}
@Pointcut("@annotation(com.ruoyi.system.redisAop.AopCacheEvict)")
public void clearCache() {}
@Around("queryCache()")
public Object intercept(ProceedingJoinPoint pjp) throws Throwable {
Method method = getMethod(pjp);
AopCacheEnable cacheAnno = method.getAnnotation(AopCacheEnable.class);
String[] keys = cacheAnno.key();
Object[] args = pjp.getArgs();
Object result = null;
for (String k : keys) {
String redisKey = k.contains("#") ? k.substring(1) + args[0].toString() : k;
result = redisCache.opsForValue().get(redisKey);
if (result == null) {
result = pjp.proceed();
if (result != null && result instanceof ArrayList) {
redisCache.opsForValue().set(redisKey, result, cacheAnno.expireTime(), TimeUnit.SECONDS);
}
}
}
return result;
}
@Around("clearCache()")
public Object evict(ProceedingJoinPoint pjp) throws Throwable {
Method method = getMethod(pjp);
AopCacheEvict evictAnno = method.getAnnotation(AopCacheEvict.class);
Object result = pjp.proceed();
for (String k : evictAnno.key()) {
redisCache.delete(k);
}
return result;
}
private Method getMethod(ProceedingJoinPoint pjp) {
Signature sig = pjp.getSignature();
return ((MethodSignature) sig).getMethod();
}
}Mapper methods are then annotated accordingly. For example, a query method uses @AopCacheEnable(key = "BusStudent", expireTime = 40) and CRUD methods use @AopCacheEvict(key = "BusStudent"). The keys must match between the query and eviction annotations.
@AopCacheEnable(key = "BusStudent", expireTime = 40)
List<BusStudent> selectBusStudentList(BusStudent busStudent);
@AopCacheEvict(key = "BusStudent")
int insertBusStudent(BusStudent busStudent);
@AopCacheEvict(key = "BusStudent")
int updateBusStudent(BusStudent busStudent);
@AopCacheEvict(key = "BusStudent")
int deleteBusStudentById(Integer id);If the application fails to start because multiple beans implement the same interface, the author marks the RedisTemplate bean with @Primary to resolve the conflict.
During debugging, a breakpoint is set inside CacheEnableAspect. The first query populates Redis (since the cache is empty); subsequent queries within the expiration window hit Redis directly. After an insert, update, or delete, the cache is evicted, forcing the next query to read fresh data from MySQL. The author notes that the RuoYi framework may immediately re‑populate the cache after a write, so testing with tools like Postman is recommended.
Finally, the article advises choosing an appropriate expiration time and avoiding mixing this custom cache with regular mapper calls. Instead, create dedicated mapper methods for high‑frequency tasks to keep the caching logic isolated.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
The Dominant Programmer
Resources and tutorials for programmers' advanced learning journey. Advanced tracks in Java, Python, and C#. Blog: https://blog.csdn.net/badao_liumang_qizhi
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
