问题重现
需求:查询数据是否存在,如果不存在则新增,只允许新增一次
考虑到多个线程并发访问的情况,所以需要通过分布式锁进行实现,同时新增是持久化到数据库中
伪代码实现如下:
@Component
public class A {
@Autowired
private A current;
public B methodA() {
// 查询数据库是否存在数据
B b = query();
if (b == null) {
b = current.methodB();
}
return b;
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
@DistributedLock
public B methodB() {
B b = query();
// 查询到已经有数据,说明已经保存成功,则直接返回
if (b != null) {
return b;
}
// 执行新增逻辑
add();
}
}
情况说明:
(1)
@Transactional(propagation = Propagation.REQUIRES_NEW):创建新的事务,如果事务已存在则挂起原事务,并创建新的事务
特性:
1.标志REQUIRES_NEW会新开启事务,外层事务不会影响内部事务的提交/回滚
2.标志REQUIRES_NEW的内部事务的异常,会影响外部事务的回滚
白说就是说,methodB事务可以单独提交回滚,这样就可以通过新的事务,进行数据库查询再次查询到已提交的数据。
(2)@DistributedLock实现了分布式锁功能,这里的意思是直到获取到锁才执行methodB方法
问题:通过Jmeter执行并发测试,10个线程,发现都会直接执行到新增逻辑。
解决方案
现在的问题就是,有两个切面都对methodB方法进行了拦截,发生上述问题的原因就是,事务还没有提交完成,下一个获取到分布式锁的线程就执行了methodB,此时肯定是查询不到未提交的数据的。那解决方案应该就是,让事务先执行完,再获取分布式锁。
要完成这个功能,我们需要了解下@Order注解
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD, ElementType.FIELD})
@Documented
public @interface Order {
// 值越小,优先级越高,默认优先级最低
int value() default 2147483647;
}
因为当时并没有设置切面的Order,也没有设置事务管理器的Order,所以默认都是2147483647,那么我们需要将分布式锁的切面的Order的值改成比2147483647小就行,例20,即自定义分布式锁的切面先执行,事务切面后执行,示意图如下:
综上,下一个获取分布式锁的线程是在上一个线程提交事务执行执行methodB方法,所以此时是没问题的