You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by ja...@apache.org on 2023/10/25 12:44:43 UTC

[camel-quarkus-examples] 03/14: Add an example for JPA based idempotent repository

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

jamesnetherton pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel-quarkus-examples.git

commit be38e501a344bb2e158c1d10f78ea53331928ea7
Author: aldettinger <al...@gmail.com>
AuthorDate: Thu Sep 7 20:35:36 2023 +0200

    Add an example for JPA based idempotent repository
---
 docs/modules/ROOT/attachments/examples.json        |   5 +
 jpa-idempotent-repository/README.adoc              | 230 ++++++++++++
 .../eclipse-formatter-config.xml                   | 276 ++++++++++++++
 jpa-idempotent-repository/pom.xml                  | 399 +++++++++++++++++++++
 jpa-idempotent-repository/schema.png               | Bin 0 -> 97169 bytes
 jpa-idempotent-repository/schemas-source.odp       | Bin 0 -> 19800 bytes
 .../idempotent/repository/CostlyApiService.java    |  53 +++
 .../repository/ExampleHarnessRoutes.java           |  68 ++++
 .../idempotent/repository/JpaIdempotentRoute.java  |  75 ++++
 .../src/main/kubernetes/kubernetes.yml             |  94 +++++
 .../src/main/kubernetes/openshift.yml              |  94 +++++
 .../src/main/resources/application.properties      |  67 ++++
 .../idempotent/repository/DerbyTestResource.java   |  79 ++++
 .../repository/JpaIdempotentRepositoryIT.java      |  24 ++
 .../repository/JpaIdempotentRepositoryTest.java    |  45 +++
 .../src/test/resources/init.sql                    |  20 ++
 16 files changed, 1529 insertions(+)

diff --git a/docs/modules/ROOT/attachments/examples.json b/docs/modules/ROOT/attachments/examples.json
index 0bda1c5..7e39740 100644
--- a/docs/modules/ROOT/attachments/examples.json
+++ b/docs/modules/ROOT/attachments/examples.json
@@ -39,6 +39,11 @@
     "description": "Shows how to run a Camel Quarkus application that supports JTA transactions on three external transactional resources: a database (MySQL), a messaging broker (Artemis) and a simulated XAResource which can demonstrate the commit, rollback and crash recovery.",
     "link": "https://github.com/apache/camel-quarkus-examples/tree/main/jms-jpa"
   },
+  {
+    "title": "JPA idempotent repository",
+    "description": "Shows how to consume a message only once, even when the message is delivered multiple times",
+    "link": "https://github.com/apache/camel-quarkus-examples/tree/main/jpa-idempotent-repository"
+  },
   {
     "title": "JTA and JPA",
     "description": "Shows how to run a Camel Quarkus application that supports JTA transactions on two external transactional resources: a database (MySQL) and a simulate XAResource which can demonstrate the commit, rollback and crash recovery.",
diff --git a/jpa-idempotent-repository/README.adoc b/jpa-idempotent-repository/README.adoc
new file mode 100644
index 0000000..0034b80
--- /dev/null
+++ b/jpa-idempotent-repository/README.adoc
@@ -0,0 +1,230 @@
+= JPA idempotent repository: A Camel Quarkus example
+:cq-example-description: An example that shows how to consume a message only once, even when the message is delivered multiple times
+
+{cq-description}
+
+TIP: Check the https://camel.apache.org/camel-quarkus/latest/first-steps.html[Camel Quarkus User guide] for prerequisites
+and other general information.
+
+Suppose an application needs to invoke a costly API. Each time a duplicate message would be processed, then a bunch of money would be lost. In such situations, it could make sense to prevent some duplicate calls by using the https://camel.apache.org/components/latest/eips/idempotentConsumer-eip.html[idempotent consumer] EIP.
+Let's see an example with the schema below:
+
+image::schema.png[]
+
+As one could see in the schema, using the idempotent consumer pattern in Camel is as simple as creating a route using the `idempotentConsumer` and `idempotentRepository` keywords. In this example, the idempotent repository is a database that is edited and read through JPA. Under the hood, this database will keep track of messages that have already been processed.
+
+Note that JPA is not the only option when it comes to selecting an `idempotentRepository` implementation.
+Other choices are available as listed in the https://camel.apache.org/components/latest/eips/idempotentConsumer-eip.html#_idempotent_consumer_implementations[documentation].
+
+In this example, let's focus on JPA and see more details about how to execute such a route in the next sections below.
+
+== Start in Development mode
+
+Let's start by executing the command below:
+
+[source,shell]
+----
+mvn clean compile quarkus:dev
+----
+
+The above command compiles the project, starts the application and lets the Quarkus tooling watch for changes in your workspace.
+Any modifications in your project will automatically take effect in the running application.
+
+TIP: Please refer to the Development mode section of
+https://camel.apache.org/camel-quarkus/latest/first-steps.html#_development_mode[Camel Quarkus User guide] for more details.
+
+It should be possible now to see some log messages appearing on the console.
+Note how some files with different content are generated `3,5,7...`
+Each time such a file is consumed by the route, a costly API is called.
+However, a file with content `1` is regularly generated.
+This duplicate file is problematic as it will generate undue calls to the costly API too frequently.
+This is where the https://camel.apache.org/components/latest/eips/idempotentConsumer-eip.html[idempotent consumer] enter the game.
+The source code could be found in the source file named `src/main/java/org/acme/jpa/idempotent/repository/JpaIdempotentRoute.java`.
+The camel application should produce logs as below:
+
+[source,shell]
+----
+2023-09-15 15:47:49,477 INFO  [route2] (Camel (camel-1) thread #2 - timer://createExampleInputFiles) -----------------------------------------------------------------
+2023-09-15 15:47:49,478 INFO  [route2] (Camel (camel-1) thread #2 - timer://createExampleInputFiles) Creating an example input file with content 1
+2023-09-15 15:47:50,974 INFO  [route1] (Camel (camel-1) thread #1 - file://target/input-files) Received an example input file having the content 1
+2023-09-15 15:47:51,167 INFO  [route1] (Camel (camel-1) thread #1 - file://target/input-files) The file was not a duplicate, invoke the costly API
+2023-09-15 15:47:51,230 INFO  [org.acm.jpa.ide.rep.CostlyApiService] (vert.x-worker-thread-1) Costly API has been called with new content => GOOD
+2023-09-15 15:47:59,475 INFO  [route2] (Camel (camel-1) thread #2 - timer://createExampleInputFiles) -----------------------------------------------------------------
+2023-09-15 15:47:59,477 INFO  [route2] (Camel (camel-1) thread #2 - timer://createExampleInputFiles) Creating an example input file with content 3
+2023-09-15 15:48:00,758 INFO  [route1] (Camel (camel-1) thread #1 - file://target/input-files) Received an example input file having the content 3
+2023-09-15 15:48:00,761 INFO  [route1] (Camel (camel-1) thread #1 - file://target/input-files) The file was not a duplicate, invoke the costly API
+2023-09-15 15:48:00,765 INFO  [org.acm.jpa.ide.rep.CostlyApiService] (vert.x-worker-thread-1) Costly API has been called with new content => GOOD
+2023-09-15 15:48:09,475 INFO  [route2] (Camel (camel-1) thread #2 - timer://createExampleInputFiles) -----------------------------------------------------------------
+2023-09-15 15:48:09,477 INFO  [route2] (Camel (camel-1) thread #2 - timer://createExampleInputFiles) Creating an example input file with content 1
+2023-09-15 15:48:10,777 INFO  [route1] (Camel (camel-1) thread #1 - file://target/input-files) Received an example input file having the content 1
+2023-09-15 15:48:19,475 INFO  [route2] (Camel (camel-1) thread #2 - timer://createExampleInputFiles) -----------------------------------------------------------------
+2023-09-15 15:48:19,477 INFO  [route2] (Camel (camel-1) thread #2 - timer://createExampleInputFiles) Creating an example input file with content 5
+2023-09-15 15:48:20,796 INFO  [route1] (Camel (camel-1) thread #1 - file://target/input-files) Received an example input file having the content 5
+2023-09-15 15:48:20,801 INFO  [route1] (Camel (camel-1) thread #1 - file://target/input-files) The file was not a duplicate, invoke the costly API
+2023-09-15 15:48:20,804 INFO  [org.acm.jpa.ide.rep.CostlyApiService] (vert.x-worker-thread-1) Costly API has been called with new content => GOOD
+----
+
+When running in dev mode, the idempotent consumer is storing the list of already processed messages in-memory, into a h2 database.
+Later on, another database will be used when we'll package and run the application.
+Indeed, the duplicate messages will then be stored in files, into a derby database.
+
+== Starting and initializing the derby database in a container
+
+Before packaging and running the application in JVM mode, we need to start and initialize a derby database in a container.
+So, in a first shell, please launch a derby database container:
+
+[source,shell]
+----
+docker run -p 1527:1527 az82/docker-derby:10.16
+----
+
+And from a second shell, please run the commands below in order to initialize the derby database:
+
+[source,shell]
+----
+DERBY_DOCKER_ID=$(docker ps -q  --filter ancestor=az82/docker-derby)
+docker cp src/test/resources/init.sql ${DERBY_DOCKER_ID}:/init.sql
+docker exec -it ${DERBY_DOCKER_ID} java -Djdbc.drivers=org.apache.derbbc.EmbeddedDriver org.apache.derby.tools.ij /init.sql
+----
+
+It should output some logs like below:
+
+[source,shell]
+----
+$ DERBY_DOCKER_ID=$(docker ps -q --filter ancestor=az82/docker-derby)
+
+$ docker cp src/test/resources/init.sql ${DERBY_DOCKER_ID}:/init.sql
+Successfully copied 2.05kB to c88edda502f7:/init.sql
+
+$ docker exec -it ${DERBY_DOCKER_ID} java -Djdbc.drivers=org.apache.derbbc.EmbeddedDriver org.apache.derby.tools.ij /init.sql
+ij version 10.16
+ij> CONNECT 'jdbc:derby:my-db;create=true';
+ij> CREATE TABLE CAMEL_MESSAGEPROCESSED ( processorName VARCHAR(255), messageId VARCHAR(100), createdAt TIMESTAMP, PRIMARY KEY (processorName, messageId) );
+0 rows inserted/updated/deleted
+ij> CREATE SEQUENCE CAMEL_MESSAGEPROCESSED_SEQ AS INT MAXVALUE 999999 CYCLE;
+0 rows inserted/updated/deleted
+----
+
+=== Package and run the application
+
+Once you are done with developing you may want to package and run the application.
+
+TIP: Find more details about the JVM mode and Native mode in the Package and run section of
+https://camel.apache.org/camel-quarkus/latest/first-steps.html#_package_and_run_the_application[Camel Quarkus User guide]
+
+==== JVM mode
+
+[source,shell]
+----
+mvn clean package -DskipTests
+java -jar target/quarkus-app/quarkus-run.jar
+----
+
+Please, note that the shell running the derby database should react by printing some logs as below:
+
+[source,shell]
+----
+Booting Derby version The Apache Software Foundation - Apache Derby - 10.16.1.1 - (1901046): instance a816c00e-018a-996e-54bf-00003e718008 
+on database directory /dbs/my-db with class loader jdk.internal.loader.ClassLoaders$AppClassLoader@5c626da3 
+Loaded from file:/derby/lib/derby.jar
+java.vendor=Eclipse Adoptium
+java.runtime.version=17.0.4.1+1
+user.dir=/dbs
+os.name=Linux
+os.arch=amd64
+os.version=4.18.0-477.21.1.el8_8.x86_64
+derby.system.home=null
+derby.stream.error.field=java.lang.System.out
+Database Class Loader started - derby.database.classpath=''
+----
+
+Beyond that, notice how the application behaves the same way.
+The only variation compared to the dev mode is actually that the idempotent repository is now a derby database running in a container.
+
+==== Native mode
+
+IMPORTANT: Native mode requires having GraalVM and other tools installed. Please check the Prerequisites section
+of https://camel.apache.org/camel-quarkus/latest/first-steps.html#_prerequisites[Camel Quarkus User guide].
+
+To prepare a native executable using GraalVM, run the following commands:
+
+[source,shell]
+----
+mvn clean package -DskipTests -Pnative
+./target/*-runner
+----
+
+The compilation is a bit slower. Beyond that, notice how the application behaves the same way.
+The only variation compared to the JVM mode is actually that the application was packaged as a native executable.
+
+==== Deploying to Kubernetes
+
+You can build a container image for the application like this. Refer to the https://quarkus.io/guides/deploying-to-kubernetes[Quarkus Kubernetes guide] for options around customizing image names, registries etc.
+
+[source,shell]
+----
+mvn clean package -DskipTests -Dquarkus.container-image.build=true
+----
+
+If you are using a local development cluster like Kind or k3s, you can use host the container image on your local host. Or, with minikube, use the Docker daemon from the cluster virtual machine `eval $(minikube docker-env)`. Otherwise, you'll need to push the image to a registry of your choosing.
+
+Next apply the necessary resources to the cluster if needed:
+
+[source,shell]
+----
+kubectl apply -f target/kubernetes/kubernetes.yml
+----
+
+TIP: You can build & deploy in one single step by doing `mvn clean package -DskipTests -Dquarkus.kubernetes.deploy=true`
+
+Check pods are running by executing:
+
+[source,shell]
+----
+kubectl get pods
+----
+
+We expect a list of two pods similar to below:
+
+[source,shell]
+----
+NAME                                                              READY   STATUS    RESTARTS      AGE
+camel-quarkus-examples-derby-database-deployment-76f6dc9bdnwwxn   1/1     Running   0             23s
+camel-quarkus-examples-jpa-idempotent-repository-7c74b9cf5ph68r   1/1     Running   1 (18s ago)   23s
+----
+
+Now, let's tail the application logs:
+
+[source,shell]
+----
+kubectl logs -f camel-quarkus-examples-jpa-idempotent-repository-56999fcffb6qv2
+----
+
+To clean up do:
+
+[source,shell]
+----
+kubectl delete all -l app.kubernetes.io/name=camel-quarkus-examples-jpa-idempotent-repository
+kubectl delete all -l app.kubernetes.io/name=camel-quarkus-examples-derby-database
+kubectl delete configmap -l app.kubernetes.io/name=camel-quarkus-examples-derby-database
+----
+
+[NOTE]
+====
+If you need to configure container resource limits & requests, or enable the Quarkus Kubernetes client to trust self signed certificates, you can find these configuration options in `src/main/resources/application.properties`. Simply uncomment them and set your desired values.
+====
+
+==== Deploying to OpenShift
+
+In order to start a Source To Image (S2I) build and deploy the application, let's execute the command below:
+
+[source,shell]
+----
+mvn clean package -DskipTests -Dquarkus.kubernetes.deploy=true -Dopenshift
+----
+
+You can check the pod status and tail logs using the commands mentioned above in the Kubernetes section. Use the `oc` binary instead of `kubectl` if preferred.
+
+== Feedback
+
+Please report bugs and propose improvements via https://github.com/apache/camel-quarkus/issues[GitHub issues of Camel Quarkus] project.
diff --git a/jpa-idempotent-repository/eclipse-formatter-config.xml b/jpa-idempotent-repository/eclipse-formatter-config.xml
new file mode 100644
index 0000000..2248b2b
--- /dev/null
+++ b/jpa-idempotent-repository/eclipse-formatter-config.xml
@@ -0,0 +1,276 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+    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.
+
+-->
+<profiles version="8">
+    <profile name="Camel Java Conventions" version="8" kind="CodeFormatterProfile">
+        <setting id="org.eclipse.jdt.core.formatter.align_type_members_on_columns" value="false"/>
+        <setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression" value="16"/>
+        <setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant" value="16"/>
+        <setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_explicit_constructor_call" value="16"/>
+        <setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_method_invocation" value="16"/>
+        <setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_qualified_allocation_expression" value="16"/>
+        <setting id="org.eclipse.jdt.core.formatter.alignment_for_binary_expression" value="16"/>
+        <setting id="org.eclipse.jdt.core.formatter.alignment_for_compact_if" value="16"/>
+        <setting id="org.eclipse.jdt.core.formatter.alignment_for_conditional_expression" value="16"/>
+        <setting id="org.eclipse.jdt.core.formatter.alignment_for_enum_constants" value="16"/>
+        <setting id="org.eclipse.jdt.core.formatter.alignment_for_expressions_in_array_initializer" value="16"/>
+        <setting id="org.eclipse.jdt.core.formatter.alignment_for_multiple_fields" value="16"/>
+        <setting id="org.eclipse.jdt.core.formatter.alignment_for_parameters_in_constructor_declaration" value="16"/>
+        <setting id="org.eclipse.jdt.core.formatter.alignment_for_parameters_in_method_declaration" value="16"/>
+        <setting id="org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation" value="16"/>
+        <setting id="org.eclipse.jdt.core.formatter.alignment_for_superclass_in_type_declaration" value="16"/>
+        <setting id="org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_enum_declaration" value="16"/>
+        <setting id="org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_type_declaration" value="16"/>
+        <setting id="org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_constructor_declaration" value="16"/>
+        <setting id="org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_method_declaration" value="16"/>
+        <setting id="org.eclipse.jdt.core.formatter.blank_lines_after_imports" value="1"/>
+        <setting id="org.eclipse.jdt.core.formatter.blank_lines_after_package" value="1"/>
+        <setting id="org.eclipse.jdt.core.formatter.blank_lines_before_field" value="0"/>
+        <setting id="org.eclipse.jdt.core.formatter.blank_lines_before_first_class_body_declaration" value="0"/>
+        <setting id="org.eclipse.jdt.core.formatter.blank_lines_before_imports" value="1"/>
+        <setting id="org.eclipse.jdt.core.formatter.blank_lines_before_member_type" value="1"/>
+        <setting id="org.eclipse.jdt.core.formatter.blank_lines_before_method" value="1"/>
+        <setting id="org.eclipse.jdt.core.formatter.blank_lines_before_new_chunk" value="1"/>
+        <setting id="org.eclipse.jdt.core.formatter.blank_lines_before_package" value="0"/>
+        <setting id="org.eclipse.jdt.core.formatter.blank_lines_between_type_declarations" value="1"/>
+        <setting id="org.eclipse.jdt.core.formatter.brace_position_for_annotation_type_declaration" value="end_of_line"/>
+        <setting id="org.eclipse.jdt.core.formatter.brace_position_for_anonymous_type_declaration" value="end_of_line"/>
+        <setting id="org.eclipse.jdt.core.formatter.brace_position_for_array_initializer" value="end_of_line"/>
+        <setting id="org.eclipse.jdt.core.formatter.brace_position_for_block" value="end_of_line"/>
+        <setting id="org.eclipse.jdt.core.formatter.brace_position_for_block_in_case" value="end_of_line"/>
+        <setting id="org.eclipse.jdt.core.formatter.brace_position_for_constructor_declaration" value="end_of_line"/>
+        <setting id="org.eclipse.jdt.core.formatter.brace_position_for_enum_constant" value="end_of_line"/>
+        <setting id="org.eclipse.jdt.core.formatter.brace_position_for_enum_declaration" value="end_of_line"/>
+        <setting id="org.eclipse.jdt.core.formatter.brace_position_for_method_declaration" value="end_of_line"/>
+        <setting id="org.eclipse.jdt.core.formatter.brace_position_for_switch" value="end_of_line"/>
+        <setting id="org.eclipse.jdt.core.formatter.brace_position_for_type_declaration" value="end_of_line"/>
+        <setting id="org.eclipse.jdt.core.formatter.comment.align_tags_names_descriptions" value="true"/>
+        <setting id="org.eclipse.jdt.core.formatter.comment.align_tags_descriptions_grouped" value="false"/>
+        <setting id="org.eclipse.jdt.core.formatter.comment.clear_blank_lines" value="false"/>
+        <setting id="org.eclipse.jdt.core.formatter.comment.format_block_comments" value="false"/>
+        <setting id="org.eclipse.jdt.core.formatter.comment.format_comments" value="true"/>
+        <setting id="org.eclipse.jdt.core.formatter.comment.format_header" value="false"/>
+        <setting id="org.eclipse.jdt.core.formatter.comment.format_html" value="true"/>
+        <setting id="org.eclipse.jdt.core.formatter.comment.format_javadoc_comments" value="true"/>
+        <setting id="org.eclipse.jdt.core.formatter.comment.format_line_comments" value="false"/>
+        <setting id="org.eclipse.jdt.core.formatter.comment.format_source_code" value="true"/>
+        <setting id="org.eclipse.jdt.core.formatter.comment.indent_parameter_description" value="false"/>
+        <setting id="org.eclipse.jdt.core.formatter.comment.indent_return_description" value="false"/>
+        <setting id="org.eclipse.jdt.core.formatter.comment.indent_root_tags" value="false"/>
+        <setting id="org.eclipse.jdt.core.formatter.comment.insert_new_line_before_root_tags" value="insert"/>
+        <setting id="org.eclipse.jdt.core.formatter.comment.insert_new_line_for_parameter" value="do not insert"/>
+        <setting id="org.eclipse.jdt.core.formatter.comment.line_length" value="120"/>
+        <setting id="org.eclipse.jdt.core.formatter.compact_else_if" value="true"/>
+        <setting id="org.eclipse.jdt.core.formatter.continuation_indentation" value="2"/>
+        <setting id="org.eclipse.jdt.core.formatter.continuation_indentation_for_array_initializer" value="2"/>
+        <setting id="org.eclipse.jdt.core.formatter.format_guardian_clause_on_one_line" value="false"/>
+        <setting id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_constant_header" value="true"/>
+        <setting id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_declaration_header" value="true"/>
+        <setting id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_type_header" value="true"/>
+        <setting id="org.eclipse.jdt.core.formatter.indent_breaks_compare_to_cases" value="true"/>
+        <setting id="org.eclipse.jdt.core.formatter.indent_statements_compare_to_block" value="true"/>
+        <setting id="org.eclipse.jdt.core.formatter.indent_statements_compare_to_body" value="true"/>
+        <setting id="org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_cases" value="true"/>
+        <setting id="org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_switch" value="false"/>
+        <setting id="org.eclipse.jdt.core.formatter.indentation.size" value="8"/>
+        <setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation" value="insert"/>
+        <setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_parameter" value="do not insert"/>
+        <setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer" value="do not insert"/>
+        <setting id="org.eclipse.jdt.core.formatter.insert_new_line_at_end_of_file_if_missing" value="insert"/>
+        <setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_catch_in_try_statement" value="do not insert"/>
+        <setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer" value="do not insert"/>
+        <setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_else_in_if_statement" value="do not insert"/>
+        <setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_finally_in_try_statement" value="do not insert"/>
+        <setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_while_in_do_statement" value="do not insert"/>
+        <setting id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_anonymous_type_declaration" value="insert"/>
+        <setting id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_block" value="insert"/>
+        <setting id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_constant" value="insert"/>
+        <setting id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_declaration" value="insert"/>
+        <setting id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_method_body" value="insert"/>
+        <setting id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_type_declaration" value="insert"/>
+        <setting id="org.eclipse.jdt.core.formatter.insert_space_after_and_in_type_parameter" value="insert"/>
+        <setting id="org.eclipse.jdt.core.formatter.insert_space_after_assignment_operator" value="insert"/>
+        <setting id="org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation" value="do not insert"/>
+        <setting id="org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation_type_declaration" value="do not insert"/>
+        <setting id="org.eclipse.jdt.core.formatter.insert_space_after_binary_operator" value="insert"/>
+        <setting id="org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_arguments" value="insert"/>
+        <setting id="org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_parameters" value="insert"/>
+        <setting id="org.eclipse.jdt.core.formatter.insert_space_after_closing_brace_in_block" value="insert"/>
+        <setting id="org.eclipse.jdt.core.formatter.insert_space_after_closing_paren_in_cast" value="insert"/>
+        <setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_assert" value="insert"/>
+        <setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_case" value="insert"/>
+        <setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_conditional" value="insert"/>
+        <setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_for" value="insert"/>
+        <setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_labeled_statement" value="insert"/>
+        <setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_allocation_expression" value="insert"/>
+        <setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_annotation" value="insert"/>
+        <setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_array_initializer" value="insert"/>
+        <setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_parameters" value="insert"/>
+        <setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_throws" value="insert"/>
+        <setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_constant_arguments" value="insert"/>
+        <setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_declarations" value="insert"/>
+        <setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_explicitconstructorcall_arguments" value="insert"/>
+        <setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_increments" value="insert"/>
+        <setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_inits" value="insert"/>
+        <setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_parameters" value="insert"/>
+        <setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_throws" value="insert"/>
+        <setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_invocation_arguments" value="insert"/>
+        <setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_field_declarations" value="insert"/>
+        <setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_local_declarations" value="insert"/>
+        <setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_parameterized_type_reference" value="insert"/>
+        <setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_superinterfaces" value="insert"/>
+        <setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_arguments" value="insert"/>
+        <setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_parameters" value="insert"/>
+        <setting id="org.eclipse.jdt.core.formatter.insert_space_after_ellipsis" value="insert"/>
+        <setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_parameterized_type_reference" value="do not insert"/>
+        <setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_arguments" value="do not insert"/>
+        <setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_parameters" value="do not insert"/>
+        <setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_brace_in_array_initializer" value="insert"/>
+        <setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_allocation_expression" value="do not insert"/>
+        <setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_reference" value="do not insert"/>
+        <setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_annotation" value="do not insert"/>
+        <setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_cast" value="do not insert"/>
+        <setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_catch" value="do not insert"/>
+        <setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_constructor_declaration" value="do not insert"/>
+        <setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_enum_constant" value="do not insert"/>
+        <setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_for" value="do not insert"/>
+        <setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_if" value="do not insert"/>
+        <setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_declaration" value="do not insert"/>
+        <setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_invocation" value="do not insert"/>
+        <setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_parenthesized_expression" value="do not insert"/>
+        <setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_switch" value="do not insert"/>
+        <setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_synchronized" value="do not insert"/>
+        <setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_while" value="do not insert"/>
+        <setting id="org.eclipse.jdt.core.formatter.insert_space_after_postfix_operator" value="do not insert"/>
+        <setting id="org.eclipse.jdt.core.formatter.insert_space_after_prefix_operator" value="do not insert"/>
+        <setting id="org.eclipse.jdt.core.formatter.insert_space_after_question_in_conditional" value="insert"/>
+        <setting id="org.eclipse.jdt.core.formatter.insert_space_after_question_in_wildcard" value="do not insert"/>
+        <setting id="org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_for" value="insert"/>
+        <setting id="org.eclipse.jdt.core.formatter.insert_space_after_unary_operator" value="do not insert"/>
+        <setting id="org.eclipse.jdt.core.formatter.insert_space_before_and_in_type_parameter" value="insert"/>
+        <setting id="org.eclipse.jdt.core.formatter.insert_space_before_assignment_operator" value="insert"/>
+        <setting id="org.eclipse.jdt.core.formatter.insert_space_before_at_in_annotation_type_declaration" value="insert"/>
+        <setting id="org.eclipse.jdt.core.formatter.insert_space_before_binary_operator" value="insert"/>
+        <setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_parameterized_type_reference" value="do not insert"/>
+        <setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_arguments" value="do not insert"/>
+        <setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_parameters" value="do not insert"/>
+        <setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_brace_in_array_initializer" value="insert"/>
+        <setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_allocation_expression" value="do not insert"/>
+        <setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_reference" value="do not insert"/>
+        <setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_annotation" value="do not insert"/>
+        <setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_cast" value="do not insert"/>
+        <setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_catch" value="do not insert"/>
+        <setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_constructor_declaration" value="do not insert"/>
+        <setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_enum_constant" value="do not insert"/>
+        <setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_for" value="do not insert"/>
+        <setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_if" value="do not insert"/>
+        <setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_declaration" value="do not insert"/>
+        <setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_invocation" value="do not insert"/>
+        <setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_parenthesized_expression" value="do not insert"/>
+        <setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_switch" value="do not insert"/>
+        <setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_synchronized" value="do not insert"/>
+        <setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_while" value="do not insert"/>
+        <setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_assert" value="insert"/>
+        <setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_case" value="do not insert"/>
+        <setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_conditional" value="insert"/>
+        <setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_default" value="do not insert"/>
+        <setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_for" value="insert"/>
+        <setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_labeled_statement" value="do not insert"/>
+        <setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_allocation_expression" value="do not insert"/>
+        <setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_annotation" value="do not insert"/>
+        <setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_array_initializer" value="do not insert"/>
+        <setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_parameters" value="do not insert"/>
+        <setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_throws" value="do not insert"/>
+        <setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_constant_arguments" value="do not insert"/>
+        <setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_declarations" value="do not insert"/>
+        <setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_explicitconstructorcall_arguments" value="do not insert"/>
+        <setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_increments" value="do not insert"/>
+        <setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_inits" value="do not insert"/>
+        <setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_parameters" value="do not insert"/>
+        <setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_throws" value="do not insert"/>
+        <setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_invocation_arguments" value="do not insert"/>
+        <setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_field_declarations" value="do not insert"/>
+        <setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_local_declarations" value="do not insert"/>
+        <setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_parameterized_type_reference" value="do not insert"/>
+        <setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_superinterfaces" value="do not insert"/>
+        <setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_arguments" value="do not insert"/>
+        <setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_parameters" value="do not insert"/>
+        <setting id="org.eclipse.jdt.core.formatter.insert_space_before_ellipsis" value="do not insert"/>
+        <setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_parameterized_type_reference" value="do not insert"/>
+        <setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_arguments" value="do not insert"/>
+        <setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_parameters" value="do not insert"/>
+        <setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_annotation_type_declaration" value="insert"/>
+        <setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_anonymous_type_declaration" value="insert"/>
+        <setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_array_initializer" value="insert"/>
+        <setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_block" value="insert"/>
+        <setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_constructor_declaration" value="insert"/>
+        <setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_constant" value="insert"/>
+        <setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_declaration" value="insert"/>
+        <setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_method_declaration" value="insert"/>
+        <setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_switch" value="insert"/>
+        <setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_type_declaration" value="insert"/>
+        <setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_allocation_expression" value="do not insert"/>
+        <setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_reference" value="do not insert"/>
+        <setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_type_reference" value="do not insert"/>
+        <setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation" value="do not insert"/>
+        <setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation_type_member_declaration" value="do not insert"/>
+        <setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_catch" value="insert"/>
+        <setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_constructor_declaration" value="do not insert"/>
+        <setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_enum_constant" value="do not insert"/>
+        <setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_for" value="insert"/>
+        <setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_if" value="insert"/>
+        <setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_declaration" value="do not insert"/>
+        <setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_invocation" value="do not insert"/>
+        <setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_parenthesized_expression" value="do not insert"/>
+        <setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_switch" value="insert"/>
+        <setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_synchronized" value="insert"/>
+        <setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_while" value="insert"/>
+        <setting id="org.eclipse.jdt.core.formatter.insert_space_before_postfix_operator" value="do not insert"/>
+        <setting id="org.eclipse.jdt.core.formatter.insert_space_before_prefix_operator" value="do not insert"/>
+        <setting id="org.eclipse.jdt.core.formatter.insert_space_before_question_in_conditional" value="insert"/>
+        <setting id="org.eclipse.jdt.core.formatter.insert_space_before_question_in_wildcard" value="do not insert"/>
+        <setting id="org.eclipse.jdt.core.formatter.insert_space_before_semicolon" value="do not insert"/>
+        <setting id="org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_for" value="do not insert"/>
+        <setting id="org.eclipse.jdt.core.formatter.insert_space_before_unary_operator" value="do not insert"/>
+        <setting id="org.eclipse.jdt.core.formatter.insert_space_between_brackets_in_array_type_reference" value="do not insert"/>
+        <setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_braces_in_array_initializer" value="do not insert"/>
+        <setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_brackets_in_array_allocation_expression" value="do not insert"/>
+        <setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_annotation_type_member_declaration" value="do not insert"/>
+        <setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_constructor_declaration" value="do not insert"/>
+        <setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_enum_constant" value="do not insert"/>
+        <setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_declaration" value="do not insert"/>
+        <setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_invocation" value="do not insert"/>
+        <setting id="org.eclipse.jdt.core.formatter.join_lines_in_comments" value="false"/>
+        <setting id="org.eclipse.jdt.core.formatter.join_wrapped_lines" value="false"/>
+        <setting id="org.eclipse.jdt.core.formatter.keep_else_statement_on_same_line" value="false"/>
+        <setting id="org.eclipse.jdt.core.formatter.keep_empty_array_initializer_on_one_line" value="false"/>
+        <setting id="org.eclipse.jdt.core.formatter.keep_imple_if_on_one_line" value="false"/>
+        <setting id="org.eclipse.jdt.core.formatter.keep_then_statement_on_same_line" value="false"/>
+        <setting id="org.eclipse.jdt.core.formatter.lineSplit" value="128"/>
+        <setting id="org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body" value="0"/>
+        <setting id="org.eclipse.jdt.core.formatter.number_of_empty_lines_to_preserve" value="1"/>
+        <setting id="org.eclipse.jdt.core.formatter.put_empty_statement_on_new_line" value="true"/>
+        <setting id="org.eclipse.jdt.core.formatter.tabulation.char" value="space"/>
+        <setting id="org.eclipse.jdt.core.formatter.tabulation.size" value="4"/>
+        <setting id="org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations" value="false"/>
+        <setting id="org.eclipse.jdt.core.formatter.use_on_off_tags" value="true"/>
+        <setting id="org.eclipse.jdt.core.formatter.disabling_tag" value="CHECKSTYLE:OFF"/>
+        <setting id="org.eclipse.jdt.core.formatter.enabling_tag" value="CHECKSTYLE:ON"/>
+    </profile>
+</profiles>
diff --git a/jpa-idempotent-repository/pom.xml b/jpa-idempotent-repository/pom.xml
new file mode 100644
index 0000000..f7ffe8b
--- /dev/null
+++ b/jpa-idempotent-repository/pom.xml
@@ -0,0 +1,399 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+    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.
+
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>camel-quarkus-examples-jpa-idempotent-repository</artifactId>
+    <groupId>org.apache.camel.quarkus.examples</groupId>
+    <version>3.5.0-SNAPSHOT</version>
+
+    <name>Camel Quarkus :: Examples :: JPA Idempotent Repository</name>
+    <description>Camel Quarkus Example :: JPA Idempotent Repository</description>
+
+    <properties>
+        <quarkus.platform.version>3.4.1</quarkus.platform.version>
+        <camel-quarkus.platform.version>3.5.0-SNAPSHOT</camel-quarkus.platform.version>
+
+        <quarkus.platform.group-id>io.quarkus</quarkus.platform.group-id>
+        <quarkus.platform.artifact-id>quarkus-bom</quarkus.platform.artifact-id>
+        <camel-quarkus.platform.group-id>org.apache.camel.quarkus</camel-quarkus.platform.group-id>
+        <camel-quarkus.platform.artifact-id>camel-quarkus-bom</camel-quarkus.platform.artifact-id>
+
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
+        <maven.compiler.target>17</maven.compiler.target>
+        <maven.compiler.source>17</maven.compiler.source>
+        <maven.compiler.testTarget>${maven.compiler.target}</maven.compiler.testTarget>
+        <maven.compiler.testSource>${maven.compiler.source}</maven.compiler.testSource>
+
+        <formatter-maven-plugin.version>2.23.0</formatter-maven-plugin.version>
+        <groovy-maven-plugin.version>2.1.1</groovy-maven-plugin.version>
+        <impsort-maven-plugin.version>1.9.0</impsort-maven-plugin.version>
+        <license-maven-plugin.version>4.2</license-maven-plugin.version>
+        <maven-compiler-plugin.version>3.11.0</maven-compiler-plugin.version>
+        <maven-jar-plugin.version>3.3.0</maven-jar-plugin.version>
+        <maven-resources-plugin.version>3.3.1</maven-resources-plugin.version>
+        <maven-surefire-plugin.version>3.1.2</maven-surefire-plugin.version>
+    </properties>
+
+    <dependencyManagement>
+        <dependencies>
+            <!-- Import BOM -->
+            <dependency>
+                <groupId>${quarkus.platform.group-id}</groupId>
+                <artifactId>${quarkus.platform.artifact-id}</artifactId>
+                <version>${quarkus.platform.version}</version>
+                <type>pom</type>
+                <scope>import</scope>
+            </dependency>
+            <dependency>
+                <groupId>${camel-quarkus.platform.group-id}</groupId>
+                <artifactId>${camel-quarkus.platform.artifact-id}</artifactId>
+                <version>${camel-quarkus.platform.version}</version>
+                <type>pom</type>
+                <scope>import</scope>
+            </dependency>
+        </dependencies>
+    </dependencyManagement>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.apache.camel.quarkus</groupId>
+            <artifactId>camel-quarkus-bean</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.camel.quarkus</groupId>
+            <artifactId>camel-quarkus-file</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.camel.quarkus</groupId>
+            <artifactId>camel-quarkus-http</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.camel.quarkus</groupId>
+            <artifactId>camel-quarkus-jpa</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.camel.quarkus</groupId>
+            <artifactId>camel-quarkus-platform-http</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.camel.quarkus</groupId>
+            <artifactId>camel-quarkus-timer</artifactId>
+        </dependency>
+        <!-- Note we added a dependency to quarkus-h2 to have an in memory 
+            database during dev mode -->
+        <dependency>
+            <groupId>io.quarkus</groupId>
+            <artifactId>quarkus-jdbc-h2</artifactId>
+        </dependency>
+        <!-- While in test and prod we use a derby database -->
+        <dependency>
+            <groupId>io.quarkus</groupId>
+            <artifactId>quarkus-jdbc-derby</artifactId>
+        </dependency>
+
+        <!-- Test -->
+        <dependency>
+            <groupId>io.quarkus</groupId>
+            <artifactId>quarkus-junit5</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.awaitility</groupId>
+            <artifactId>awaitility</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>io.rest-assured</groupId>
+            <artifactId>rest-assured</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.testcontainers</groupId>
+            <artifactId>testcontainers</artifactId>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <pluginManagement>
+            <plugins>
+                <plugin>
+                    <groupId>org.codehaus.gmaven</groupId>
+                    <artifactId>groovy-maven-plugin</artifactId>
+                    <version>${groovy-maven-plugin.version}</version>
+                </plugin>
+
+                <plugin>
+                    <groupId>net.revelc.code.formatter</groupId>
+                    <artifactId>formatter-maven-plugin</artifactId>
+                    <version>${formatter-maven-plugin.version}</version>
+                    <configuration>
+                        <configFile>${maven.multiModuleProjectDirectory}/eclipse-formatter-config.xml</configFile>
+                        <lineEnding>LF</lineEnding>
+                    </configuration>
+                </plugin>
+
+                <plugin>
+                    <groupId>net.revelc.code</groupId>
+                    <artifactId>impsort-maven-plugin</artifactId>
+                    <version>${impsort-maven-plugin.version}</version>
+                    <configuration>
+                        <groups>java.,javax.,org.w3c.,org.xml.,junit.</groups>
+                        <removeUnused>true</removeUnused>
+                        <staticAfter>true</staticAfter>
+                        <staticGroups>java.,javax.,org.w3c.,org.xml.,junit.</staticGroups>
+                    </configuration>
+                </plugin>
+
+                <plugin>
+                    <groupId>org.apache.maven.plugins</groupId>
+                    <artifactId>maven-compiler-plugin</artifactId>
+                    <version>${maven-compiler-plugin.version}</version>
+                    <configuration>
+                        <showDeprecation>true</showDeprecation>
+                        <showWarnings>true</showWarnings>
+                        <compilerArgs>
+                            <arg>-Xlint:unchecked</arg>
+                        </compilerArgs>
+                    </configuration>
+                </plugin>
+
+                <plugin>
+                    <groupId>org.apache.maven.plugins</groupId>
+                    <artifactId>maven-surefire-plugin</artifactId>
+                    <version>${maven-surefire-plugin.version}</version>
+                    <configuration>
+                        <failIfNoTests>false</failIfNoTests>
+                        <systemProperties>
+                            <java.util.logging.manager>org.jboss.logmanager.LogManager</java.util.logging.manager>
+                        </systemProperties>
+                    </configuration>
+                </plugin>
+
+                <plugin>
+                    <groupId>${quarkus.platform.group-id}</groupId>
+                    <artifactId>quarkus-maven-plugin</artifactId>
+                    <version>${quarkus.platform.version}</version>
+                </plugin>
+
+                <plugin>
+                    <groupId>org.apache.maven.plugins</groupId>
+                    <artifactId>maven-failsafe-plugin</artifactId>
+                    <version>${maven-surefire-plugin.version}</version>
+                </plugin>
+
+                <plugin>
+                    <groupId>org.apache.maven.plugins</groupId>
+                    <artifactId>maven-jar-plugin</artifactId>
+                    <version>${maven-jar-plugin.version}</version>
+                </plugin>
+
+                <plugin>
+                    <groupId>com.mycila</groupId>
+                    <artifactId>license-maven-plugin</artifactId>
+                    <version>${license-maven-plugin.version}</version>
+                    <configuration>
+                        <failIfUnknown>true</failIfUnknown>
+                        <header>${maven.multiModuleProjectDirectory}/header.txt</header>
+                        <excludes>
+                            <exclude>**/*.adoc</exclude>
+                            <exclude>**/*.odp</exclude>
+                            <exclude>**/*.txt</exclude>
+                            <exclude>**/LICENSE.txt</exclude>
+                            <exclude>**/LICENSE</exclude>
+                            <exclude>**/NOTICE.txt</exclude>
+                            <exclude>**/NOTICE</exclude>
+                            <exclude>**/README</exclude>
+                            <exclude>**/pom.xml.versionsBackup</exclude>
+                        </excludes>
+                        <mapping>
+                            <java>SLASHSTAR_STYLE</java>
+                            <properties>CAMEL_PROPERTIES_STYLE</properties>
+                            <kt>SLASHSTAR_STYLE</kt>
+                        </mapping>
+                        <headerDefinitions>
+                            <headerDefinition>${maven.multiModuleProjectDirectory}/license-properties-headerdefinition.xml</headerDefinition>
+                        </headerDefinitions>
+                    </configuration>
+                </plugin>
+            </plugins>
+        </pluginManagement>
+
+        <plugins>
+            <plugin>
+                <groupId>${quarkus.platform.group-id}</groupId>
+                <artifactId>quarkus-maven-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <id>build</id>
+                        <goals>
+                            <goal>build</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+
+            <plugin>
+                <groupId>net.revelc.code.formatter</groupId>
+                <artifactId>formatter-maven-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <id>format</id>
+                        <goals>
+                            <goal>format</goal>
+                        </goals>
+                        <phase>process-sources</phase>
+                    </execution>
+                </executions>
+            </plugin>
+
+            <plugin>
+                <groupId>net.revelc.code</groupId>
+                <artifactId>impsort-maven-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <id>sort-imports</id>
+                        <goals>
+                            <goal>sort</goal>
+                        </goals>
+                        <phase>process-sources</phase>
+                    </execution>
+                </executions>
+            </plugin>
+
+            <plugin>
+                <groupId>org.codehaus.gmaven</groupId>
+                <artifactId>groovy-maven-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <id>sync-k8s-resource-versions</id>
+                        <inherited>false</inherited>
+                        <goals>
+                            <goal>execute</goal>
+                        </goals>
+                        <phase>process-sources</phase>
+                        <configuration>
+                            <source>
+                                import java.util.regex.Matcher
+                                import java.util.regex.Pattern
+
+                                Pattern pattern = Pattern.compile("app.kubernetes.io/version: .*")
+
+                                ["kubernetes", "openshift"].each { k8sResource -&gt;
+                                    String sanitizedBasedir = project.basedir.path.replace('\\', '/')
+                                    File file = new File("${sanitizedBasedir}/src/main/kubernetes/${k8sResource}.yml")
+                                    String content = file.text
+                                    Matcher matcher = pattern.matcher(content)
+
+                                    if (matcher.find()) {
+                                        String updatedContent = matcher.replaceAll("app.kubernetes.io/version: ${project.version}")
+                                        if (updatedContent != content) {
+                                            log.info("Updating app.kubernetes.io/version label to ${project.version} in ${file.path}")
+                                            file.write(updatedContent)
+                                        }
+                                    }
+                                }
+                            </source>
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+    </build>
+
+    <profiles>
+        <profile>
+            <id>native</id>
+            <activation>
+                <property>
+                    <name>native</name>
+                </property>
+            </activation>
+            <properties>
+                <quarkus.package.type>native</quarkus.package.type>
+            </properties>
+            <build>
+                <plugins>
+                    <plugin>
+                        <groupId>org.apache.maven.plugins</groupId>
+                        <artifactId>maven-failsafe-plugin</artifactId>
+                        <executions>
+                            <execution>
+                                <goals>
+                                    <goal>integration-test</goal>
+                                    <goal>verify</goal>
+                                </goals>
+                            </execution>
+                        </executions>
+                    </plugin>
+                </plugins>
+            </build>
+        </profile>
+        <profile>
+            <id>kubernetes</id>
+            <activation>
+                <activeByDefault>true</activeByDefault>
+                <property>
+                    <name>kubernetes</name>
+                </property>
+            </activation>
+            <dependencies>
+                <dependency>
+                    <groupId>io.quarkus</groupId>
+                    <artifactId>quarkus-kubernetes</artifactId>
+                </dependency>
+                <dependency>
+                    <groupId>io.quarkus</groupId>
+                    <artifactId>quarkus-container-image-jib</artifactId>
+                </dependency>
+            </dependencies>
+        </profile>
+        <profile>
+            <id>openshift</id>
+            <activation>
+                <property>
+                    <name>openshift</name>
+                </property>
+            </activation>
+            <dependencies>
+                <dependency>
+                    <groupId>io.quarkus</groupId>
+                    <artifactId>quarkus-openshift</artifactId>
+                </dependency>
+            </dependencies>
+        </profile>
+        <profile>
+            <id>skip-testcontainers-tests</id>
+            <activation>
+                <property>
+                    <name>skip-testcontainers-tests</name>
+                </property>
+            </activation>
+            <properties>
+                <skipTests>true</skipTests>
+            </properties>
+        </profile>
+    </profiles>
+
+</project>
diff --git a/jpa-idempotent-repository/schema.png b/jpa-idempotent-repository/schema.png
new file mode 100644
index 0000000..0252310
Binary files /dev/null and b/jpa-idempotent-repository/schema.png differ
diff --git a/jpa-idempotent-repository/schemas-source.odp b/jpa-idempotent-repository/schemas-source.odp
new file mode 100644
index 0000000..0c40e43
Binary files /dev/null and b/jpa-idempotent-repository/schemas-source.odp differ
diff --git a/jpa-idempotent-repository/src/main/java/org/acme/jpa/idempotent/repository/CostlyApiService.java b/jpa-idempotent-repository/src/main/java/org/acme/jpa/idempotent/repository/CostlyApiService.java
new file mode 100644
index 0000000..09351f9
--- /dev/null
+++ b/jpa-idempotent-repository/src/main/java/org/acme/jpa/idempotent/repository/CostlyApiService.java
@@ -0,0 +1,53 @@
+/*
+ * 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.acme.jpa.idempotent.repository;
+
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
+import io.quarkus.runtime.annotations.RegisterForReflection;
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.inject.Named;
+import org.apache.camel.Handler;
+import org.jboss.logging.Logger;
+
+@ApplicationScoped
+@RegisterForReflection
+@Named("costlyApiService")
+public class CostlyApiService {
+
+    private static final Logger LOG = Logger.getLogger(CostlyApiService.class);
+
+    private static Set<String> ALREADY_USED_CONTENT = ConcurrentHashMap.newKeySet();
+
+    /**
+     * The content parameter is populated with the incoming HTTP body sent to this API.
+     */
+    @Handler
+    void invoke(String content) {
+        if (ALREADY_USED_CONTENT.contains(content)) {
+            LOG.info("Costly API has been called two times with the same content => TOO MUCH EXPENSIVE !");
+        } else {
+            ALREADY_USED_CONTENT.add(content);
+            LOG.info("Costly API has been called with new content => GOOD");
+        }
+    }
+
+    String getContentSet() {
+        return String.join(",", ALREADY_USED_CONTENT);
+    }
+}
diff --git a/jpa-idempotent-repository/src/main/java/org/acme/jpa/idempotent/repository/ExampleHarnessRoutes.java b/jpa-idempotent-repository/src/main/java/org/acme/jpa/idempotent/repository/ExampleHarnessRoutes.java
new file mode 100644
index 0000000..1e5fcb4
--- /dev/null
+++ b/jpa-idempotent-repository/src/main/java/org/acme/jpa/idempotent/repository/ExampleHarnessRoutes.java
@@ -0,0 +1,68 @@
+/*
+ * 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.acme.jpa.idempotent.repository;
+
+import jakarta.enterprise.context.ApplicationScoped;
+import org.apache.camel.builder.RouteBuilder;
+
+/**
+ * The example harness provide a timer based route creating some input files for the main example route in
+ * JpaIdempotentRoute.java. It also defines a platform-http based route that mock the costly API.
+ */
+@ApplicationScoped
+public class ExampleHarnessRoutes extends RouteBuilder {
+
+    private static int COUNT = 1;
+
+    @Override
+    public void configure() {
+        /**
+         * Generate some example input files
+         */
+        from("timer:createExampleInputFiles?delay={{timer.delay}}&period={{timer.period}}&repeatCount={{timer.repeatCount}}")
+                /**
+                 * Populate the content of each file with a series of odd numbers 1,3,1,5,1,7 Note that the value 1 is a
+                 * doublon in the series, so some of the files have a duplicate content
+                 */
+                .setBody(e -> Integer.toString(++COUNT % 2 == 0 ? 1 : COUNT))
+                .log("-----------------------------------------------------------------")
+                .log("Creating an example input file with content ${body}")
+                /**
+                 * Arbitrary delay to ensure logs are printed in human friendly order most of the time
+                 */
+                .delay(constant(1000))
+                /**
+                 * Create the file with the generated content in the target/input-files folder
+                 */
+                .to("file:target/input-files");
+
+        /**
+         * Consume the incoming API calls.
+         */
+        from("platform-http:/costly-api-call")
+                /**
+                 * Delegate treatment to the bean named costlyApiService defined in CostlyApiService.java
+                 */
+                .bean("costlyApiService");
+
+        /**
+         * Creates a service that return the set of content that were already consumed.
+         */
+        from("platform-http:/content-set")
+                .bean("costlyApiService", "getContentSet");
+    }
+}
diff --git a/jpa-idempotent-repository/src/main/java/org/acme/jpa/idempotent/repository/JpaIdempotentRoute.java b/jpa-idempotent-repository/src/main/java/org/acme/jpa/idempotent/repository/JpaIdempotentRoute.java
new file mode 100644
index 0000000..6ca97c9
--- /dev/null
+++ b/jpa-idempotent-repository/src/main/java/org/acme/jpa/idempotent/repository/JpaIdempotentRoute.java
@@ -0,0 +1,75 @@
+/*
+ * 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.acme.jpa.idempotent.repository;
+
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.enterprise.inject.Produces;
+import jakarta.inject.Named;
+import jakarta.persistence.EntityManagerFactory;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.component.jpa.TransactionStrategy;
+import org.apache.camel.processor.idempotent.jpa.JpaMessageIdRepository;
+import org.eclipse.microprofile.config.inject.ConfigProperty;
+
+@ApplicationScoped
+public class JpaIdempotentRoute extends RouteBuilder {
+
+    /**
+     * Gets the HTTP port where the example harness is listening for API calls.
+     */
+    @ConfigProperty(name = "quarkus.http.port", defaultValue = "8085")
+    private int quarkusPlatformHttpPort;
+
+    /**
+     * The camel route expect a bean named jpaIdempotentRepository of type IdempotentRepository to be present in the
+     * registry. One way to create such a bean is to use a producer method as shown below.
+     */
+    @Produces
+    @Named
+    JpaMessageIdRepository jpaIdempotentRepository(EntityManagerFactory entityManagerFactory,
+            TransactionStrategy transactionStrategy) {
+        /**
+         * The JPA message id repository will store duplicate message IDs in a database. Distinct repository could be
+         * created on the same database by using different processor names.
+         */
+        return new JpaMessageIdRepository(entityManagerFactory, transactionStrategy, "myProcessorName");
+    }
+
+    @Override
+    public void configure() {
+        /**
+         * Read the files generated by the example harness in the target/input-files folder
+         */
+        from("file:target/input-files")
+                .log("Received an example input file having the content ${body}")
+                /**
+                 * The idempotent consumer pattern could be used as below. All messages presented with the same body more
+                 * than once will be filtered out.
+                 */
+                .idempotentConsumer(simple("${body}"))
+                /**
+                 * A place is needed in order to keep track of duplicate message bodies, it's called an idempotent
+                 * repository The idempotent repository could be provided as a bean from the registry like below.
+                 */
+                .idempotentRepository("jpaIdempotentRepository")
+                .log("The file was not a duplicate, invoke the costly API")
+                /**
+                 * Sends the content of the file to the costly API simulated by the example harness.
+                 */
+                .toF("http://localhost:%s/costly-api-call", quarkusPlatformHttpPort);
+    }
+}
diff --git a/jpa-idempotent-repository/src/main/kubernetes/kubernetes.yml b/jpa-idempotent-repository/src/main/kubernetes/kubernetes.yml
new file mode 100644
index 0000000..dfd3968
--- /dev/null
+++ b/jpa-idempotent-repository/src/main/kubernetes/kubernetes.yml
@@ -0,0 +1,94 @@
+#
+# 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.
+#
+
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: camel-quarkus-examples-derby-database-deployment
+  labels:
+    app.kubernetes.io/name: camel-quarkus-examples-derby-database
+    app.kubernetes.io/version: 3.5.0-SNAPSHOT
+spec:
+  replicas: 1
+  selector:
+    matchLabels:
+      app.kubernetes.io/name: camel-quarkus-examples-derby-database
+      app.kubernetes.io/version: 3.5.0-SNAPSHOT
+  template:
+    metadata:
+      labels:
+        app.kubernetes.io/name: camel-quarkus-examples-derby-database
+        app.kubernetes.io/version: 3.5.0-SNAPSHOT
+    spec:
+      containers:
+        - name: derby-database
+          # Use a default configured derby database for example purpose, think twice before deploying to production
+          image: az82/docker-derby:10.16
+          ports:
+            - containerPort: 1527
+          volumeMounts:
+            # The /derby-init folder contains the SQL init script to create the database, the table and the sequence
+            - name: derby-database-init-script-volume
+              mountPath: /derby-init
+            # The /dbs folder is where the actual database content is stored
+            - name: derby-database-data-volume
+              mountPath: /dbs
+          lifecycle:
+            postStart:
+              # Execute the SQL init script after the derby container has started
+              exec:
+                command: ["java", "-Djdbc.drivers=org.apache.derbbc.EmbeddedDriver", "org.apache.derby.tools.ij", "/derby-init/init.sql"]
+      volumes:
+        # Create a volume in order to store the SQL init file
+        - name: derby-database-init-script-volume
+          configMap:
+              name: derby-database-init-script-config-map
+              defaultMode: 0744
+        # Explicitly create an empty dir volume in order to ensure read/write access needed to store database files
+        - name: derby-database-data-volume
+          emptyDir: {}
+---
+apiVersion: v1
+kind: Service
+metadata:
+  labels:
+    app.kubernetes.io/name: camel-quarkus-examples-derby-database
+    app.kubernetes.io/version: 3.5.0-SNAPSHOT
+  name: derby-database
+spec:
+  ports:
+    - name: derby
+      port: 1527
+      targetPort: 1527
+  selector:
+    app.kubernetes.io/name: camel-quarkus-examples-derby-database
+    app.kubernetes.io/version: 3.5.0-SNAPSHOT
+  type: ClusterIP
+---
+apiVersion: v1
+kind: ConfigMap
+metadata:
+  name: derby-database-init-script-config-map
+  labels:
+    app.kubernetes.io/name: camel-quarkus-examples-derby-database
+    app.kubernetes.io/version: 3.5.0-SNAPSHOT
+data:
+  init.sql: |
+    CONNECT 'jdbc:derby:my-db;create=true';
+    CREATE TABLE CAMEL_MESSAGEPROCESSED ( processorName VARCHAR(255), messageId VARCHAR(100), createdAt TIMESTAMP, PRIMARY KEY (processorName, messageId) );
+    CREATE SEQUENCE CAMEL_MESSAGEPROCESSED_SEQ AS INT MAXVALUE 999999 CYCLE;
+---
\ No newline at end of file
diff --git a/jpa-idempotent-repository/src/main/kubernetes/openshift.yml b/jpa-idempotent-repository/src/main/kubernetes/openshift.yml
new file mode 100644
index 0000000..dfd3968
--- /dev/null
+++ b/jpa-idempotent-repository/src/main/kubernetes/openshift.yml
@@ -0,0 +1,94 @@
+#
+# 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.
+#
+
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: camel-quarkus-examples-derby-database-deployment
+  labels:
+    app.kubernetes.io/name: camel-quarkus-examples-derby-database
+    app.kubernetes.io/version: 3.5.0-SNAPSHOT
+spec:
+  replicas: 1
+  selector:
+    matchLabels:
+      app.kubernetes.io/name: camel-quarkus-examples-derby-database
+      app.kubernetes.io/version: 3.5.0-SNAPSHOT
+  template:
+    metadata:
+      labels:
+        app.kubernetes.io/name: camel-quarkus-examples-derby-database
+        app.kubernetes.io/version: 3.5.0-SNAPSHOT
+    spec:
+      containers:
+        - name: derby-database
+          # Use a default configured derby database for example purpose, think twice before deploying to production
+          image: az82/docker-derby:10.16
+          ports:
+            - containerPort: 1527
+          volumeMounts:
+            # The /derby-init folder contains the SQL init script to create the database, the table and the sequence
+            - name: derby-database-init-script-volume
+              mountPath: /derby-init
+            # The /dbs folder is where the actual database content is stored
+            - name: derby-database-data-volume
+              mountPath: /dbs
+          lifecycle:
+            postStart:
+              # Execute the SQL init script after the derby container has started
+              exec:
+                command: ["java", "-Djdbc.drivers=org.apache.derbbc.EmbeddedDriver", "org.apache.derby.tools.ij", "/derby-init/init.sql"]
+      volumes:
+        # Create a volume in order to store the SQL init file
+        - name: derby-database-init-script-volume
+          configMap:
+              name: derby-database-init-script-config-map
+              defaultMode: 0744
+        # Explicitly create an empty dir volume in order to ensure read/write access needed to store database files
+        - name: derby-database-data-volume
+          emptyDir: {}
+---
+apiVersion: v1
+kind: Service
+metadata:
+  labels:
+    app.kubernetes.io/name: camel-quarkus-examples-derby-database
+    app.kubernetes.io/version: 3.5.0-SNAPSHOT
+  name: derby-database
+spec:
+  ports:
+    - name: derby
+      port: 1527
+      targetPort: 1527
+  selector:
+    app.kubernetes.io/name: camel-quarkus-examples-derby-database
+    app.kubernetes.io/version: 3.5.0-SNAPSHOT
+  type: ClusterIP
+---
+apiVersion: v1
+kind: ConfigMap
+metadata:
+  name: derby-database-init-script-config-map
+  labels:
+    app.kubernetes.io/name: camel-quarkus-examples-derby-database
+    app.kubernetes.io/version: 3.5.0-SNAPSHOT
+data:
+  init.sql: |
+    CONNECT 'jdbc:derby:my-db;create=true';
+    CREATE TABLE CAMEL_MESSAGEPROCESSED ( processorName VARCHAR(255), messageId VARCHAR(100), createdAt TIMESTAMP, PRIMARY KEY (processorName, messageId) );
+    CREATE SEQUENCE CAMEL_MESSAGEPROCESSED_SEQ AS INT MAXVALUE 999999 CYCLE;
+---
\ No newline at end of file
diff --git a/jpa-idempotent-repository/src/main/resources/application.properties b/jpa-idempotent-repository/src/main/resources/application.properties
new file mode 100644
index 0000000..c96b8e1
--- /dev/null
+++ b/jpa-idempotent-repository/src/main/resources/application.properties
@@ -0,0 +1,67 @@
+## ---------------------------------------------------------------------------
+## 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.
+## ---------------------------------------------------------------------------
+
+quarkus.banner.enabled = false
+
+# How often should input files be created
+timer.period = 10000
+timer.delay = 1000
+timer.repeatCount = 0
+
+# Few tricks needed to fix port for test purpose
+quarkus.http.port = 8085
+quarkus.http.test-port = 8085
+
+derby-hostname=localhost
+
+# Uncomment if your application image is to be pushed to an external registry
+#quarkus.container-image.registry=my.docker-registry.net
+
+# Kubernetes
+
+# Uncomment to trust self signed certificates if they are presented by the Kubernetes API server
+#quarkus.kubernetes-client.trust-certs=true
+
+quarkus.kubernetes.image-pull-policy=IfNotPresent
+quarkus.kubernetes.env.vars.derby-hostname=derby-database
+
+# Uncomment to set resource limits
+#quarkus.kubernetes.resources.requests.memory=64Mi
+#quarkus.kubernetes.resources.requests.cpu=250m
+#quarkus.kubernetes.resources.limits.memory=512Mi
+#quarkus.kubernetes.resources.limits.cpu=1000m
+
+# OpenShift
+quarkus.openshift.image-pull-policy=IfNotPresent
+quarkus.openshift.env.vars.derby-hostname=derby-database
+
+# Uncomment to set resource limits
+#quarkus.openshift.resources.requests.memory=64Mi
+#quarkus.openshift.resources.requests.cpu=250m
+#quarkus.openshift.resources.limits.memory=512Mi
+#quarkus.openshift.resources.limits.cpu=1000m
+
+# JPA
+quarkus.datasource.db-kind=derby
+%dev.quarkus.datasource.db-kind=h2
+
+quarkus.datasource.jdbc.url=jdbc:derby://${derby-hostname}:1527/my-db
+%dev.quarkus.datasource.jdbc.url=jdbc:h2:mem:my-db;DB_CLOSE_DELAY=-1
+
+quarkus.datasource.jdbc.max-size=8
+
+quarkus.hibernate-orm.database.generation=drop-and-create
diff --git a/jpa-idempotent-repository/src/test/java/org/acme/jpa/idempotent/repository/DerbyTestResource.java b/jpa-idempotent-repository/src/test/java/org/acme/jpa/idempotent/repository/DerbyTestResource.java
new file mode 100644
index 0000000..6e456a2
--- /dev/null
+++ b/jpa-idempotent-repository/src/test/java/org/acme/jpa/idempotent/repository/DerbyTestResource.java
@@ -0,0 +1,79 @@
+/*
+ * 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.acme.jpa.idempotent.repository;
+
+import java.util.Map;
+
+import io.quarkus.test.common.QuarkusTestResourceLifecycleManager;
+import org.apache.camel.util.CollectionHelper;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testcontainers.containers.GenericContainer;
+import org.testcontainers.containers.wait.strategy.Wait;
+import org.testcontainers.utility.MountableFile;
+import org.testcontainers.utility.TestcontainersConfiguration;
+
+/**
+ * The derby test resource starts a derby container. It uses fixed port number 1527.
+ */
+public class DerbyTestResource<T extends GenericContainer> implements QuarkusTestResourceLifecycleManager {
+    private static final Logger LOGGER = LoggerFactory.getLogger(DerbyTestResource.class);
+    private static final String DERBY_IMAGE_NAME = "az82/docker-derby:10.16";
+    private static final int DERBY_PORT = 1527;
+
+    private GenericContainer container;
+
+    @Override
+    public Map<String, String> start() {
+
+        LOGGER.info(TestcontainersConfiguration.getInstance().toString());
+
+        try {
+            container = new GenericContainer(DERBY_IMAGE_NAME)
+                    .withExposedPorts(DERBY_PORT)
+                    .withCopyFileToContainer(MountableFile.forClasspathResource("init.sql"), "/init.sql")
+                    .waitingFor(Wait.forListeningPort());
+            container.start();
+
+            container.execInContainer("java", "-Djdbc.drivers=org.apache.derbbc.EmbeddedDriver",
+                    "org.apache.derby.tools.ij", "/init.sql");
+
+            return CollectionHelper.mapOf("quarkus.datasource.jdbc.url",
+                    "jdbc:derby://localhost:" + container.getMappedPort(DERBY_PORT) + "/my-db", "timer.period", "100",
+                    "timer.delay",
+                    "0", "timer.repeatCount", "4");
+        } catch (Exception e) {
+            LOGGER.error("An error occurred while starting the derby container", e);
+            throw new RuntimeException(e);
+        }
+    }
+
+    protected void startContainer() {
+        container.start();
+    }
+
+    @Override
+    public void stop() {
+        try {
+            if (container != null) {
+                container.stop();
+            }
+        } catch (Exception e) {
+            // ignored
+        }
+    }
+}
diff --git a/jpa-idempotent-repository/src/test/java/org/acme/jpa/idempotent/repository/JpaIdempotentRepositoryIT.java b/jpa-idempotent-repository/src/test/java/org/acme/jpa/idempotent/repository/JpaIdempotentRepositoryIT.java
new file mode 100644
index 0000000..f193ad7
--- /dev/null
+++ b/jpa-idempotent-repository/src/test/java/org/acme/jpa/idempotent/repository/JpaIdempotentRepositoryIT.java
@@ -0,0 +1,24 @@
+/*
+ * 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.acme.jpa.idempotent.repository;
+
+import io.quarkus.test.junit.QuarkusIntegrationTest;
+
+@QuarkusIntegrationTest
+class JpaIdempotentRepositoryIT extends JpaIdempotentRepositoryTest {
+
+}
diff --git a/jpa-idempotent-repository/src/test/java/org/acme/jpa/idempotent/repository/JpaIdempotentRepositoryTest.java b/jpa-idempotent-repository/src/test/java/org/acme/jpa/idempotent/repository/JpaIdempotentRepositoryTest.java
new file mode 100644
index 0000000..e49b5da
--- /dev/null
+++ b/jpa-idempotent-repository/src/test/java/org/acme/jpa/idempotent/repository/JpaIdempotentRepositoryTest.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.acme.jpa.idempotent.repository;
+
+import java.util.concurrent.TimeUnit;
+
+import io.quarkus.test.common.QuarkusTestResource;
+import io.quarkus.test.junit.QuarkusTest;
+import io.restassured.RestAssured;
+import org.junit.jupiter.api.Test;
+
+import static org.awaitility.Awaitility.await;
+
+@QuarkusTest
+@QuarkusTestResource(DerbyTestResource.class)
+public class JpaIdempotentRepositoryTest {
+
+    @Test
+    public void contentSetShouldStartWithOneThreeFive() {
+
+        await().atMost(30L, TimeUnit.SECONDS).pollDelay(500, TimeUnit.MILLISECONDS).until(() -> {
+            String contentSet = RestAssured
+                    .when()
+                    .get("/content-set")
+                    .then()
+                    .extract().asString();
+
+            return contentSet != null && contentSet.startsWith("1,3,5");
+        });
+    }
+}
diff --git a/jpa-idempotent-repository/src/test/resources/init.sql b/jpa-idempotent-repository/src/test/resources/init.sql
new file mode 100644
index 0000000..bac7b01
--- /dev/null
+++ b/jpa-idempotent-repository/src/test/resources/init.sql
@@ -0,0 +1,20 @@
+--
+-- 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.
+--
+
+CONNECT 'jdbc:derby:my-db;create=true';
+CREATE TABLE CAMEL_MESSAGEPROCESSED ( processorName VARCHAR(255), messageId VARCHAR(100), createdAt TIMESTAMP, PRIMARY KEY (processorName, messageId) );
+CREATE SEQUENCE CAMEL_MESSAGEPROCESSED_SEQ AS INT MAXVALUE 999999 CYCLE;