最近项目需要版本号乐观锁,但发现每个需要加锁的地方都要做处理发现很繁琐很臃肿,所以使用aop切面+自定义注解来抽取实现乐观锁。
第一步使用@Aspect需要pom引入
<!--使用AspectJ方式注解需要相应的包-->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.9.1</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.1</version>
</dependency>
|
第二步: 自定义注解
package com.aop;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/**
* 自定义尝试切面接口
*/
// 注解会在class字节码文件中存在,在运行时可以通过反射获取到
@Retention(RetentionPolicy.RUNTIME)
//@interface定义一个注解
public @interface IsTryAgain {
}
|
第三步: 自定义一个异常 ApiException
第四部:编写AOP
package com.aop;
import com.aop.exception.ApiException;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
/**
* 定义重试切面方法,是为了发生乐观锁异常时在一个全新的事务里提交上一次的操作,
* 直到达到重试上限;因此切面实现 org.springframework.core.Ordered 接口,
* 这样我们就可以把切面的优先级设定为高于事务通知 。
*/
@Aspect
@Component
public class ConcurrentOperationExecutor implements Ordered {
private int maxRetries = 3;
private int order = 1;
public void setMaxRetries(int maxRetries) {
this.maxRetries = maxRetries;
}
public int getOrder() {
return this.order;
}
/**
* 切点可访问性修饰符与类可访问性修饰符的功能是相同的,它可以决定定义的切点可以在哪些类中可使用。
* 因为命名切点仅利用方法名及访问修饰符的信息,所以我们一般定义方法的返回类型为 void ,并且方法体为空 。
*
* 所有加了自定义注解的地方切入
*
*/
@Pointcut("@annotation(IsTryAgain)")
public void businessService() {
}
@Around("businessService()")
@Transactional(rollbackFor = Exception.class)
public Object doConcurrentOperation(ProceedingJoinPoint pjp) throws Throwable {
int numAttempts = 0;
do {
numAttempts++;
try {
return pjp.proceed();
} catch (TryAgainException ex) {
if (numAttempts > maxRetries) {
throw new ApiException(1, "重试达到最大次数");
} else {
System.out.println("=====try recovery=====");
}
}
} while (numAttempts <= this.maxRetries);
return null;
}
}
|
方法类:
/**
* update有排它锁,当当前事务update没提交之前,另外集群上面的事务不能更新需要等待
* @param content
* @throws Exception
*/
@IsTryAgain
@Transactional(rollbackFor = Exception.class)
public void IsTryAgainMethod(String content) throws Exception{
HappyLockTest happyLockTest = happyLockTestMapper.selectByPrimaryKey(1L);
happyLockTest.setContent(content);
System.out.println(content+"--->"+happyLockTest.getVersion());
Thread.sleep(8000);
System.out.println("开始--->");
happyLockTest.setVersion(happyLockTest.getVersion());
int temp = happyLockTestMapper.updateByPrimaryKeySelective(happyLockTest);
if (temp == 0){
throw new TryAgainException("重试");
}
System.out.println("结束了去查询吧------------》");
Thread.sleep(8000);
System.out.println("---------------结束------------");
}
|
sql:
<update id="updateByPrimaryKeySelective" parameterType="com.pojo.HappyLockTest" >
update happy_lock_test
<set >
<if test="content != null" >
content = #{content,jdbcType=VARCHAR},
</if>
<if test="version != null" >
version = #{version,jdbcType=INTEGER} + 1,
</if>
</set>
where id = #{id,jdbcType=BIGINT} and version = #{version}
</update>
|
注:如果用的是mybatisPlus则只需要在版本号上加上version注解就可以了

参考网友:https://blog.csdn.net/zhanghongzheng3213/article/details/50819539