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 Dominant Programmer
The Dominant Programmer
The Dominant Programmer
Cache Database Data in Redis with Custom SpringBoot AOP Annotations

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.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

CacheaopRedisMySQLCustom AnnotationSpringBoot
The Dominant Programmer
Written by

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

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.