Skip to content

fix: resolve JPA transaction managers for Storm-initiated transactions - #442

Merged
zantvoort merged 1 commit into
mainfrom
fix/jpa-transaction-manager-resolution
Aug 10, 2026
Merged

fix: resolve JPA transaction managers for Storm-initiated transactions#442
zantvoort merged 1 commit into
mainfrom
fix/jpa-transaction-manager-resolution

Conversation

@zantvoort

Copy link
Copy Markdown
Collaborator

Problem

SpringTransactionContext.resolveTransactionManager filtered the application's transaction managers on DataSourceTransactionManager. An application with spring-boot-starter-data-jpa gets a JpaTransactionManager (Boot's DataSourceTransactionManagerAutoConfiguration backs off), so Storm-initiated transaction { } blocks failed with "No TransactionManager found for DataSource". The asymmetry made it hard to diagnose: @Transactional plus Storm kept working through DataSourceUtils, so only Storm-initiated transactions failed, and CI never saw it because every test config registered a DataSourceTransactionManager explicitly.

Fix

Resolution now matches a manager that owns the touched DataSource:

  • any ResourceTransactionManager working directly on the DataSource, which covers DataSourceTransactionManager and JdbcTransactionManager;
  • a JpaTransactionManager whose entity manager factory is backed by the DataSource, which is what Spring Boot registers when JPA is on the class path.

The JPA branch is checked first (JpaTransactionManager is itself a ResourceTransactionManager, and getResourceFactory() throws when no entity manager factory is set) and sits behind a class-presence check, with spring-orm as an optional (provided, requires static) dependency, so JDBC-only applications are unaffected. A manager that cannot report its resource factory counts as owning nothing rather than failing resolution.

When several managers own the same DataSource, resolution fails fast naming the candidates instead of picking by list order: the choice decides which manager completes the transaction (a JPA manager flushes the persistence context on commit), so it must be made by configuration. The error points at the remedy: keep one manager per DataSource, or define a TransactionTemplateProvider bean constructed with the manager that must own Storm-initiated transactions.

StormTransactionAutoConfiguration additionally gains name-based ordering hints for HibernateJpaAutoConfiguration (Spring Boot 3 and 4 locations), so @ConditionalOnBean(PlatformTransactionManager) sees the JPA-registered manager instead of silently backing off.

Behavior change

An application that registers both a DataSourceTransactionManager and a JpaTransactionManager over the same DataSource — the workaround for this very issue — previously had Storm silently use the JDBC one; it now gets the descriptive ambiguity error. Removing the redundant JDBC manager restores a single, unambiguous owner.

Tests

  • SpringJpaTransactionBridgeTest: transaction { } under a real JpaTransactionManager (Hibernate entity manager factory, no JPA entities) — commit, rollback-only, REQUIRES_NEW, joining a JPA-managed Spring transaction, a JPA manager for a different DataSource being skipped, and the ambiguity fail-fast.
  • StormTransactionAutoConfigurationTest: ApplicationContextRunner activation with a JDBC manager, with a JPA manager, and back-off without any; the Storm auto-configuration is listed first so the declared ordering hints are what the test proves.
  • Full suites green: storm-spring (75), storm-kotlin-spring (253), both starters and the test auto-configure module. The starters have no spring-orm even in test scope, so their runs exercise the spring-orm-absent path.

Fixes #383

The transaction bridge resolved the manager for a DataSource by filtering
on DataSourceTransactionManager. An application with
spring-boot-starter-data-jpa gets a JpaTransactionManager instead, so
Storm-initiated transaction blocks failed with "No TransactionManager
found for DataSource" while @transactional kept working through
DataSourceUtils.

Resolution now matches any ResourceTransactionManager working directly
on the DataSource, plus a JpaTransactionManager whose entity manager
factory is backed by it. The JPA branch sits behind a class-presence
check with spring-orm as an optional dependency, so JDBC-only
applications are unaffected. When several managers own the same
DataSource, resolution fails fast naming the candidates: the choice
decides which manager completes the transaction, so it must be made by
configuration rather than list order.

The transaction auto-configuration also gains ordering hints for the
Hibernate JPA auto-configuration (Spring Boot 3 and 4 locations), so
@ConditionalOnBean(PlatformTransactionManager) sees the JPA-registered
manager.

Fixes #383
@zantvoort
zantvoort merged commit cf0a4b9 into main Aug 10, 2026
7 checks passed
@zantvoort
zantvoort deleted the fix/jpa-transaction-manager-resolution branch August 10, 2026 20:39
@codecov

codecov Bot commented Aug 10, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 66.66667% with 7 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
...a/st/orm/spring/impl/SpringTransactionContext.java 66.66% 3 Missing and 4 partials ⚠️

📢 Thoughts on this report? Let us know!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Storm-initiated transactions fail when a JpaTransactionManager is on the classpath

1 participant