Reputation: 107
I am currently upgrading legacy code from spring-boot v2.7.1 to spring-boot v3.1.0. Unfortunately, I never had direct contact with JDBC. I mostly use JPA.
The error I get is the following:
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 was used in the following way:
private JdbcLockRegistry getJdbcLockRegistry() {
final var dataSource = getDatasource
final var defaultLockRepository = new DefaultLockRepository(dataSource);
defaultLockRepository.setPrefix(getSchema());
defaultLockRepository.afterPropertiesSet();
return new JdbcLockRegistry(defaultLockRepository);
}
Now, the DefaultLockRepository class changed because of the upgrade:
@Repository
any moreprivate ApplicationContext applicationContext;
private PlatformTransactionManager transactionManager;
private TransactionTemplate defaultTransactionTemplate;
private TransactionTemplate readOnlyTransactionTemplate;
private TransactionTemplate serializableTransactionTemplate;
After debugging, I concluded, that I have to use it the following way:
@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);
}
Is this valid? Why Do I have to autowire the ApplicationContext myself? Normally, Spring handles injection of Beans and therefore the ApplicationContext itself? I am fairly confused by the changes. The tests are running tough.
Upvotes: 2
Views: 1389
Reputation: 521
Probably this happens because there is code that instantiates some singleton bean that depends on JdbcLockRegistry and starts using it.
The problem is that org.springframework.integration.jdbc.lock.DefaultLockRepository#afterSingletonsInstantiated
Invoked right at the end of the singleton pre-instantiation phase, with a guarantee that all regular singleton beans have been created already.
For example, the next code will cause a similar error:
@Bean
public LockRegistryLeaderInitiator leaderInitiator(LockRegistry lockRegistry) {
var lockRegistryLeaderInitiator = new LockRegistryLeaderInitiator(lockRegistry);
lockRegistryLeaderInitiator.start();
return lockRegistryLeaderInitiator;
}
To fix it we need to move lockRegistryLeaderInitiator.start()
outside. One possible solution is to run it after the Spring context has been initialized:
@EventListener
public void onApplicationEvent(ContextRefreshedEvent event) {
lockRegistryLeaderInitiator.start();
}
Upvotes: 0