This is an automated email from the ASF dual-hosted git repository.
davsclaus pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel.git
The following commit(s) were added to refs/heads/main by this push:
new 3480b7cea781 CAMEL-21438: Fix racy JpaPollingConsumerLockEntityTest
and fact-check jpa-component.adoc
3480b7cea781 is described below
commit 3480b7cea781cb4f3ca6ece52597a893a021e130
Author: Adriano Machado <[email protected]>
AuthorDate: Thu Jul 9 11:39:24 2026 -0400
CAMEL-21438: Fix racy JpaPollingConsumerLockEntityTest and fact-check
jpa-component.adoc
Closes #24566
Co-Authored-By: Claude Sonnet 5 <[email protected]>
---
.../camel-jpa/src/main/docs/jpa-component.adoc | 82 ++++++++++++++++++----
.../jpa/JpaPollingConsumerLockEntityTest.java | 11 ++-
2 files changed, 77 insertions(+), 16 deletions(-)
diff --git a/components/camel-jpa/src/main/docs/jpa-component.adoc
b/components/camel-jpa/src/main/docs/jpa-component.adoc
index d01cbc0a6294..43627d4dff20 100644
--- a/components/camel-jpa/src/main/docs/jpa-component.adoc
+++ b/components/camel-jpa/src/main/docs/jpa-component.adoc
@@ -37,7 +37,7 @@ jpa:entityClassName[?options]
----
For sending to the endpoint, the _entityClassName_ is optional. If
-specified, it helps the http://camel.apache.org/type-converter.html[Type
Converter] to
+specified, it helps the xref:manual::type-converter.adoc[Type Converter] to
ensure the body is of the correct type.
For consuming, the _entityClassName_ is mandatory.
@@ -56,7 +56,7 @@ include::partial$component-endpoint-headers.adoc[]
You can store a Java entity bean in a database by sending it to a JPA
producer endpoint. The body of the _In_ message is assumed to be an
entity bean (that is a POJO with an
-https://jakarta.ee/specifications/persistence/2.2/apidocs/javax/persistence/entity[@Entity]
+https://jakarta.ee/specifications/persistence/3.2/apidocs/jakarta.persistence/jakarta/persistence/entity[@Entity]
annotation on it) or a collection or array of entity beans.
If the body is a List of entities, make sure to use
@@ -150,6 +150,45 @@ explicitly configure a JPA component that references the
</bean>
----
+=== Entity enhancement
+
+JPA providers such as OpenJPA need to enhance (byte-code weave) your `@Entity`
classes so they
+can track field access and manage lazy loading. Without enhancement, you may
see errors such as
+`ArgumentException: ... were not enhanced at build time or at class load time
with a javaagent`
+when the entity is first used.
+
+OpenJPA supports enhancing entities either dynamically at runtime (for example
through a
+`-javaagent`, or automatic class-loading hooks in a Jakarta EE/OSGi container)
or at build time.
+https://openjpa.apache.org/entity-enhancement.html[Build-time enhancement] is
recommended: it is
+faster and more reliable than dynamic enhancement, which OpenJPA's own
documentation discourages
+for production use due to known performance and functional issues.
+
+To enable build-time enhancement with Maven, add the `openjpa-maven-plugin` to
your `pom.xml`
+bound to the `process-classes` phase:
+
+[source,xml]
+----
+<plugin>
+ <groupId>org.apache.openjpa</groupId>
+ <artifactId>openjpa-maven-plugin</artifactId>
+ <!-- use the same version as your OpenJPA runtime dependency -->
+ <version>4.1.1</version>
+ <executions>
+ <execution>
+ <id>enhancer</id>
+ <phase>process-classes</phase>
+ <goals>
+ <goal>enhance</goal>
+ </goals>
+ </execution>
+ </executions>
+</plugin>
+----
+
+See https://openjpa.apache.org/enhancement-with-maven.html[Entity Enhancement
with Maven] for the
+full set of plugin options (such as scoping enhancement to specific packages
with
+`includes`/`excludes`) and for the `maven-antrun-plugin` alternative.
+
=== Using a consumer with a named query
For consuming only selected entities, you can use the
@@ -431,23 +470,40 @@ the message body.
=== Using the JPA-Based Idempotent Repository
-The Idempotent Consumer from the
http://camel.apache.org/enterprise-integration-patterns.html[EIP patterns] is
used to filter out duplicate messages. A JPA-based idempotent repository is
provided.
+Camel supports the Idempotent Consumer EIP, which is used to filter out
duplicate messages. A JPA-based idempotent repository is provided.
To use the JPA based idempotent repository.
.Procedure
. Set up a `persistence-unit` in the persistence.xml file:
++
+[source,xml]
+----
+<persistence-unit name="idempotentDb" transaction-type="RESOURCE_LOCAL">
+ <class>org.apache.camel.processor.idempotent.jpa.MessageProcessed</class>
-. Set up a `org.springframework.orm.jpa.JpaTemplate`
-which is used by the
-`org.apache.camel.processor.idempotent.jpa.JpaMessageIdRepository`:
-
-. Configure the error formatting macro: snippet:
java.lang.IndexOutOfBoundsException:
-Index: 20, Size: 20
+ <properties>
+ <property name="openjpa.ConnectionURL"
value="jdbc:h2:./target/idempotentTest;DB_CLOSE_DELAY=-1"/>
+ <property name="openjpa.ConnectionDriverName" value="org.h2.Driver"/>
+ <property name="openjpa.jdbc.SynchronizeMappings" value="buildSchema"/>
+ </properties>
+</persistence-unit>
+----
-. Configure the idempotent repository:
-`org.apache.camel.processor.idempotent.jpa.JpaMessageIdRepository`:
+. Configure the
`org.apache.camel.processor.idempotent.jpa.JpaMessageIdRepository` bean,
+referencing the `EntityManagerFactory` built from that persistence unit:
++
+[source,xml]
+----
+<bean id="jpaStore"
class="org.apache.camel.processor.idempotent.jpa.JpaMessageIdRepository">
+ <!-- Here we refer to the entityManagerFactory -->
+ <constructor-arg index="0" ref="entityManagerFactory"/>
+ <!-- This 2nd parameter is the name (a category name).
+ You can have different repositories with different names -->
+ <constructor-arg index="1" value="myProcessorName"/>
+</bean>
+----
. Create the JPA idempotent repository in the Spring XML file:
@@ -520,10 +576,10 @@
https://github.com/apache/camel/blob/main/components/camel-jpa/pom.xml[enhance
the byte-code at build time]. To overcome this, you need to enable
http://openjpa.apache.org/entity-enhancement.html#dynamic-enhancement[dynamic
byte-code enhancement of OpenJPA]. For example, assuming the current
-OpenJPA version being used in Camel is 2.2.1, to run the
+OpenJPA version being used in Camel is 4.1.1, to run the
tests inside your IDE, you would need to pass the following
argument to the JVM:
----
--javaagent:<path_to_your_local_m2_cache>/org/apache/openjpa/openjpa/2.2.1/openjpa-2.2.1.jar
+-javaagent:<path_to_your_local_m2_cache>/org/apache/openjpa/openjpa/4.1.1/openjpa-4.1.1.jar
----
diff --git
a/components/camel-jpa/src/test/java/org/apache/camel/processor/jpa/JpaPollingConsumerLockEntityTest.java
b/components/camel-jpa/src/test/java/org/apache/camel/processor/jpa/JpaPollingConsumerLockEntityTest.java
index 3cc1bff20dbd..5e6126cb88dd 100644
---
a/components/camel-jpa/src/test/java/org/apache/camel/processor/jpa/JpaPollingConsumerLockEntityTest.java
+++
b/components/camel-jpa/src/test/java/org/apache/camel/processor/jpa/JpaPollingConsumerLockEntityTest.java
@@ -50,7 +50,10 @@ public class JpaPollingConsumerLockEntityTest extends
AbstractJpaTest {
public void testPollingConsumerWithLock() throws Exception {
MockEndpoint mock = getMockEndpoint("mock:locked");
- mock.expectedBodiesReceived(
+ // The two concurrent requests race for the optimistic lock, so
whichever one wins the
+ // uncontended read (and thus produces "orders: 1") is not
deterministic, and the winner's
+ // exchange is not guaranteed to reach the mock endpoint first under
CI-level thread contention.
+ mock.expectedBodiesReceivedInAnyOrder(
"orders: 1",
"orders: 2");
@@ -104,8 +107,10 @@ public class JpaPollingConsumerLockEntityTest extends
AbstractJpaTest {
from("direct:locked")
.onException(OptimisticLockException.class)
- .redeliveryDelay(60)
- .maximumRedeliveries(2)
+ // Generous budget so the losing side of the
optimistic-lock race has enough
+ // room to succeed even under heavy CI host contention.
+ .redeliveryDelay(100)
+ .maximumRedeliveries(10)
.end()
.pollEnrich()
.simple("jpa://" + Customer.class.getName()