Bootstrap

Springboot redisson 自定义注解实现双写一致性

在 Spring Boot 项目中使用 Redisson 实现双写一致性(即数据库和缓存的一致性),可以通过自定义注解和 AOP(面向切面编程)来简化代码并提高可维护性。以下是一个具体的案例,展示了如何使用自定义注解和 AOP 来实现这一目标。

实现步骤

1.添加依赖

首先,确保你的项目中包含了 Redisson 和 Spring Boot 的依赖。

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

<!-- Redisson -->
<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson-spring-boot-starter</artifactId>
    <version>3.16.3</version>
</dependency>

2.配置 Redisson

在 application.yml 中配置 Redisson 客户端连接到 Redis 服务器。

spring:
  redis:
    host: localhost
    port: 6379

redisson:
  singleServerConfig:
    address: redis://${spring.redis.host}:${spring.redis.port}

3.添加配置类:

@Configuration
public class RedissonConfig {

    @Value("${redisson.singleServerConfig.address}")
    private String redisAddress;

    @Bean(destroyMethod = "shutdown")
    public RedissonClient redissonClient() {
        Config config = new Config();
        config.useSingleServer().setAddress(redisAddress);
        return Redisson.create(config);
    }
}

 4.自定义注解

创建一个自定义注解,另一个注解还用上篇的

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface CustomerCacheEvict {
    /**
     * 缓存名称
     * @return
     */
    String key() default "";
}

5.AOP 切面

创建一个切面类 CustomCacheAspect,使用 Redisson 的读写锁来实现缓存和数据库的一致性。


@Aspect
@Component
public class CustomCacheAspect {

    @Resource
    private RedisTemplate<String, Object> redisTemplate;

    @Resource
    private RedissonClient redissonClient;

    @Around("@annotation(customCacheable)")
    public Object cache(ProceedingJoinPoint joinPoint, CustomCacheable customCacheable) throws Throwable {
        Object result;
        String key = customCacheable.key();
        long expireTime = customCacheable.expireTime();
        //获取参数值
        key = getKey(joinPoint, key);

        // 加锁
        RLock rLock = redissonClient.getReadWriteLock(key+":lock").readLock();
        rLock.lock();
        try {
            // 尝试从缓存中获取数据
            Object cachedValue = redisTemplate.opsForValue().get(key);
            if (cachedValue != null) {
                return cachedValue;
            }

            // 如果缓存中没有数据,则执行方法并将结果存入缓存
            result = joinPoint.proceed();

            if(expireTime > 0){
                redisTemplate.opsForValue().set(key, result, expireTime, TimeUnit.SECONDS);
            }else{
                redisTemplate.opsForValue().set(key, result);
            }
        }finally{
            if(rLock.isHeldByCurrentThread()){
                rLock.unlock();
            }
        }
        return result;
    }

    @Around("@annotation(customerCacheEvict)")
    public Object cache(ProceedingJoinPoint joinPoint, CustomerCacheEvict customerCacheEvict) throws Throwable {
        Object result;
        String key = customerCacheEvict.key();
        //获取参数值
        key = getKey(joinPoint, key);
        // 加锁
        RLock wLock = redissonClient.getReadWriteLock(key + ":lock").writeLock();
        wLock.lock();
        try {
            result = joinPoint.proceed();
            try {
                Thread.sleep(100);
            }catch (Exception e){
               e.printStackTrace();
            }
            redisTemplate.delete(key);
        }finally{
            if(wLock.isHeldByCurrentThread()){
                wLock.forceUnlock();
            }
        }
        return result;
    }

    /**
     * 获取参数值
     * @return
     */
    private String getKey(ProceedingJoinPoint joinPoint, String key) {
        Object[] args = joinPoint.getArgs();
        // 构建缓存键
        if (args != null && args.length > 0) {
            key = key + ":" + args[0];

        }
        return key;
    }
}

6.服务类

在接口类中使用自定义注解。(实际加到service 层更好)

    /**
     * 根据id查询
     */
    @GetMapping("/{id}")
    @CustomCacheable(key = "user")
    public  UserTest getById(@PathVariable Long id) {
        return userTestService.getById(id);
    }
    /**
     * 修改
     */
    @PutMapping("/{id}")
    @CustomerCacheEvict(key="user")
    public boolean update(@PathVariable Integer id, @RequestBody UserTest userTest) {
        userTest.setId(id);
        return userTestService.updateById(userTest);
    }

通过以上步骤,我们使用自定义注解和 AOP 确保了数据库和 Redis 缓存之间的一致性。

悦读

道可道,非常道;名可名,非常名。 无名,天地之始,有名,万物之母。 故常无欲,以观其妙,常有欲,以观其徼。 此两者,同出而异名,同谓之玄,玄之又玄,众妙之门。

;