This is an automated email from the ASF dual-hosted git repository. davsclaus pushed a commit to branch CAMEL-26851 in repository https://gitbox.apache.org/repos/asf/camel.git
commit 3ddbf0c1260ac926dc8b256879669e30a983bfdc Author: Claus Ibsen <[email protected]> AuthorDate: Wed Nov 11 12:17:10 2026 +0100 CAMEL-26851: camel-jbang - camel run uses the local transaction manager when the application has one DataSource camel run bound a placeholder policy for transacted, which runs the steps without a transaction (CAMEL-26850). When the transacted has no policy of its own, the registry has no transaction manager but exactly one DataSource (such as the one of spring.datasource.*), and spring-jdbc is on the classpath, it now downloads camel-spring instead, and Camel makes the local transaction manager for the DataSource. The placeholder and its WARN stay for the other cases; the WARN now says what gives transactions. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_016Y4dDi5iQNgtHrDdz2NpwF --- dsl/camel-kamelet-main/pom.xml | 6 ++++ .../camel/main/download/TransactedDownloader.java | 29 ++++++++++++++++++-- .../main/download/TransactedDownloaderTest.java | 32 ++++++++++++++++++++++ 3 files changed, 64 insertions(+), 3 deletions(-) diff --git a/dsl/camel-kamelet-main/pom.xml b/dsl/camel-kamelet-main/pom.xml index f09d2aa98791..c981972f75d2 100644 --- a/dsl/camel-kamelet-main/pom.xml +++ b/dsl/camel-kamelet-main/pom.xml @@ -183,6 +183,12 @@ </dependency> <!-- test --> + <dependency> + <groupId>org.springframework</groupId> + <artifactId>spring-jdbc</artifactId> + <version>${spring-version}</version> + <scope>test</scope> + </dependency> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter-api</artifactId> diff --git a/dsl/camel-kamelet-main/src/main/java/org/apache/camel/main/download/TransactedDownloader.java b/dsl/camel-kamelet-main/src/main/java/org/apache/camel/main/download/TransactedDownloader.java index a455a36c1359..c4934de8f436 100644 --- a/dsl/camel-kamelet-main/src/main/java/org/apache/camel/main/download/TransactedDownloader.java +++ b/dsl/camel-kamelet-main/src/main/java/org/apache/camel/main/download/TransactedDownloader.java @@ -16,6 +16,8 @@ */ package org.apache.camel.main.download; +import javax.sql.DataSource; + import org.apache.camel.CamelContext; import org.apache.camel.NamedNode; import org.apache.camel.Processor; @@ -59,10 +61,16 @@ public class TransactedDownloader { if (downloader != null) { downloader.downloadDependency("org.apache.camel", "camel-jta", route.getCamelContext().getVersion()); - if (bindPassThroughPolicies(route.getCamelContext(), transacted)) { + if (usesLocalTransactionManager(route.getCamelContext(), transacted)) { + // CAMEL-26851: Camel makes a local transaction manager for the one DataSource, and the + // policy of it, which is in camel-spring + downloader.downloadDependency("org.apache.camel", "camel-spring", + route.getCamelContext().getVersion()); + } else if (bindPassThroughPolicies(route.getCamelContext(), transacted)) { LOG.warn("Route {} uses transacted, but there is no transaction manager: the route runs" - + " without a transaction. Declare a transaction manager bean, or export to" - + " Spring Boot or Quarkus, for transactions.", + + " without a transaction. Declare one DataSource (spring.datasource.*) with" + + " spring-jdbc on the classpath, as the sql component has, or a transaction" + + " manager bean, or export to Spring Boot or Quarkus, for transactions.", route.getRouteId()); } } @@ -92,6 +100,21 @@ public class TransactedDownloader { return true; } + /** + * Whether Camel makes a local transaction manager for the transacted (CAMEL-26851): it has no policy or reference + * of its own, the registry has no transaction manager but exactly one DataSource, and spring-jdbc is on the + * classpath. + */ + static boolean usesLocalTransactionManager(CamelContext context, TransactedDefinition transacted) { + if (transacted.getPolicy() != null || transacted.getRef() != null || hasTransactionManager(context)) { + return false; + } + // with the application class loader, as for the PlatformTransactionManager below + Class<?> jtm = ObjectHelper.loadClass("org.springframework.jdbc.support.JdbcTransactionManager", + context.getApplicationContextClassLoader()); + return jtm != null && context.getRegistry().findByType(DataSource.class).size() == 1; + } + /** * Whether the registry has a transacted policy of the user's own, or a Spring PlatformTransactionManager, of which * Camel makes the policy. diff --git a/dsl/camel-kamelet-main/src/test/java/org/apache/camel/main/download/TransactedDownloaderTest.java b/dsl/camel-kamelet-main/src/test/java/org/apache/camel/main/download/TransactedDownloaderTest.java index bd4e3e007516..ca7da114c7b2 100644 --- a/dsl/camel-kamelet-main/src/test/java/org/apache/camel/main/download/TransactedDownloaderTest.java +++ b/dsl/camel-kamelet-main/src/test/java/org/apache/camel/main/download/TransactedDownloaderTest.java @@ -26,6 +26,7 @@ import org.apache.camel.impl.DefaultCamelContext; import org.apache.camel.model.TransactedDefinition; import org.apache.camel.spi.TransactedPolicy; import org.junit.jupiter.api.Test; +import org.springframework.jdbc.datasource.DriverManagerDataSource; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; @@ -82,6 +83,37 @@ public class TransactedDownloaderTest { } } + @Test + public void oneDataSourceWithSpringJdbcGetsALocalTransactionManager() throws Exception { + // CAMEL-26851: Camel makes a local transaction manager for it, so no placeholder is bound + try (CamelContext context = new DefaultCamelContext()) { + context.getRegistry().bind("orders", new DriverManagerDataSource("jdbc:h2:mem:orders")); + assertTrue(TransactedDownloader.usesLocalTransactionManager(context, new TransactedDefinition())); + } + } + + @Test + public void noLocalTransactionManagerWithoutExactlyOneDataSource() throws Exception { + try (CamelContext context = new DefaultCamelContext()) { + assertFalse(TransactedDownloader.usesLocalTransactionManager(context, new TransactedDefinition())); + context.getRegistry().bind("orders", new DriverManagerDataSource("jdbc:h2:mem:orders")); + context.getRegistry().bind("audit", new DriverManagerDataSource("jdbc:h2:mem:audit")); + assertFalse(TransactedDownloader.usesLocalTransactionManager(context, new TransactedDefinition())); + } + } + + @Test + public void noLocalTransactionManagerForAPolicyOfItsOwn() throws Exception { + try (CamelContext context = new DefaultCamelContext()) { + context.getRegistry().bind("orders", new DriverManagerDataSource("jdbc:h2:mem:orders")); + TransactedDefinition transacted = new TransactedDefinition(); + transacted.setRef("myPolicy"); + assertFalse(TransactedDownloader.usesLocalTransactionManager(context, transacted)); + context.getRegistry().bind("PROPAGATION_REQUIRED", new CountingPolicy()); + assertFalse(TransactedDownloader.usesLocalTransactionManager(context, new TransactedDefinition())); + } + } + private static RouteBuilder orders() { return new RouteBuilder() { @Override
