需求
Redis的multi事务没有找到方便的框架能够自定义提供注解,想要的效果就是在一个方法上添加一个注解以后就可以自动添加redis事务,方法执行失败或者当正在修改的redis数据被修改了,就从头执行一次或者返回错误等之类的操作。实现思路主要是通过自定义注解以及aop环绕通知来实现。
首先写一个注解,用于标识在需要添加事务的方法上。
/**
* @Author Diuut
* @Date 2020/6/1 10:51
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface UserRedisTranscational {
public int NeedTryAgain() default 3; //默认重试次数
}
然后通过aop绑定该注解。
/**
* @Author Diuut
* @Date 2020/6/1 10:49
*/
@Aspect
@Component
@Slf4j
public class UserRedisTransactionalAspect {
@Autowired
private UserRedisDao userRedisDao;
@Around(value = "@annotation(userRedisTransactional)")
public Object around(ProceedingJoinPoint point, UserRedisTransactional userRedisTransactional) {
log.info("++++执行了around方法++++");
//拦截的类名
Class clazz = point.getTarget().getClass();
//拦截的方法
Method method = ((MethodSignature) point.getSignature()).getMethod();
log.info("执行了 类:" + clazz + " 方法:" + method);
int tryAgain = userRedisTransactional.tryTimes(); //重试次数
Object[] args = point.getArgs();
User user = (User) args[0];
int uid = user.getUid();
log.info("uid: {}", uid);
Object proceed = null;
try {
List list = null;
do {
if (tryAgain <= 0) {
//如果循环次数结束后依旧被锁,就不执行,直接结束,返回值 null
return null;
} else {
tryAgain--;
}
userRedisDao.watchAndMulti(uid);
User userByUid = userRedisDao.getUserByUid(uid);
userByUid.setLastVersion(4);
args[0] = userByUid;
log.info("proceed循环中");
proceed = point.proceed(args); //执行方法,以最新获取的参数(如果不带args就是用原先的参数)
list = userRedisDao.exec();
} while (list.isEmpty());
log.info("list.proceed循环结束");
} catch (Throwable throwable) {
throwable.printStackTrace();
return null;
}
return proceed;
}
}
使用注解。环绕通知会从拦截的方法中自动获取到User对象,然后根据调用redistTemplate中的watch以及multi方法,监控这个user的key值,并添加事务。期间可以通过修改args来修改参数。本篇中是在开始监控user之后,再次从redis中获取一次值,以确保在方法执行期间,上锁之前不被修改。
@UserRedisTransactional(NeedTryAgain = 5)
public void test32(User user) throws InterruptedException {
user.setLoginKey("happyPalmTiger");
userRedisDao.saveUser(user);
}