总结
- 初始化时,将所有切面加载到一个域成员变量的Map缓存中,加载时会将每个切面类中的切面方法进行排序
- 切面方法中的排序方式,首先根据切面注解触发的顺序排序,然后根据字母序进行排序
- 初始化完成后,每个切面类中的切面方法的顺序就不会再次改变了
- 每次调用切面命中的业务代码时,会触发切面扫描,筛选出匹配的切面方法,根据切面方法所在的切面类,通过order属性的值,做一次排序,这次排序不会更改之前同一个类型中切面方法的相对顺序
- 根据上边几步的排序结果,依次触发切面的逻辑
上面分析的排序是指业务切面之间的排序逻辑,但是当业务切面和事务切面都存在的话,如果不指定order的值,那么事务切面的执行顺序始终都会先于业务切面,不会按照切面名字来排序。
![[Pasted image 20260102135617.png]]
当前方法事务还没有提交,但是锁已经释放,其它用户获得到锁时事务还未提交,导致数据库中并没有插入订单,
- 线程 A 和 线程 B 同时发起请求,都进入到 Spring AOP 链。
- 因为
@Transactional的默认优先级高于你自定义的@ServiceLock,事务拦截器会先给 A 和 B 各自打开一个数据库事务(但还没执行任何 SQL)。 - 下一步判断 锁 AOP:
- A 线程拿到本地锁→拿到分布式锁→执行业务(
getById→originalNumber +1→updateById)→释放锁。 - B 线程因为锁被 A 持有,被阻塞在锁上,等 A 完成释放后才拿到锁。
- 但注意:A、B 的事务都是同时打开的,B 线程在排队期间它的事务早已开始;当它拿到锁后,还是在自己旧事务里再做一次
getById。 - 隔离级别是
REPEATABLE_READ(MySQL 默认),B 在事务开始时已经把“快照”读进来了,第二次**getById**反而会重用事务快照,继续拿到旧值。 - B 在自己的事务里基于旧值再 +1,
updateById覆盖了 A 的结果,导致“一个加法”丢失。
同样,即便用 READ_COMMITTED,提交时机也不在锁保护范围内:
- A 线程释放分布式锁后,才真正在事务拦截器里提交事务;
- B 线程拿锁后立即在同一个已打开的事务里读入数据并更新,然后提交 —— 中间并没有再加一层锁来保护“读+写+提交”这一整个过程。