在 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: localhostport: 6379redisson: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 {@Resourceprivate RedisTemplate<String, Object> redisTemplate;@Resourceprivate 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;}// 讀鎖升級為寫鎖,執行數據庫查詢操作 rLock.unlock();RLock wLock = redissonClient.getReadWriteLock(key + ":lock").writeLock();wLock.lock();try {// 再次檢查緩存,避免在獲取寫鎖期間,其他線程已填充緩存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(wLock.isHeldByCurrentThread()) {wLock.unlock();}}}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 緩存之間的一致性。