实测 Spring Framework 7 内置的 @Retryable:默认 maxRetries = 3、delay = 1000 ms(执行时刻 5、1014、2019 ms);multiplier = 2、maxDelay = 500 时间隔 100、200、400、500、500;includes / excludes / predicate 筛选异常(嵌套的 cause 也参与匹配);次数用完抛原始异常,timeout 把 maxRetries = 10 截断成 3 次;RetryTemplate 次数用完抛 RetryException;@ConcurrencyLimit(2) 把最大并发限制成 2。
实测怎么从代理拿回原始对象与类:AopUtils.getTargetClass 只脱一层,AopProxyUtils.ultimateTargetClass 一路脱到底,ClassUtils.getUserClass 不处理 JDK 代理;CGLIB 代理类上 getAnnotation 找不到目标类注解(findAnnotation 才行);代理与目标 equals、hashCode 都不相等;两个切面加 @Async 合并成同一个代理的 4 个拦截器。
实测 @Transactional 的实现:事务管理器依次 doGetTransaction、doBegin(拿连接、关 autocommit、绑定线程)、doCommit 或 doRollback、doCleanupAfterCompletion;事务里两条 insert 用同一个连接(connection_id 都是 12);MySQL general_log 里一个事务是 SET autocommit=0 … COMMIT … SET autocommit=1 共 7 条,没有 @Transactional 的两条 insert 只有两条语句本身。
实测事务传播行为 7 种:外面有事务时 REQUIRED、NESTED、SUPPORTS、MANDATORY 加入同一个连接,REQUIRES_NEW 与 NOT_SUPPORTED 用另一个连接,NEVER 抛异常;外层回滚时 REQUIRES_NEW 与 NOT_SUPPORTED 的行保留;inner 抛异常被外层吞掉时 REQUIRED 在提交时抛 UnexpectedRollbackException,REQUIRES_NEW 与 NESTED 只回滚自己;外面没有事务时 MANDATORY 抛异常。
实测事务回滚规则:RuntimeException 与 Error 回滚,受检异常 IOException 默认提交(表里剩 1 行);rollbackFor 才会回滚;方法内部吞掉异常就提交,setRollbackOnly() 可以手动回滚;rollbackFor 与 noRollbackFor 同时匹配时继承层次上更近的一条赢。
实测 @Transactional 不生效的场景:自调用、private、final、吞掉异常、事务里开线程、事务管理器绑错数据库、MyISAM 表、new 出来的对象都不回滚;Spring 7 的 CGLIB 代理下 package-private 与 protected 的方法是生效的;默认 JDK 代理只能按接口取 Bean。
实测 readOnly = true 的效果:JDBC 一侧靠 SET SESSION TRANSACTION READ ONLY 开始、READ WRITE 结束,里面用 JdbcTemplate 写会被驱动在客户端拒绝;Hibernate 一侧实体修改不再刷到数据库(没有 UPDATE,手动 em.flush() 也不报错,修改悄悄丢掉);加入外层读写事务时内层的 readOnly 无效。
实测事务资源绑定在线程上:事务内线程上绑着 HikariDataSource → ConnectionHolder,子线程里事务不活跃、资源 0 个;同步回调提交时依次是 beforeCommit、beforeCompletion、afterCommit、afterCompletion(COMMITTED),回滚时没有 beforeCommit 和 afterCommit;afterCommit 抛异常撤销不了提交;绕过 Spring 直接 ds.getConnection() 拿到另一个连接,写入不会回滚。
实测 TransactionTemplate:回调抛 RuntimeException 或 setRollbackOnly() 都回滚;在同一个类里使用没有自调用问题;事务超时通过语句超时实现(setTimeout(1) 约 1025 ms 取消 select sleep(3));把 300 ms 的非数据库工作移出事务后,8 线程 2 连接的池从 1225 ms 降到 308 ms。
实测 NESTED 就是 JDBC Savepoint:MySQL 上依次出现 SAVEPOINT、(失败时 ROLLBACK TO SAVEPOINT)、RELEASE SAVEPOINT,整个过程只有一个 COMMIT;每次多出两条语句,耗时是 REQUIRED 的两倍多;JpaTransactionManager 默认不允许 NESTED,打开之后 Hibernate 的 JpaDialect 仍然不支持 savepoint。
实测多数据源下一个事务管理器只管一个数据源:@Transactional(txA) 写 A 和 B 后抛异常,A 回滚而 B 留下 1 行;嵌套模板先提交的一方撤销不了,后提交的失败会让两库不一致(七个场景里 4 个不一致);只有恰好是先提交的那一方失败时才碰巧一致。
实测 @TransactionalEventListener:普通 @EventListener 在事务里立刻执行,AFTER_COMMIT 在提交之后,回滚时收到 AFTER_ROLLBACK;@Transactional(REQUIRED) 的 AFTER_COMMIT 监听器在 Spring 7.0.9 启动时就被拒绝;@Async @EventListener 有 10% 读不到刚提交的订单(90/100),换成 AFTER_COMMIT 是 100/100;AFTER_COMMIT 监听器抛异常只记日志、不传给调用者。