This is an automated email from the ASF dual-hosted git repository.

davsclaus pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel.git


The following commit(s) were added to refs/heads/main by this push:
     new a151475a461b CAMEL-26106: camel-jpa: don't hard-wire the Spring based 
DefaultTransactionStrategy as the default (#28496)
a151475a461b is described below

commit a151475a461b052e538a859e84d8c173f4a2904c
Author: Jiří Ondrušek <[email protected]>
AuthorDate: Tue Nov 3 18:05:49 2026 +0100

    CAMEL-26106: camel-jpa: don't hard-wire the Spring based 
DefaultTransactionStrategy as the default (#28496)
    
    Co-Authored-By: Claude Opus 5.5 <[email protected]>
---
 .../org/apache/camel/catalog/components/jpa.json   |   2 +-
 .../apache/camel/catalog/docs/jpa-component.adoc   |  26 ++--
 .../component/jpa/JpaComponentConfigurer.java      |   2 +-
 .../org/apache/camel/component/jpa/jpa.json        |   2 +-
 .../camel-jpa/src/main/docs/jpa-component.adoc     |  26 ++--
 .../apache/camel/component/jpa/JpaComponent.java   |   8 +-
 .../apache/camel/component/jpa/JpaEndpoint.java    |   6 +-
 .../org/apache/camel/component/jpa/JpaHelper.java  |  40 ++++++
 .../idempotent/jpa/JpaMessageIdRepository.java     |   8 +-
 .../keyvalue/jpa/JpaKeyValueRepository.java        |   3 +-
 .../camel/component/jpa/JpaComponentTest.java      | 152 +++++++++++++++++++++
 .../idempotent/jpa/JpaMessageIdRepositoryTest.java |  45 ++++++
 .../keyvalue/jpa/JpaKeyValueRepositoryTest.java    |   8 ++
 .../ROOT/pages/camel-4x-upgrade-guide-4_24.adoc    |  14 ++
 14 files changed, 302 insertions(+), 40 deletions(-)

diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/jpa.json
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/jpa.json
index 9a323a66c6a7..33821f57db5d 100644
--- 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/jpa.json
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/jpa.json
@@ -28,7 +28,7 @@
     "entityManagerFactory": { "index": 1, "kind": "property", "displayName": 
"Entity Manager Factory", "group": "common", "label": "", "required": false, 
"type": "object", "javaType": "jakarta.persistence.EntityManagerFactory", 
"deprecated": false, "autowired": true, "secret": false, "description": "To use 
the EntityManagerFactory. This is strongly recommended to configure." },
     "joinTransaction": { "index": 2, "kind": "property", "displayName": "Join 
Transaction", "group": "common", "label": "", "required": false, "type": 
"boolean", "javaType": "boolean", "deprecated": false, "autowired": false, 
"secret": false, "defaultValue": true, "description": "The camel-jpa component 
will join transaction by default. You can use this option to turn this off, for 
example if you use LOCAL_RESOURCE and join transaction doesn't work with your 
JPA provider. This option can [...]
     "sharedEntityManager": { "index": 3, "kind": "property", "displayName": 
"Shared Entity Manager", "group": "common", "label": "", "required": false, 
"type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": 
false, "secret": false, "defaultValue": false, "description": "Whether to use 
Spring's SharedEntityManager for the consumer\/producer. Note in most cases 
joinTransaction should be set to false as this is not an EXTENDED 
EntityManager." },
-    "transactionStrategy": { "index": 4, "kind": "property", "displayName": 
"Transaction Strategy", "group": "common", "label": "", "required": false, 
"type": "object", "javaType": 
"org.apache.camel.component.jpa.TransactionStrategy", "deprecated": false, 
"autowired": false, "secret": false, "description": "To use the 
TransactionStrategy for running the operations in a transaction." },
+    "transactionStrategy": { "index": 4, "kind": "property", "displayName": 
"Transaction Strategy", "group": "common", "label": "", "required": false, 
"type": "object", "javaType": 
"org.apache.camel.component.jpa.TransactionStrategy", "deprecated": false, 
"autowired": true, "secret": false, "description": "To use the 
TransactionStrategy for running the operations in a transaction." },
     "bridgeErrorHandler": { "index": 5, "kind": "property", "displayName": 
"Bridge Error Handler", "group": "consumer", "label": "consumer", "required": 
false, "type": "boolean", "javaType": "boolean", "deprecated": false, 
"autowired": false, "secret": false, "defaultValue": false, "description": 
"Allows for bridging the consumer to the Camel routing Error Handler, which 
mean any exceptions (if possible) occurred while the Camel consumer is trying 
to pickup incoming messages, or the like [...]
     "lazyStartProducer": { "index": 6, "kind": "property", "displayName": 
"Lazy Start Producer", "group": "producer", "label": "producer", "required": 
false, "type": "boolean", "javaType": "boolean", "deprecated": false, 
"autowired": false, "secret": false, "defaultValue": false, "description": 
"Whether the producer should be started lazy (on the first message). By 
starting lazy you can use this to allow CamelContext and routes to startup in 
situations where a producer may otherwise fail [...]
     "autowiredEnabled": { "index": 7, "kind": "property", "displayName": 
"Autowired Enabled", "group": "advanced", "label": "advanced", "required": 
false, "type": "boolean", "javaType": "boolean", "deprecated": false, 
"autowired": false, "secret": false, "defaultValue": true, "description": 
"Whether autowiring is enabled. This is used for automatic autowiring options 
(the option must be marked as autowired) by looking up in the registry to find 
if there is a single instance of matching t [...]
diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/jpa-component.adoc
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/jpa-component.adoc
index a98afb62038f..af156f128bb8 100644
--- 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/jpa-component.adoc
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/jpa-component.adoc
@@ -145,20 +145,20 @@ log a WARN.
 The `TransactionStrategy` is a vendor neutral abstraction that allows 
`camel-jpa`
 to easily plug in and work with Spring `TransactionManager` or Quarkus 
Transaction API.
 
-The `JpaComponent` looks up automatically the
-`TransactionStrategy` from the Registry. If Camel
-cannot find any `TransactionStrategy` instance registered, it will also
-look up for the `TransactionTemplate` and try to
-extract `TransactionStrategy` from it.
-
-If none `TransactionTemplate` is available in the registry,
-`JpaEndpoint` will auto create a default instance 
(`org.apache.camel.component.jpa.DefaultTransactionStrategy`)
-of `TransactionStrategy` which most often is not what you want.
-
-If more than single instance of the `TransactionStrategy` is found, Camel
-will log a WARN. In such cases you might want to instantiate and
+When no `TransactionStrategy` is configured, the `JpaComponent` and 
`JpaEndpoint`
+use the `TransactionStrategy` from the Registry, if there is exactly one and 
autowiring is enabled.
+Otherwise, they create the Spring based 
`org.apache.camel.component.jpa.DefaultTransactionStrategy`,
+which uses the single `PlatformTransactionManager` (or `TransactionTemplate`) 
from the Registry,
+or else creates a `JpaTransactionManager` for the `EntityManagerFactory`.
+
+The `TransactionStrategy` from the Registry is used whatever the 
`EntityManagerFactory` is.
+If it is bound to one `EntityManagerFactory`, configure the 
`transactionStrategy` option explicitly
+on a JPA component that uses another one.
+
+If there is more than one `TransactionStrategy` in the Registry, none of them 
is used, and Camel logs a WARN.
+In such cases you might want to instantiate and
 explicitly configure a JPA component that references the
-`myTransactionManager` transaction manager, as follows:
+`myTransactionStrategy` transaction strategy, as follows:
 
 [source,xml]
 ----
diff --git 
a/components/camel-jpa/src/generated/java/org/apache/camel/component/jpa/JpaComponentConfigurer.java
 
b/components/camel-jpa/src/generated/java/org/apache/camel/component/jpa/JpaComponentConfigurer.java
index 801cd9427d27..bc41eeb4ce0e 100644
--- 
a/components/camel-jpa/src/generated/java/org/apache/camel/component/jpa/JpaComponentConfigurer.java
+++ 
b/components/camel-jpa/src/generated/java/org/apache/camel/component/jpa/JpaComponentConfigurer.java
@@ -49,7 +49,7 @@ public class JpaComponentConfigurer extends 
PropertyConfigurerSupport implements
 
     @Override
     public String[] getAutowiredNames() {
-        return new String[]{"entityManagerFactory"};
+        return new String[]{"entityManagerFactory", "transactionStrategy"};
     }
 
     @Override
diff --git 
a/components/camel-jpa/src/generated/resources/META-INF/org/apache/camel/component/jpa/jpa.json
 
b/components/camel-jpa/src/generated/resources/META-INF/org/apache/camel/component/jpa/jpa.json
index 9a323a66c6a7..33821f57db5d 100644
--- 
a/components/camel-jpa/src/generated/resources/META-INF/org/apache/camel/component/jpa/jpa.json
+++ 
b/components/camel-jpa/src/generated/resources/META-INF/org/apache/camel/component/jpa/jpa.json
@@ -28,7 +28,7 @@
     "entityManagerFactory": { "index": 1, "kind": "property", "displayName": 
"Entity Manager Factory", "group": "common", "label": "", "required": false, 
"type": "object", "javaType": "jakarta.persistence.EntityManagerFactory", 
"deprecated": false, "autowired": true, "secret": false, "description": "To use 
the EntityManagerFactory. This is strongly recommended to configure." },
     "joinTransaction": { "index": 2, "kind": "property", "displayName": "Join 
Transaction", "group": "common", "label": "", "required": false, "type": 
"boolean", "javaType": "boolean", "deprecated": false, "autowired": false, 
"secret": false, "defaultValue": true, "description": "The camel-jpa component 
will join transaction by default. You can use this option to turn this off, for 
example if you use LOCAL_RESOURCE and join transaction doesn't work with your 
JPA provider. This option can [...]
     "sharedEntityManager": { "index": 3, "kind": "property", "displayName": 
"Shared Entity Manager", "group": "common", "label": "", "required": false, 
"type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": 
false, "secret": false, "defaultValue": false, "description": "Whether to use 
Spring's SharedEntityManager for the consumer\/producer. Note in most cases 
joinTransaction should be set to false as this is not an EXTENDED 
EntityManager." },
-    "transactionStrategy": { "index": 4, "kind": "property", "displayName": 
"Transaction Strategy", "group": "common", "label": "", "required": false, 
"type": "object", "javaType": 
"org.apache.camel.component.jpa.TransactionStrategy", "deprecated": false, 
"autowired": false, "secret": false, "description": "To use the 
TransactionStrategy for running the operations in a transaction." },
+    "transactionStrategy": { "index": 4, "kind": "property", "displayName": 
"Transaction Strategy", "group": "common", "label": "", "required": false, 
"type": "object", "javaType": 
"org.apache.camel.component.jpa.TransactionStrategy", "deprecated": false, 
"autowired": true, "secret": false, "description": "To use the 
TransactionStrategy for running the operations in a transaction." },
     "bridgeErrorHandler": { "index": 5, "kind": "property", "displayName": 
"Bridge Error Handler", "group": "consumer", "label": "consumer", "required": 
false, "type": "boolean", "javaType": "boolean", "deprecated": false, 
"autowired": false, "secret": false, "defaultValue": false, "description": 
"Allows for bridging the consumer to the Camel routing Error Handler, which 
mean any exceptions (if possible) occurred while the Camel consumer is trying 
to pickup incoming messages, or the like [...]
     "lazyStartProducer": { "index": 6, "kind": "property", "displayName": 
"Lazy Start Producer", "group": "producer", "label": "producer", "required": 
false, "type": "boolean", "javaType": "boolean", "deprecated": false, 
"autowired": false, "secret": false, "defaultValue": false, "description": 
"Whether the producer should be started lazy (on the first message). By 
starting lazy you can use this to allow CamelContext and routes to startup in 
situations where a producer may otherwise fail [...]
     "autowiredEnabled": { "index": 7, "kind": "property", "displayName": 
"Autowired Enabled", "group": "advanced", "label": "advanced", "required": 
false, "type": "boolean", "javaType": "boolean", "deprecated": false, 
"autowired": false, "secret": false, "defaultValue": true, "description": 
"Whether autowiring is enabled. This is used for automatic autowiring options 
(the option must be marked as autowired) by looking up in the registry to find 
if there is a single instance of matching t [...]
diff --git a/components/camel-jpa/src/main/docs/jpa-component.adoc 
b/components/camel-jpa/src/main/docs/jpa-component.adoc
index a98afb62038f..af156f128bb8 100644
--- a/components/camel-jpa/src/main/docs/jpa-component.adoc
+++ b/components/camel-jpa/src/main/docs/jpa-component.adoc
@@ -145,20 +145,20 @@ log a WARN.
 The `TransactionStrategy` is a vendor neutral abstraction that allows 
`camel-jpa`
 to easily plug in and work with Spring `TransactionManager` or Quarkus 
Transaction API.
 
-The `JpaComponent` looks up automatically the
-`TransactionStrategy` from the Registry. If Camel
-cannot find any `TransactionStrategy` instance registered, it will also
-look up for the `TransactionTemplate` and try to
-extract `TransactionStrategy` from it.
-
-If none `TransactionTemplate` is available in the registry,
-`JpaEndpoint` will auto create a default instance 
(`org.apache.camel.component.jpa.DefaultTransactionStrategy`)
-of `TransactionStrategy` which most often is not what you want.
-
-If more than single instance of the `TransactionStrategy` is found, Camel
-will log a WARN. In such cases you might want to instantiate and
+When no `TransactionStrategy` is configured, the `JpaComponent` and 
`JpaEndpoint`
+use the `TransactionStrategy` from the Registry, if there is exactly one and 
autowiring is enabled.
+Otherwise, they create the Spring based 
`org.apache.camel.component.jpa.DefaultTransactionStrategy`,
+which uses the single `PlatformTransactionManager` (or `TransactionTemplate`) 
from the Registry,
+or else creates a `JpaTransactionManager` for the `EntityManagerFactory`.
+
+The `TransactionStrategy` from the Registry is used whatever the 
`EntityManagerFactory` is.
+If it is bound to one `EntityManagerFactory`, configure the 
`transactionStrategy` option explicitly
+on a JPA component that uses another one.
+
+If there is more than one `TransactionStrategy` in the Registry, none of them 
is used, and Camel logs a WARN.
+In such cases you might want to instantiate and
 explicitly configure a JPA component that references the
-`myTransactionManager` transaction manager, as follows:
+`myTransactionStrategy` transaction strategy, as follows:
 
 [source,xml]
 ----
diff --git 
a/components/camel-jpa/src/main/java/org/apache/camel/component/jpa/JpaComponent.java
 
b/components/camel-jpa/src/main/java/org/apache/camel/component/jpa/JpaComponent.java
index ffc8800abed5..c17f195c6ac4 100644
--- 
a/components/camel-jpa/src/main/java/org/apache/camel/component/jpa/JpaComponent.java
+++ 
b/components/camel-jpa/src/main/java/org/apache/camel/component/jpa/JpaComponent.java
@@ -43,7 +43,7 @@ public class JpaComponent extends HealthCheckComponent {
 
     @Metadata(autowired = true)
     private EntityManagerFactory entityManagerFactory;
-    @Metadata
+    @Metadata(autowired = true)
     private TransactionStrategy transactionStrategy;
     @Metadata(defaultValue = "true")
     private boolean joinTransaction = true;
@@ -185,7 +185,11 @@ public class JpaComponent extends HealthCheckComponent {
 
     private void createTransactionStrategy() {
         if (transactionStrategy == null && getEntityManagerFactory() != null) {
-            transactionStrategy = new 
DefaultTransactionStrategy(getCamelContext(), getEntityManagerFactory());
+            // a component added with CamelContext.addComponent is initialized 
before it is autowired,
+            // so look up the registry here too, unless autowiring is turned 
off
+            boolean autowired = getCamelContext().isAutowiredEnabled() && 
isAutowiredEnabled();
+            transactionStrategy
+                    = JpaHelper.resolveTransactionStrategy(getCamelContext(), 
getEntityManagerFactory(), autowired);
         }
     }
 
diff --git 
a/components/camel-jpa/src/main/java/org/apache/camel/component/jpa/JpaEndpoint.java
 
b/components/camel-jpa/src/main/java/org/apache/camel/component/jpa/JpaEndpoint.java
index c163cc2d20f6..415ac99972ad 100644
--- 
a/components/camel-jpa/src/main/java/org/apache/camel/component/jpa/JpaEndpoint.java
+++ 
b/components/camel-jpa/src/main/java/org/apache/camel/component/jpa/JpaEndpoint.java
@@ -622,8 +622,10 @@ public class JpaEndpoint extends ScheduledPollEndpoint {
         }
     }
 
-    protected DefaultTransactionStrategy createTransactionStrategy() {
-        return new DefaultTransactionStrategy(getCamelContext(), 
getEntityManagerFactory());
+    protected TransactionStrategy createTransactionStrategy() {
+        // the registry is only looked up when autowiring is enabled, as for 
the autowired endpoint options
+        boolean autowired = isAutowiredEnabled() && getComponent() != null && 
getComponent().isAutowiredEnabled();
+        return JpaHelper.resolveTransactionStrategy(getCamelContext(), 
getEntityManagerFactory(), autowired);
     }
 
     protected Expression createProducerExpression() {
diff --git 
a/components/camel-jpa/src/main/java/org/apache/camel/component/jpa/JpaHelper.java
 
b/components/camel-jpa/src/main/java/org/apache/camel/component/jpa/JpaHelper.java
index a71b2ccbcf86..8f63101cc2b3 100644
--- 
a/components/camel-jpa/src/main/java/org/apache/camel/component/jpa/JpaHelper.java
+++ 
b/components/camel-jpa/src/main/java/org/apache/camel/component/jpa/JpaHelper.java
@@ -22,7 +22,10 @@ import java.util.Map;
 import jakarta.persistence.EntityManager;
 import jakarta.persistence.EntityManagerFactory;
 
+import org.apache.camel.CamelContext;
 import org.apache.camel.Exchange;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 import org.springframework.orm.jpa.SharedEntityManagerCreator;
 
 /**
@@ -30,6 +33,8 @@ import org.springframework.orm.jpa.SharedEntityManagerCreator;
  */
 public final class JpaHelper {
 
+    private static final Logger LOG = LoggerFactory.getLogger(JpaHelper.class);
+
     private JpaHelper() {
     }
 
@@ -83,6 +88,34 @@ public final class JpaHelper {
         return em;
     }
 
+    /**
+     * Gets the {@link TransactionStrategy} to use when none has been 
configured: the single {@link TransactionStrategy}
+     * in the registry if there is one and autowiring is enabled, otherwise a 
new {@link DefaultTransactionStrategy} (a
+     * WARN is logged when the registry has more than one).
+     *
+     * @param  camelContext         the camel context, or <tt>null</tt> if not 
available (then the registry is not used)
+     * @param  entityManagerFactory the entity manager factory
+     * @param  autowiredEnabled     whether autowiring is enabled (otherwise 
the registry is not used)
+     * @return                      the transaction strategy (is never null)
+     */
+    public static TransactionStrategy resolveTransactionStrategy(
+            CamelContext camelContext, EntityManagerFactory 
entityManagerFactory, boolean autowiredEnabled) {
+        if (camelContext != null && autowiredEnabled) {
+            TransactionStrategy strategy = 
camelContext.getRegistry().findSingleByType(TransactionStrategy.class);
+            if (strategy != null) {
+                LOG.debug("Using TransactionStrategy found in registry: {}", 
strategy);
+                return strategy;
+            }
+            int count = 
camelContext.getRegistry().findByType(TransactionStrategy.class).size();
+            if (count > 1) {
+                LOG.warn("Found {} TransactionStrategy beans in the registry, 
so none of them is used."
+                         + " Configure the transactionStrategy option to 
choose one.",
+                        count);
+            }
+        }
+        return createDefaultTransactionStrategy(camelContext, 
entityManagerFactory);
+    }
+
     /**
      * Copy JpaConstants.ENTITY_MANAGER property from source to target 
exchange.
      *
@@ -98,6 +131,13 @@ public final class JpaHelper {
         }
     }
 
+    // the only place creating the Spring based DefaultTransactionStrategy, so 
runtimes without Spring can replace it:
+    // Camel Quarkus replaces this method by its name, so do not rename it or 
change its signature
+    private static TransactionStrategy createDefaultTransactionStrategy(
+            CamelContext camelContext, EntityManagerFactory 
entityManagerFactory) {
+        return new DefaultTransactionStrategy(camelContext, 
entityManagerFactory);
+    }
+
     private static EntityManager createEntityManager(Exchange exchange, 
EntityManagerFactory entityManagerFactory) {
         EntityManager em;
         em = entityManagerFactory.createEntityManager();
diff --git 
a/components/camel-jpa/src/main/java/org/apache/camel/processor/idempotent/jpa/JpaMessageIdRepository.java
 
b/components/camel-jpa/src/main/java/org/apache/camel/processor/idempotent/jpa/JpaMessageIdRepository.java
index d9eb966abb62..8905ef388137 100644
--- 
a/components/camel-jpa/src/main/java/org/apache/camel/processor/idempotent/jpa/JpaMessageIdRepository.java
+++ 
b/components/camel-jpa/src/main/java/org/apache/camel/processor/idempotent/jpa/JpaMessageIdRepository.java
@@ -30,7 +30,7 @@ import org.apache.camel.Exchange;
 import org.apache.camel.api.management.ManagedAttribute;
 import org.apache.camel.api.management.ManagedOperation;
 import org.apache.camel.api.management.ManagedResource;
-import org.apache.camel.component.jpa.DefaultTransactionStrategy;
+import org.apache.camel.component.jpa.JpaHelper;
 import org.apache.camel.component.jpa.TransactionStrategy;
 import org.apache.camel.spi.IdempotentRepository;
 import org.apache.camel.support.service.ServiceSupport;
@@ -58,7 +58,7 @@ public class JpaMessageIdRepository extends ServiceSupport 
implements Idempotent
     private boolean sharedEntityManager;
 
     public JpaMessageIdRepository(EntityManagerFactory entityManagerFactory, 
String processorName) {
-        this(entityManagerFactory, 
createDefaultTransactionStrategy(entityManagerFactory), processorName);
+        this(entityManagerFactory, JpaHelper.resolveTransactionStrategy(null, 
entityManagerFactory, false), processorName);
     }
 
     public JpaMessageIdRepository(EntityManagerFactory entityManagerFactory, 
TransactionStrategy transactionStrategy,
@@ -77,10 +77,6 @@ public class JpaMessageIdRepository extends ServiceSupport 
implements Idempotent
         return new JpaMessageIdRepository(entityManagerFactory, processorName);
     }
 
-    private static TransactionStrategy 
createDefaultTransactionStrategy(EntityManagerFactory entityManagerFactory) {
-        return new DefaultTransactionStrategy(null, entityManagerFactory);
-    }
-
     @Override
     @ManagedOperation(description = "Adds the key to the store")
     public boolean add(String messageId) {
diff --git 
a/components/camel-jpa/src/main/java/org/apache/camel/processor/keyvalue/jpa/JpaKeyValueRepository.java
 
b/components/camel-jpa/src/main/java/org/apache/camel/processor/keyvalue/jpa/JpaKeyValueRepository.java
index 104a7d96cad3..581f57101d3c 100644
--- 
a/components/camel-jpa/src/main/java/org/apache/camel/processor/keyvalue/jpa/JpaKeyValueRepository.java
+++ 
b/components/camel-jpa/src/main/java/org/apache/camel/processor/keyvalue/jpa/JpaKeyValueRepository.java
@@ -32,6 +32,7 @@ import org.apache.camel.api.management.ManagedAttribute;
 import org.apache.camel.api.management.ManagedOperation;
 import org.apache.camel.api.management.ManagedResource;
 import org.apache.camel.component.jpa.DefaultTransactionStrategy;
+import org.apache.camel.component.jpa.JpaHelper;
 import org.apache.camel.component.jpa.TransactionStrategy;
 import org.apache.camel.spi.Configurer;
 import org.apache.camel.spi.KeyValueRepository;
@@ -476,7 +477,7 @@ public class JpaKeyValueRepository extends ServiceSupport 
implements KeyValueRep
     @Override
     protected void doInit() throws Exception {
         if (transactionStrategy == null) {
-            transactionStrategy = new DefaultTransactionStrategy(null, 
entityManagerFactory);
+            transactionStrategy = JpaHelper.resolveTransactionStrategy(null, 
entityManagerFactory, false);
         }
     }
 
diff --git 
a/components/camel-jpa/src/test/java/org/apache/camel/component/jpa/JpaComponentTest.java
 
b/components/camel-jpa/src/test/java/org/apache/camel/component/jpa/JpaComponentTest.java
index c0c6ea6137be..a4f00a3b4fa1 100644
--- 
a/components/camel-jpa/src/test/java/org/apache/camel/component/jpa/JpaComponentTest.java
+++ 
b/components/camel-jpa/src/test/java/org/apache/camel/component/jpa/JpaComponentTest.java
@@ -16,19 +16,30 @@
  */
 package org.apache.camel.component.jpa;
 
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+
 import jakarta.persistence.EntityManagerFactory;
 import jakarta.persistence.LockModeType;
 import jakarta.persistence.Persistence;
 
 import org.apache.camel.examples.SendEmail;
 import org.apache.camel.test.junit6.CamelTestSupport;
+import org.apache.logging.log4j.Level;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.core.LogEvent;
+import org.apache.logging.log4j.core.Logger;
+import org.apache.logging.log4j.core.appender.AbstractAppender;
+import org.apache.logging.log4j.core.config.Property;
 import org.junit.jupiter.api.Test;
 import org.springframework.orm.jpa.JpaTransactionManager;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
 import static org.junit.jupiter.api.Assertions.assertNull;
 import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.junit.jupiter.api.Assertions.assertTrue;
 
 public class JpaComponentTest extends CamelTestSupport {
 
@@ -88,6 +99,147 @@ public class JpaComponentTest extends CamelTestSupport {
         }
     }
 
+    @Test
+    public void testJpaComponentDefaultTransactionStrategy() throws Exception {
+        EntityManagerFactory fac = 
Persistence.createEntityManagerFactory("camel");
+        try (JpaComponent comp = new JpaComponent()) {
+            comp.setCamelContext(context);
+            comp.setEntityManagerFactory(fac);
+            comp.init();
+            assertInstanceOf(DefaultTransactionStrategy.class, 
comp.getTransactionStrategy());
+
+            JpaEndpoint jpa = (JpaEndpoint) comp.createEndpoint("jpa://" + 
SendEmail.class.getName());
+            jpa.init();
+            assertSame(comp.getTransactionStrategy(), 
jpa.getTransactionStrategy());
+        } finally {
+            fac.close();
+        }
+    }
+
+    @Test
+    public void testJpaComponentTransactionStrategyFromRegistry() throws 
Exception {
+        TransactionStrategy strategy = Runnable::run;
+        context.getRegistry().bind("myTransactionStrategy", strategy);
+
+        EntityManagerFactory fac = 
Persistence.createEntityManagerFactory("camel");
+        try (JpaComponent comp = new JpaComponent()) {
+            comp.setCamelContext(context);
+            comp.setEntityManagerFactory(fac);
+            comp.init();
+            assertSame(strategy, comp.getTransactionStrategy());
+
+            JpaEndpoint jpa = (JpaEndpoint) comp.createEndpoint("jpa://" + 
SendEmail.class.getName());
+            jpa.init();
+            assertSame(strategy, jpa.getTransactionStrategy());
+        } finally {
+            fac.close();
+        }
+    }
+
+    @Test
+    public void testJpaEndpointTransactionStrategyFromRegistry() throws 
Exception {
+        TransactionStrategy strategy = Runnable::run;
+        context.getRegistry().bind("myTransactionStrategy", strategy);
+
+        EntityManagerFactory fac = 
Persistence.createEntityManagerFactory("camel");
+        try (JpaComponent comp = new JpaComponent()) {
+            comp.setCamelContext(context);
+            comp.init();
+            // no EntityManagerFactory on the component, so the endpoint 
resolves its own strategy
+            assertNull(comp.getTransactionStrategy());
+
+            JpaEndpoint jpa = (JpaEndpoint) comp.createEndpoint("jpa://" + 
SendEmail.class.getName());
+            jpa.setEntityManagerFactory(fac);
+            jpa.init();
+            assertSame(strategy, jpa.getTransactionStrategy());
+        } finally {
+            fac.close();
+        }
+    }
+
+    @Test
+    public void testJpaComponentTransactionStrategyAutowired() {
+        TransactionStrategy strategy = Runnable::run;
+        context.getRegistry().bind("myTransactionStrategy", strategy);
+
+        // created by the camel context, so the component is autowired (it has 
no EntityManagerFactory)
+        JpaComponent comp = context.getComponent("jpa", JpaComponent.class);
+        assertSame(strategy, comp.getTransactionStrategy());
+    }
+
+    @Test
+    public void 
testJpaComponentTransactionStrategyNotFromRegistryWhenAutowiringDisabled() 
throws Exception {
+        TransactionStrategy strategy = Runnable::run;
+        context.getRegistry().bind("myTransactionStrategy", strategy);
+        context.setAutowiredEnabled(false);
+
+        EntityManagerFactory fac = 
Persistence.createEntityManagerFactory("camel");
+        try (JpaComponent comp = new JpaComponent()) {
+            comp.setCamelContext(context);
+            comp.setEntityManagerFactory(fac);
+            comp.init();
+            assertInstanceOf(DefaultTransactionStrategy.class, 
comp.getTransactionStrategy());
+        } finally {
+            fac.close();
+        }
+    }
+
+    @Test
+    public void 
testJpaEndpointTransactionStrategyNotFromRegistryWhenAutowiringDisabled() 
throws Exception {
+        TransactionStrategy strategy = Runnable::run;
+        context.getRegistry().bind("myTransactionStrategy", strategy);
+
+        EntityManagerFactory fac = 
Persistence.createEntityManagerFactory("camel");
+        try (JpaComponent comp = new JpaComponent()) {
+            comp.setCamelContext(context);
+            comp.setAutowiredEnabled(false);
+            comp.init();
+
+            JpaEndpoint jpa = (JpaEndpoint) comp.createEndpoint("jpa://" + 
SendEmail.class.getName());
+            jpa.setEntityManagerFactory(fac);
+            jpa.init();
+            assertInstanceOf(DefaultTransactionStrategy.class, 
jpa.getTransactionStrategy());
+        } finally {
+            fac.close();
+        }
+    }
+
+    @Test
+    public void testJpaComponentMoreTransactionStrategiesInRegistry() throws 
Exception {
+        TransactionStrategy strategy = Runnable::run;
+        TransactionStrategy otherStrategy = Runnable::run;
+        context.getRegistry().bind("myTransactionStrategy", strategy);
+        context.getRegistry().bind("myOtherTransactionStrategy", 
otherStrategy);
+
+        List<String> warnings = new CopyOnWriteArrayList<>();
+        Logger logger = (Logger) LogManager.getLogger(JpaHelper.class);
+        AbstractAppender appender = new AbstractAppender("warnings", null, 
null, true, Property.EMPTY_ARRAY) {
+            @Override
+            public void append(LogEvent event) {
+                if (event.getLevel() == Level.WARN) {
+                    warnings.add(event.getMessage().getFormattedMessage());
+                }
+            }
+        };
+        appender.start();
+        logger.addAppender(appender);
+
+        EntityManagerFactory fac = 
Persistence.createEntityManagerFactory("camel");
+        try (JpaComponent comp = new JpaComponent()) {
+            comp.setCamelContext(context);
+            comp.setEntityManagerFactory(fac);
+            comp.init();
+            // none of them is used
+            assertInstanceOf(DefaultTransactionStrategy.class, 
comp.getTransactionStrategy());
+            assertEquals(1, warnings.size());
+            assertTrue(warnings.get(0).startsWith("Found 2 TransactionStrategy 
beans in the registry"));
+        } finally {
+            logger.removeAppender(appender);
+            appender.stop();
+            fac.close();
+        }
+    }
+
     @Test
     public void testJpaComponentWithPath() throws Exception {
         try (JpaComponent comp = new JpaComponent()) {
diff --git 
a/components/camel-jpa/src/test/java/org/apache/camel/processor/idempotent/jpa/JpaMessageIdRepositoryTest.java
 
b/components/camel-jpa/src/test/java/org/apache/camel/processor/idempotent/jpa/JpaMessageIdRepositoryTest.java
new file mode 100644
index 000000000000..0a4a66dd6f4c
--- /dev/null
+++ 
b/components/camel-jpa/src/test/java/org/apache/camel/processor/idempotent/jpa/JpaMessageIdRepositoryTest.java
@@ -0,0 +1,45 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.processor.idempotent.jpa;
+
+import java.lang.reflect.Field;
+
+import jakarta.persistence.EntityManagerFactory;
+import jakarta.persistence.Persistence;
+
+import org.apache.camel.component.jpa.DefaultTransactionStrategy;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
+
+class JpaMessageIdRepositoryTest {
+
+    @Test
+    void testDefaultTransactionStrategy() throws Exception {
+        EntityManagerFactory entityManagerFactory = 
Persistence.createEntityManagerFactory("idempotentDb");
+        try {
+            JpaMessageIdRepository repository = new 
JpaMessageIdRepository(entityManagerFactory, "myProcessor");
+
+            // the repository has no getter for its strategy
+            Field field = 
JpaMessageIdRepository.class.getDeclaredField("transactionStrategy");
+            field.setAccessible(true);
+            assertInstanceOf(DefaultTransactionStrategy.class, 
field.get(repository));
+        } finally {
+            entityManagerFactory.close();
+        }
+    }
+}
diff --git 
a/components/camel-jpa/src/test/java/org/apache/camel/processor/keyvalue/jpa/JpaKeyValueRepositoryTest.java
 
b/components/camel-jpa/src/test/java/org/apache/camel/processor/keyvalue/jpa/JpaKeyValueRepositoryTest.java
index 7398a775a53f..d3ed2eca3f8e 100644
--- 
a/components/camel-jpa/src/test/java/org/apache/camel/processor/keyvalue/jpa/JpaKeyValueRepositoryTest.java
+++ 
b/components/camel-jpa/src/test/java/org/apache/camel/processor/keyvalue/jpa/JpaKeyValueRepositoryTest.java
@@ -31,6 +31,7 @@ import org.junit.jupiter.api.Test;
 import static org.awaitility.Awaitility.await;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
 import static org.junit.jupiter.api.Assertions.assertNull;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
@@ -79,6 +80,13 @@ class JpaKeyValueRepositoryTest {
         assertNull(repository.get("nonexistent"));
     }
 
+    @Test
+    void testDefaultTransactionStrategy() throws Exception {
+        JpaKeyValueRepository repo = new 
JpaKeyValueRepository(entityManagerFactory);
+        repo.init();
+        assertInstanceOf(DefaultTransactionStrategy.class, 
repo.getTransactionStrategy());
+    }
+
     @Test
     void testPutOverwritesExistingValue() {
         repository.put("key1", "value1", null);
diff --git 
a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc 
b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
index 57e6ad3fbe21..6ad1a45e7755 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
@@ -6214,3 +6214,17 @@ A scheduled pool with virtual threads now keeps 
`poolSize` threads (before: none
 queue, the same as a scheduled pool with platform threads. After shutdown, it 
handles a rejected task with the rejected
 policy of its thread pool profile (by default it discards the task) instead of 
throwing `RejectedExecutionException`.
 
+=== camel-jpa - a TransactionStrategy in the registry is used by default
+
+The `transactionStrategy` option of the JPA component is now autowired. When 
no `TransactionStrategy` is configured,
+the JPA component and endpoint now use the single `TransactionStrategy` bean 
in the registry, if there is one, before
+creating the Spring based `DefaultTransactionStrategy`. Without such a bean, 
or with `autowiredEnabled=false`, nothing
+changes. If there is more than one, none of them is used and a WARN is logged.
+
+The bean applies to every JPA component and endpoint without a configured 
strategy, including ones using another
+`EntityManagerFactory`. If the bean is bound to one `EntityManagerFactory` 
(for example a `DefaultTransactionStrategy`
+created for it), set the `transactionStrategy` option explicitly.
+
+The return type of the protected method 
`JpaEndpoint.createTransactionStrategy()` is now `TransactionStrategy` instead
+of `DefaultTransactionStrategy`. A subclass that overrides it must be 
recompiled: compiled against the old return type,
+its method is no longer called.

Reply via email to