我目前正在将遗留代码从 spring-boot v2.7.1 升级到 spring-boot v3.1.0。 不幸的是,我从未直接接触过 JDBC。我主要使用JPA。
我得到的错误如下:
org.springframework.dao.CannotAcquireLockException: Failed to lock mutex at b02643f1-227e-4893-9260-cdbi18x6f377
...
Caused by: java.lang.NullPointerException: Cannot invoke "org.springframework.transaction.support.TransactionTemplate.execute(org.springframework.transaction.support.TransactionCallback)" because "this.serializableTransactionTemplate" is null
at org.springframework.integration.jdbc.lock.DefaultLockRepository.acquire(DefaultLockRepository.java:343)
at org.springframework.integration.jdbc.lock.JdbcLockRegistry$JdbcLock.doLock(JdbcLockRegistry.java:272)
at org.springframework.integration.jdbc.lock.JdbcLockRegistry$JdbcLock.tryLock(JdbcLockRegistry.java:253)
... 73 more
JDBC 的使用方式如下:
private JdbcLockRegistry getJdbcLockRegistry() {
final var dataSource = getDatasource
final var defaultLockRepository = new DefaultLockRepository(dataSource);
defaultLockRepository.setPrefix(getSchema());
defaultLockRepository.afterPropertiesSet();
return new JdbcLockRegistry(defaultLockRepository);
}
现在,DefaultLockRepository 类由于升级而发生了变化:
@Repository
注释private ApplicationContext applicationContext;
private PlatformTransactionManager transactionManager;
private TransactionTemplate defaultTransactionTemplate;
private TransactionTemplate readOnlyTransactionTemplate;
private TransactionTemplate serializableTransactionTemplate;
调试后,我得出结论,我必须按以下方式使用它:
@Autowired
ApplicationContext context;
private JdbcLockRegistry getJdbcLockRegistry() {
final var dataSource = getDatasource();
final var defaultLockRepository = new DefaultLockRepository(dataSource);
defaultLockRepository.setApplicationContext(context);
defaultLockRepository.setPrefix(getSchema());
defaultLockRepository.afterSingletonsInstantiated();
defaultLockRepository.afterPropertiesSet();
return new JdbcLockRegistry(defaultLockRepository);
}
这有效吗?为什么我必须自己自动装配 ApplicationContext?通常,Spring 处理 Bean 的注入,因此处理 ApplicationContext 本身? 我对这些变化感到相当困惑。测试进行得很艰难。
发生这种情况可能是因为有代码实例化了一些依赖于 JdbcLockRegistry 的单例 bean 并开始使用它。 问题是
org.springframework.integration.jdbc.lock.DefaultLockRepository#afterSingletonsInstantiated
在单例预实例化阶段结束时调用,并保证所有常规单例 bean 都已创建。
例如,下面的代码将导致类似的错误:
@Bean
public LockRegistryLeaderInitiator leaderInitiator(LockRegistry lockRegistry) {
var lockRegistryLeaderInitiator = new LockRegistryLeaderInitiator(lockRegistry);
lockRegistryLeaderInitiator.start();
return lockRegistryLeaderInitiator;
}
要修复它,我们需要将
lockRegistryLeaderInitiator.start()
移到外面。一种可能的解决方案是在 Spring 上下文初始化后运行它:
@EventListener
public void onApplicationEvent(ContextRefreshedEvent event) {
lockRegistryLeaderInitiator.start();
}