You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by da...@apache.org on 2023/05/15 11:22:22 UTC

[camel] branch camel-3.x updated: CAMEL-19350: outputTarget in camel-jpa (3.x) (#10079)

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

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


The following commit(s) were added to refs/heads/camel-3.x by this push:
     new 483d7b86a75 CAMEL-19350: outputTarget in camel-jpa (3.x) (#10079)
483d7b86a75 is described below

commit 483d7b86a75c2e46e43069d3dcbe05249967811c
Author: jacekszymanski <ja...@gmail.com>
AuthorDate: Mon May 15 13:22:12 2023 +0200

    CAMEL-19350: outputTarget in camel-jpa (3.x) (#10079)
    
    * JPA: add outputTarget to send result to a header/property
    
    * refactor test: pull @Query and @Find up
    
    * refactor tests
    
    * more test refactoring
    
    * test output target
    
    * rename AdditionalQueryParameters to AdditionalEndpointParameters
    
    * change proeprty prefix to property:
---
 .../camel/component/jpa/JpaEndpointConfigurer.java |  6 ++
 .../camel/component/jpa/JpaEndpointUriFactory.java |  3 +-
 .../org/apache/camel/component/jpa/jpa.json        |  1 +
 .../apache/camel/component/jpa/JpaEndpoint.java    | 14 ++++
 .../apache/camel/component/jpa/JpaProducer.java    | 46 ++++++-----
 ...ters.java => AdditionalEndpointParameters.java} |  2 +-
 .../camel/component/jpa/JpaOutputTargetTest.java   | 86 ++++++++++++++++++++
 .../camel/component/jpa/JpaOutputTypeTest.java     | 75 +++--------------
 .../apache/camel/component/jpa/JpaPagingTest.java  | 20 ++---
 .../component/jpa/JpaWithOptionsTestSupport.java   | 95 ++++++++++++++++++++--
 10 files changed, 242 insertions(+), 106 deletions(-)

diff --git a/components/camel-jpa/src/generated/java/org/apache/camel/component/jpa/JpaEndpointConfigurer.java b/components/camel-jpa/src/generated/java/org/apache/camel/component/jpa/JpaEndpointConfigurer.java
index 8155acd62a5..58906922346 100644
--- a/components/camel-jpa/src/generated/java/org/apache/camel/component/jpa/JpaEndpointConfigurer.java
+++ b/components/camel-jpa/src/generated/java/org/apache/camel/component/jpa/JpaEndpointConfigurer.java
@@ -65,6 +65,8 @@ public class JpaEndpointConfigurer extends PropertyConfigurerSupport implements
         case "namedQuery": target.setNamedQuery(property(camelContext, java.lang.String.class, value)); return true;
         case "nativequery":
         case "nativeQuery": target.setNativeQuery(property(camelContext, java.lang.String.class, value)); return true;
+        case "outputtarget":
+        case "outputTarget": target.setOutputTarget(property(camelContext, java.lang.String.class, value)); return true;
         case "parameters": target.setParameters(property(camelContext, java.util.Map.class, value)); return true;
         case "persistenceunit":
         case "persistenceUnit": target.setPersistenceUnit(property(camelContext, java.lang.String.class, value)); return true;
@@ -157,6 +159,8 @@ public class JpaEndpointConfigurer extends PropertyConfigurerSupport implements
         case "namedQuery": return java.lang.String.class;
         case "nativequery":
         case "nativeQuery": return java.lang.String.class;
+        case "outputtarget":
+        case "outputTarget": return java.lang.String.class;
         case "parameters": return java.util.Map.class;
         case "persistenceunit":
         case "persistenceUnit": return java.lang.String.class;
@@ -250,6 +254,8 @@ public class JpaEndpointConfigurer extends PropertyConfigurerSupport implements
         case "namedQuery": return target.getNamedQuery();
         case "nativequery":
         case "nativeQuery": return target.getNativeQuery();
+        case "outputtarget":
+        case "outputTarget": return target.getOutputTarget();
         case "parameters": return target.getParameters();
         case "persistenceunit":
         case "persistenceUnit": return target.getPersistenceUnit();
diff --git a/components/camel-jpa/src/generated/java/org/apache/camel/component/jpa/JpaEndpointUriFactory.java b/components/camel-jpa/src/generated/java/org/apache/camel/component/jpa/JpaEndpointUriFactory.java
index 7c748ec540a..b8bd1613dbc 100644
--- a/components/camel-jpa/src/generated/java/org/apache/camel/component/jpa/JpaEndpointUriFactory.java
+++ b/components/camel-jpa/src/generated/java/org/apache/camel/component/jpa/JpaEndpointUriFactory.java
@@ -21,7 +21,7 @@ public class JpaEndpointUriFactory extends org.apache.camel.support.component.En
     private static final Set<String> SECRET_PROPERTY_NAMES;
     private static final Set<String> MULTI_VALUE_PREFIXES;
     static {
-        Set<String> props = new HashSet<>(47);
+        Set<String> props = new HashSet<>(48);
         props.add("backoffErrorThreshold");
         props.add("backoffIdleThreshold");
         props.add("backoffMultiplier");
@@ -46,6 +46,7 @@ public class JpaEndpointUriFactory extends org.apache.camel.support.component.En
         props.add("maximumResults");
         props.add("namedQuery");
         props.add("nativeQuery");
+        props.add("outputTarget");
         props.add("parameters");
         props.add("persistenceUnit");
         props.add("pollStrategy");
diff --git a/components/camel-jpa/src/generated/resources/org/apache/camel/component/jpa/jpa.json b/components/camel-jpa/src/generated/resources/org/apache/camel/component/jpa/jpa.json
index ced7dfba7aa..0af0c02895d 100644
--- a/components/camel-jpa/src/generated/resources/org/apache/camel/component/jpa/jpa.json
+++ b/components/camel-jpa/src/generated/resources/org/apache/camel/component/jpa/jpa.json
@@ -64,6 +64,7 @@
     "findEntity": { "kind": "parameter", "displayName": "Find Entity", "group": "producer", "label": "producer", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "If enabled then the producer will find a single entity by using the message body as key and entityType as the class type. This can be used instead of a query to find a single entity." },
     "firstResult": { "kind": "parameter", "displayName": "First Result", "group": "producer", "label": "producer", "required": false, "type": "integer", "javaType": "int", "deprecated": false, "autowired": false, "secret": false, "defaultValue": -1, "description": "Set the position of the first result to retrieve." },
     "flushOnSend": { "kind": "parameter", "displayName": "Flush On Send", "group": "producer", "label": "producer", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": true, "description": "Flushes the EntityManager after the entity bean has been persisted." },
+    "outputTarget": { "kind": "parameter", "displayName": "Output Target", "group": "producer", "label": "producer", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "To put the query (or find) result in a header or property instead of the body. If the value starts with the prefix property:, put the result into the so named property, otherwise into the header." },
     "remove": { "kind": "parameter", "displayName": "Remove", "group": "producer", "label": "producer", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Indicates to use entityManager.remove(entity)." },
     "singleResult": { "kind": "parameter", "displayName": "Single Result", "group": "producer", "label": "producer", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "If enabled, a query or a find which would return no results or more than one result, will throw an exception instead." },
     "useExecuteUpdate": { "kind": "parameter", "displayName": "Use Execute Update", "group": "producer", "label": "producer", "required": false, "type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false, "autowired": false, "secret": false, "description": "To configure whether to use executeUpdate() when producer executes a query. When you use INSERT, UPDATE or DELETE statement as a named query, you need to specify this option to 'true'." },
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 d0041b49412..59a8a1bc494 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
@@ -114,6 +114,8 @@ public class JpaEndpoint extends ScheduledPollEndpoint {
     private boolean findEntity;
     @UriParam(label = "producer", defaultValue = "false")
     private boolean singleResult;
+    @UriParam(label = "producer")
+    private String outputTarget;
 
     @UriParam(label = "advanced", prefix = "emf.", multiValue = true)
     private Map<String, Object> entityManagerProperties;
@@ -571,6 +573,18 @@ public class JpaEndpoint extends ScheduledPollEndpoint {
         this.singleResult = singleResult;
     }
 
+    public String getOutputTarget() {
+        return outputTarget;
+    }
+
+    /**
+     * To put the query (or find) result in a header or property instead of the body. If the value starts with the
+     * prefix "property:", put the result into the so named property, otherwise into the header.
+     */
+    public void setOutputTarget(String outputTarget) {
+        this.outputTarget = outputTarget;
+    }
+
     // Implementation methods
     // -------------------------------------------------------------------------
 
diff --git a/components/camel-jpa/src/main/java/org/apache/camel/component/jpa/JpaProducer.java b/components/camel-jpa/src/main/java/org/apache/camel/component/jpa/JpaProducer.java
index 1bf4a6cdbc5..5847472a0f5 100644
--- a/components/camel-jpa/src/main/java/org/apache/camel/component/jpa/JpaProducer.java
+++ b/components/camel-jpa/src/main/java/org/apache/camel/component/jpa/JpaProducer.java
@@ -40,6 +40,9 @@ public class JpaProducer extends DefaultProducer {
 
     private static final Logger LOG = LoggerFactory.getLogger(JpaProducer.class);
 
+    /* prefix for marking property in outputTarget */
+    private static final String PROPERTY_PREFIX = "property:";
+
     private final EntityManagerFactory entityManagerFactory;
     private final TransactionStrategy transactionStrategy;
     private final Expression expression;
@@ -182,15 +185,6 @@ public class JpaProducer extends DefaultProducer {
                 entityManager.joinTransaction();
             }
 
-            Message target;
-            if (ExchangeHelper.isOutCapable(exchange)) {
-                target = exchange.getMessage();
-                // preserve headers
-                target.getHeaders().putAll(exchange.getIn().getHeaders());
-            } else {
-                target = exchange.getIn();
-            }
-
             final Object answer;
             if (isUseExecuteUpdate()) {
                 answer = innerQuery.executeUpdate();
@@ -200,7 +194,7 @@ public class JpaProducer extends DefaultProducer {
                 answer = innerQuery.getResultList();
             }
 
-            target.setBody(answer);
+            putAnswer(exchange, answer, getEndpoint().getOutputTarget());
 
             if (getEndpoint().isFlushOnSend()) {
                 entityManager.flush();
@@ -261,15 +255,7 @@ public class JpaProducer extends DefaultProducer {
                                     key));
                 }
 
-                Message target;
-                if (ExchangeHelper.isOutCapable(exchange)) {
-                    target = exchange.getMessage();
-                    // preserve headers
-                    target.getHeaders().putAll(exchange.getIn().getHeaders());
-                } else {
-                    target = exchange.getIn();
-                }
-                target.setBody(answer);
+                putAnswer(exchange, answer, getEndpoint().getOutputTarget());
 
                 if (getEndpoint().isFlushOnSend()) {
                     entityManager.flush();
@@ -383,4 +369,26 @@ public class JpaProducer extends DefaultProducer {
         }
     }
 
+    private static void putAnswer(final Exchange exchange, final Object answer, final String outputTarget) {
+        if (outputTarget == null || outputTarget.isBlank()) {
+            getTargetMessage(exchange).setBody(answer);
+        } else if (outputTarget.startsWith(PROPERTY_PREFIX)) {
+            exchange.setProperty(outputTarget.substring(PROPERTY_PREFIX.length()), answer);
+        } else {
+            getTargetMessage(exchange).setHeader(outputTarget, answer);
+        }
+    }
+
+    private static Message getTargetMessage(Exchange exchange) {
+        final Message target;
+        if (ExchangeHelper.isOutCapable(exchange)) {
+            target = exchange.getMessage();
+            // preserve headers
+            target.getHeaders().putAll(exchange.getIn().getHeaders());
+        } else {
+            target = exchange.getIn();
+        }
+        return target;
+    }
+
 }
diff --git a/components/camel-jpa/src/test/java/org/apache/camel/component/jpa/AdditionalQueryParameters.java b/components/camel-jpa/src/test/java/org/apache/camel/component/jpa/AdditionalEndpointParameters.java
similarity index 96%
rename from components/camel-jpa/src/test/java/org/apache/camel/component/jpa/AdditionalQueryParameters.java
rename to components/camel-jpa/src/test/java/org/apache/camel/component/jpa/AdditionalEndpointParameters.java
index d966268b4b2..7e6c45b56d1 100644
--- a/components/camel-jpa/src/test/java/org/apache/camel/component/jpa/AdditionalQueryParameters.java
+++ b/components/camel-jpa/src/test/java/org/apache/camel/component/jpa/AdditionalEndpointParameters.java
@@ -23,7 +23,7 @@ import java.lang.annotation.Target;
 
 @Retention(value = RetentionPolicy.RUNTIME)
 @Target(value = { ElementType.METHOD })
-@interface AdditionalQueryParameters {
+@interface AdditionalEndpointParameters {
 
     String value();
 
diff --git a/components/camel-jpa/src/test/java/org/apache/camel/component/jpa/JpaOutputTargetTest.java b/components/camel-jpa/src/test/java/org/apache/camel/component/jpa/JpaOutputTargetTest.java
new file mode 100644
index 00000000000..9bf419f022e
--- /dev/null
+++ b/components/camel-jpa/src/test/java/org/apache/camel/component/jpa/JpaOutputTargetTest.java
@@ -0,0 +1,86 @@
+/*
+ * 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.component.jpa;
+
+import org.apache.camel.Exchange;
+import org.apache.camel.component.jpa.JpaWithOptionsTestSupport.Query;
+import org.apache.camel.examples.Customer;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+@Query
+public class JpaOutputTargetTest extends JpaWithOptionsTestSupport {
+
+    private static final String TARGET_NAME = "__target";
+    private static final String PROP_MARK = "property:";
+
+    @Test
+    @AdditionalEndpointParameters("outputTarget=" + TARGET_NAME)
+    public void testQueryToHeader() throws Exception {
+        final Exchange result = doRunQueryTest();
+
+        Assertions.assertNotNull(result.getIn().getHeader(TARGET_NAME));
+    }
+
+    @Test
+    @AdditionalEndpointParameters("outputTarget=" + TARGET_NAME)
+    public void testBodyRemainsUnchanged() throws Exception {
+        final Object body = new Object();
+        final Exchange result = doRunQueryTest(withBody(body));
+
+        Assertions.assertEquals(body, result.getIn().getBody());
+    }
+
+    @Test
+    @AdditionalEndpointParameters("outputTarget=" + PROP_MARK + TARGET_NAME)
+    public void testQueryToProperty() throws Exception {
+        final Exchange result = doRunQueryTest();
+
+        Assertions.assertNotNull(result.getProperty(TARGET_NAME));
+    }
+
+    @Test
+    @Find
+    @AdditionalEndpointParameters("outputTarget=" + TARGET_NAME)
+    public void testFindToHeader() throws Exception {
+        final Long customerId = validCustomerId(entityManager);
+        final Exchange result = doRunQueryTest(withBody(customerId));
+
+        Assertions.assertEquals(customerId, result.getIn().getHeader(TARGET_NAME, Customer.class).getId());
+    }
+
+    @Test
+    @Find
+    @AdditionalEndpointParameters("outputTarget=" + TARGET_NAME)
+    public void testFindBodyRemainsUnchanged() throws Exception {
+        final Object body = new Object();
+        final Exchange result = doRunQueryTest(withBody(body));
+
+        Assertions.assertEquals(body, result.getIn().getBody());
+    }
+
+    @Test
+    @Find
+    @AdditionalEndpointParameters("outputTarget=" + PROP_MARK + TARGET_NAME)
+    public void testFindToProperty() throws Exception {
+        final Long customerId = validCustomerId(entityManager);
+        final Exchange result = doRunQueryTest(withBody(customerId));
+
+        Assertions.assertEquals(customerId, result.getProperty(TARGET_NAME, Customer.class).getId());
+    }
+
+}
diff --git a/components/camel-jpa/src/test/java/org/apache/camel/component/jpa/JpaOutputTypeTest.java b/components/camel-jpa/src/test/java/org/apache/camel/component/jpa/JpaOutputTypeTest.java
index 25ae0e299e8..518aeac914e 100644
--- a/components/camel-jpa/src/test/java/org/apache/camel/component/jpa/JpaOutputTypeTest.java
+++ b/components/camel-jpa/src/test/java/org/apache/camel/component/jpa/JpaOutputTypeTest.java
@@ -16,32 +16,22 @@
  */
 package org.apache.camel.component.jpa;
 
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-import java.lang.reflect.AnnotatedElement;
-
 import javax.persistence.NoResultException;
 import javax.persistence.NonUniqueResultException;
 
 import org.apache.camel.Exchange;
+import org.apache.camel.component.jpa.JpaWithOptionsTestSupport.Query;
 import org.apache.camel.examples.Customer;
 import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.Test;
-import org.junit.jupiter.api.extension.ExtensionContext;
 
 import static org.junit.jupiter.api.Assertions.assertNotNull;
 
+@Query("select c from Customer c where c.name like :seq")
 public class JpaOutputTypeTest extends JpaWithOptionsTestSupport {
 
-    private static final String ENDPOINT_URI = "jpa://" + Customer.class.getName();
-
-    private String queryOrFind;
-
     @Test
-    @Query
-    @AdditionalQueryParameters("singleResult=true&parameters.seq=% 001")
+    @AdditionalEndpointParameters("singleResult=true&parameters.seq=% 001")
     public void testSingleCustomerOKQuery() throws Exception {
         final Customer customer = runQueryTest(Customer.class);
 
@@ -50,7 +40,7 @@ public class JpaOutputTypeTest extends JpaWithOptionsTestSupport {
 
     @Test
     @Query("select c from Customer c")
-    @AdditionalQueryParameters("singleResult=true")
+    @AdditionalEndpointParameters("singleResult=true")
     public void testTooMuchResults() throws Exception {
         final Exchange result = doRunQueryTest();
 
@@ -58,8 +48,7 @@ public class JpaOutputTypeTest extends JpaWithOptionsTestSupport {
     }
 
     @Test
-    @Query
-    @AdditionalQueryParameters("singleResult=true&parameters.seq=% xxx")
+    @AdditionalEndpointParameters("singleResult=true&parameters.seq=% xxx")
     public void testNoCustomersQuery() throws Exception {
         final Exchange result = doRunQueryTest();
 
@@ -68,73 +57,29 @@ public class JpaOutputTypeTest extends JpaWithOptionsTestSupport {
 
     @Test
     @Find
-    @AdditionalQueryParameters("singleResult=true")
+    @AdditionalEndpointParameters("singleResult=true")
     public void testSingleCustomerOKFind() throws Exception {
         // ids in the db are not known, so query for a known element and use its id.
-        super.setUp(getEndpointUri());
+        Long customerId = validCustomerId(entityManager);
 
-        final Customer fromDb = (Customer) entityManager
-                .createQuery("select c from Customer c where c.name like '% 001'")
-                .getSingleResult();
-
-        final Exchange result = template.send("direct:start", withBody(fromDb.getId()));
+        final Exchange result = template.send("direct:start", withBody(customerId));
 
         assertNotNull(result.getIn().getBody(Customer.class));
     }
 
     @Test
     @Find
-    @AdditionalQueryParameters("singleResult=true")
+    @AdditionalEndpointParameters("singleResult=true")
     public void testNoCustomerFind() throws Exception {
         final Exchange result = doRunQueryTest(withBody(Long.MAX_VALUE));
 
         Assertions.assertInstanceOf(NoResultException.class, getException(result));
     }
 
-    @Override
-    protected String getEndpointUri() {
-        return String.format("%s?%s%s",
-                ENDPOINT_URI,
-                queryOrFind,
-                createAdditionalQueryParameters());
-    }
-
-    @Override
-    public void beforeEach(ExtensionContext context) throws Exception {
-        super.beforeEach(context);
-
-        // a query or a find is necessary - without the annotation test can't continue
-        final AnnotatedElement annotatedElement = context.getElement().get();
-
-        final Find find = annotatedElement.getAnnotation(Find.class);
-        final Query query = annotatedElement.getAnnotation(Query.class);
-
-        if ((find == null) == (query == null)) {
-            throw new IllegalStateException("Test must be annotated with EITHER Find OR Query");
-        }
-
-        if (find != null) {
-            queryOrFind = "findEntity=" + true;
-        } else { // query != null
-            queryOrFind = "query=" + query.value();
-        }
-
-    }
-
-    @Target(ElementType.METHOD)
-    @Retention(RetentionPolicy.RUNTIME)
-    private @interface Find {
-    }
-
-    @Target(ElementType.METHOD)
-    @Retention(RetentionPolicy.RUNTIME)
-    private @interface Query {
-        String value() default "select c from Customer c where c.name like :seq";
-    }
-
     private static Exception getException(final Exchange exchange) {
         final Exception exception = exchange.getException();
 
         return exception != null ? exception : exchange.getProperty(Exchange.EXCEPTION_CAUGHT, Exception.class);
     }
+
 }
diff --git a/components/camel-jpa/src/test/java/org/apache/camel/component/jpa/JpaPagingTest.java b/components/camel-jpa/src/test/java/org/apache/camel/component/jpa/JpaPagingTest.java
index 32aaf4d3d1a..a1bef8192d6 100644
--- a/components/camel-jpa/src/test/java/org/apache/camel/component/jpa/JpaPagingTest.java
+++ b/components/camel-jpa/src/test/java/org/apache/camel/component/jpa/JpaPagingTest.java
@@ -18,17 +18,16 @@ package org.apache.camel.component.jpa;
 
 import java.util.List;
 
+import org.apache.camel.component.jpa.JpaWithOptionsTestSupport.Query;
 import org.apache.camel.examples.Customer;
 import org.junit.jupiter.api.Test;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
+@Query("select c from Customer c order by c.name")
 public class JpaPagingTest extends JpaWithOptionsTestSupport {
 
-    private static final String ENDPOINT_URI = "jpa://" + Customer.class.getName() +
-                                               "?query=select c from Customer c order by c.name";
-
     // both should be less than JpaWithOptionsTestSupport.ENTRIES_COUNT / 2
     private static final int FIRST_RESULT = 5;
     private static final int MAXIMUM_RESULTS = 10;
@@ -41,7 +40,7 @@ public class JpaPagingTest extends JpaWithOptionsTestSupport {
     }
 
     @Test
-    @AdditionalQueryParameters("firstResult=" + FIRST_RESULT)
+    @AdditionalEndpointParameters("firstResult=" + FIRST_RESULT)
     public void testFirstResultInUri() throws Exception {
         final List<Customer> customers = runQueryTest();
 
@@ -57,7 +56,7 @@ public class JpaPagingTest extends JpaWithOptionsTestSupport {
     }
 
     @Test
-    @AdditionalQueryParameters("maximumResults=" + MAXIMUM_RESULTS)
+    @AdditionalEndpointParameters("maximumResults=" + MAXIMUM_RESULTS)
     public void testFirstInHeaderMaxInUri() throws Exception {
         final List<Customer> customers = runQueryTest(
                 withHeader(JpaConstants.JPA_FIRST_RESULT, FIRST_RESULT));
@@ -67,7 +66,7 @@ public class JpaPagingTest extends JpaWithOptionsTestSupport {
     }
 
     @Test
-    @AdditionalQueryParameters("maximumResults=" + MAXIMUM_RESULTS)
+    @AdditionalEndpointParameters("maximumResults=" + MAXIMUM_RESULTS)
     public void testMaxHeaderPrevailsOverUri() throws Exception {
         final List<Customer> customers = runQueryTest(
                 withHeader(JpaConstants.JPA_MAXIMUM_RESULTS, MAXIMUM_RESULTS * 2));
@@ -76,7 +75,7 @@ public class JpaPagingTest extends JpaWithOptionsTestSupport {
     }
 
     @Test
-    @AdditionalQueryParameters("firstResult=" + FIRST_RESULT)
+    @AdditionalEndpointParameters("firstResult=" + FIRST_RESULT)
     public void testFirstHeaderPrevailsOverUri() throws Exception {
         final List<Customer> customers = runQueryTest(
                 withHeader(JpaConstants.JPA_FIRST_RESULT, FIRST_RESULT * 2));
@@ -96,7 +95,7 @@ public class JpaPagingTest extends JpaWithOptionsTestSupport {
     }
 
     @Test
-    @AdditionalQueryParameters("firstResult=" + JpaWithOptionsTestSupport.ENTRIES_COUNT)
+    @AdditionalEndpointParameters("firstResult=" + JpaWithOptionsTestSupport.ENTRIES_COUNT)
     public void testFirstResultAfterTheEnd() throws Exception {
         final List<Customer> customers = runQueryTest();
 
@@ -108,9 +107,4 @@ public class JpaPagingTest extends JpaWithOptionsTestSupport {
                 String.format(JpaWithOptionsTestSupport.ENTRY_SEQ_FORMAT, firstResult)));
     }
 
-    protected String getEndpointUri() {
-        return ENDPOINT_URI +
-               createAdditionalQueryParameters();
-    }
-
 }
diff --git a/components/camel-jpa/src/test/java/org/apache/camel/component/jpa/JpaWithOptionsTestSupport.java b/components/camel-jpa/src/test/java/org/apache/camel/component/jpa/JpaWithOptionsTestSupport.java
index 0394799f362..43cd82d3f20 100644
--- a/components/camel-jpa/src/test/java/org/apache/camel/component/jpa/JpaWithOptionsTestSupport.java
+++ b/components/camel-jpa/src/test/java/org/apache/camel/component/jpa/JpaWithOptionsTestSupport.java
@@ -16,10 +16,22 @@
  */
 package org.apache.camel.component.jpa;
 
+import java.lang.annotation.Annotation;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
 import java.lang.reflect.AnnotatedElement;
+import java.lang.reflect.Method;
+import java.util.Arrays;
 import java.util.List;
 import java.util.Optional;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
 import java.util.stream.IntStream;
+import java.util.stream.Stream;
+
+import javax.persistence.EntityManager;
 
 import org.apache.camel.Exchange;
 import org.apache.camel.Processor;
@@ -35,8 +47,12 @@ public abstract class JpaWithOptionsTestSupport extends AbstractJpaMethodSupport
     static final int ENTRIES_COUNT = 30;
     static final String ENTRY_SEQ_FORMAT = "%03d";
 
+    private static final String ENDPOINT_URI = "jpa://" + Customer.class.getName();
+
     private String additionalQueryParameters = "";
 
+    private String queryOrFind;
+
     protected String createAdditionalQueryParameters() {
         return additionalQueryParameters.isBlank() ? "" : "&" + additionalQueryParameters;
     }
@@ -52,8 +68,6 @@ public abstract class JpaWithOptionsTestSupport extends AbstractJpaMethodSupport
     }
 
     protected Exchange doRunQueryTest(final Processor... preRun) throws Exception {
-        setUp(getEndpointUri());
-
         return template.send("direct:start", exchange -> {
             for (Processor processor : preRun) {
                 processor.process(exchange);
@@ -92,14 +106,63 @@ public abstract class JpaWithOptionsTestSupport extends AbstractJpaMethodSupport
         final Optional<AnnotatedElement> element = context.getElement();
         if (element.isPresent()) {
             final AnnotatedElement annotatedElement = element.get();
-            final AdditionalQueryParameters annotation = annotatedElement.getAnnotation(AdditionalQueryParameters.class);
-            if (annotation != null && !annotation.value().isBlank()) {
-                additionalQueryParameters = annotation.value();
-            }
+
+            setAdditionalParameters(annotatedElement);
+            setQueryOrFind(annotatedElement);
         }
+
     }
 
-    protected abstract String getEndpointUri();
+    @Override
+    public void beforeTestExecution(ExtensionContext context) throws Exception {
+        super.beforeTestExecution(context);
+        setUp(getEndpointUri());
+    }
+
+    private void setAdditionalParameters(final AnnotatedElement annotatedElement) {
+        final AdditionalEndpointParameters annotation = annotatedElement.getAnnotation(AdditionalEndpointParameters.class);
+        if (annotation != null && !annotation.value().isBlank()) {
+            additionalQueryParameters = annotation.value();
+        }
+    }
+
+    private void setQueryOrFind(final AnnotatedElement annotatedElement) throws IllegalStateException {
+        if (!(annotatedElement instanceof Method)) {
+            return;
+        }
+
+        final Method annotatedMethod = (Method) annotatedElement;
+
+        final Predicate<Annotation> isQueryOrFind
+                = ann -> Stream.of(Query.class, Find.class).anyMatch(foc -> foc.isAssignableFrom(ann.annotationType()));
+
+        final List<Annotation> onMethod = Arrays.stream(annotatedMethod.getAnnotations())
+                .filter(isQueryOrFind)
+                .collect(Collectors.toList());
+        final List<Annotation> onClass = Arrays.stream(annotatedMethod.getDeclaringClass().getAnnotations())
+                .filter(isQueryOrFind)
+                .collect(Collectors.toList());
+
+        if (onMethod.size() > 1 || onClass.size() > 1 || onMethod.size() + onClass.size() == 0) {
+            throw new IllegalStateException("Test (method or class) must be annotated with EITHER Find OR Query");
+        }
+
+        final Annotation queryOrFindAnn = Stream.concat(onMethod.stream(), onClass.stream())
+                .filter(isQueryOrFind).findFirst().get();
+
+        if (queryOrFindAnn instanceof Find) {
+            queryOrFind = "findEntity=" + true;
+        } else { // queryOrFindAnn instanceof Query
+            queryOrFind = "query=" + ((Query) queryOrFindAnn).value();
+        }
+    }
+
+    protected String getEndpointUri() {
+        return String.format("%s?%s%s",
+                ENDPOINT_URI,
+                queryOrFind,
+                createAdditionalQueryParameters());
+    }
 
     protected void createCustomers() {
         IntStream.range(0, ENTRIES_COUNT).forEach(idx -> {
@@ -109,6 +172,13 @@ public abstract class JpaWithOptionsTestSupport extends AbstractJpaMethodSupport
         });
     }
 
+    static Long validCustomerId(final EntityManager entityManager) {
+        final Customer fromDb
+                = (Customer) entityManager.createQuery("select c from Customer c where c.name like '% 001'").getSingleResult();
+        final Long customerId = fromDb.getId();
+        return customerId;
+    }
+
     @Override
     protected void setUp(String endpointUri) throws Exception {
         super.setUp(endpointUri);
@@ -116,4 +186,15 @@ public abstract class JpaWithOptionsTestSupport extends AbstractJpaMethodSupport
         assertEntitiesInDatabase(ENTRIES_COUNT, Customer.class.getName());
     }
 
+    @Target({ ElementType.METHOD, ElementType.TYPE })
+    @Retention(RetentionPolicy.RUNTIME)
+    @interface Find {
+    }
+
+    @Target({ ElementType.METHOD, ElementType.TYPE })
+    @Retention(RetentionPolicy.RUNTIME)
+    @interface Query {
+        String value() default "select c from Customer c";
+    }
+
 }