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

Reply via email to