From 2557e43574706578e77fa1601d31aacc114330b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20Ondru=C5=A1ek?= Date: Thu, 3 Sep 2026 07:46:24 +0000 Subject: [PATCH 1/2] CAMEL-24604: camel-jpa - Mark KeyValueEntry.isExpired() as @Transient The entity uses property access, so the derived isExpired() getter was treated as a persistent property without a setter and Hibernate failed to build the SessionFactory ("Could not locate setter method for property 'expired'"). On Quarkus this broke any application with camel-jpa on the classpath, since the entity is auto-discovered and mapped with Hibernate. The fix is covered by the new KeyValueEntryHibernateMappingTest, which runs in every bu. The OpenJPA enhancer -javaagent is now configured only while the openjpa profile is active: the agent jar is copied to target/ by that profile alone, so with -Dhibernate the forked test JVM pointed at a missing jar and failed to start. The OpenJPA persistence.xml configuresopenjpa provcider (as hibernate is now on classpath) Co-Authored-By: Claude Fable 5 Co-Authored-By: Claude Fable 5 --- components/camel-jpa/pom.xml | 30 +++++++---- .../processor/keyvalue/jpa/KeyValueEntry.java | 2 + .../KeyValueEntryHibernateMappingTest.java | 54 +++++++++++++++++++ .../test/resources/META-INF/persistence.xml | 4 ++ .../hibernate/META-INF/persistence.xml | 12 +++++ 5 files changed, 91 insertions(+), 11 deletions(-) create mode 100644 components/camel-jpa/src/test/java/org/apache/camel/processor/keyvalue/jpa/KeyValueEntryHibernateMappingTest.java diff --git a/components/camel-jpa/pom.xml b/components/camel-jpa/pom.xml index ec0b1d70b66f3..065a11cc9898b 100644 --- a/components/camel-jpa/pom.xml +++ b/components/camel-jpa/pom.xml @@ -32,7 +32,7 @@ Camel JPA support - -javaagent:${project.basedir}/target/openjpa-${openjpa-version}.jar -Xmx3G + -Xmx3G 240 @@ -94,7 +94,15 @@ h2 ${h2-version} test - + + + + org.hibernate.orm + hibernate-core + ${hibernate-version} + test + org.junit.jupiter junit-jupiter @@ -134,6 +142,11 @@ !hibernate + + + -javaagent:${project.basedir}/target/openjpa-${openjpa-version}.jar -Xmx3G + @@ -231,8 +244,11 @@ copy-resources - generate-test-resources + + process-test-resources + true @@ -247,14 +263,6 @@ - - - org.hibernate.orm - hibernate-core - ${hibernate-version} - test - - full diff --git a/components/camel-jpa/src/main/java/org/apache/camel/processor/keyvalue/jpa/KeyValueEntry.java b/components/camel-jpa/src/main/java/org/apache/camel/processor/keyvalue/jpa/KeyValueEntry.java index 95517fb6d96d9..b55e14c61ad78 100644 --- a/components/camel-jpa/src/main/java/org/apache/camel/processor/keyvalue/jpa/KeyValueEntry.java +++ b/components/camel-jpa/src/main/java/org/apache/camel/processor/keyvalue/jpa/KeyValueEntry.java @@ -24,6 +24,7 @@ import jakarta.persistence.Id; import jakarta.persistence.Lob; import jakarta.persistence.Table; +import jakarta.persistence.Transient; /** * JPA entity representing a single key-value entry in the {@code CAMEL_KEYVALUE} table. @@ -97,6 +98,7 @@ public void setExpiresAt(long expiresAt) { * * @return whether the entry is expired */ + @Transient public boolean isExpired() { return expiresAt > 0 && System.currentTimeMillis() >= expiresAt; } diff --git a/components/camel-jpa/src/test/java/org/apache/camel/processor/keyvalue/jpa/KeyValueEntryHibernateMappingTest.java b/components/camel-jpa/src/test/java/org/apache/camel/processor/keyvalue/jpa/KeyValueEntryHibernateMappingTest.java new file mode 100644 index 0000000000000..5ddde83b0401c --- /dev/null +++ b/components/camel-jpa/src/test/java/org/apache/camel/processor/keyvalue/jpa/KeyValueEntryHibernateMappingTest.java @@ -0,0 +1,54 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.camel.processor.keyvalue.jpa; + +import org.hibernate.boot.MetadataSources; +import org.hibernate.boot.registry.StandardServiceRegistry; +import org.hibernate.boot.registry.StandardServiceRegistryBuilder; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + +/** + * Verifies in every build (not only with -Dhibernate) that Hibernate can map {@link KeyValueEntry}. The entity is + * shipped in the camel-jpa jar and e.g. Quarkus auto-discovers it from the classpath and always maps it with Hibernate, + * so a mapping problem breaks applications that never use the KeyValueRepository (CAMEL-24604: a derived getter + * without @Transient made Hibernate fail with "Could not locate setter method for property 'expired'"). + *

+ * Uses the native Hibernate bootstrap on purpose: it does not go through jakarta.persistence provider resolution, so + * the rest of the test suite keeps using the provider selected by the active maven profile. + */ +class KeyValueEntryHibernateMappingTest { + + @Test + void hibernateMustBeAbleToMapKeyValueEntry() { + StandardServiceRegistry registry = new StandardServiceRegistryBuilder() + .applySetting("hibernate.connection.driver_class", "org.h2.Driver") + .applySetting("hibernate.connection.url", "jdbc:h2:mem:camel24604") + .build(); + try { + assertDoesNotThrow(() -> new MetadataSources(registry) + .addAnnotatedClass(KeyValueEntry.class) + .buildMetadata() + .buildSessionFactory() + .close(), + "Hibernate should be able to build a SessionFactory for KeyValueEntry"); + } finally { + StandardServiceRegistryBuilder.destroy(registry); + } + } +} diff --git a/components/camel-jpa/src/test/resources/META-INF/persistence.xml b/components/camel-jpa/src/test/resources/META-INF/persistence.xml index 68006958b71a1..b77a38fb5b470 100644 --- a/components/camel-jpa/src/test/resources/META-INF/persistence.xml +++ b/components/camel-jpa/src/test/resources/META-INF/persistence.xml @@ -50,6 +50,7 @@ + org.apache.openjpa.persistence.PersistenceProviderImpl org.apache.camel.examples.MultiSteps org.apache.camel.examples.SendEmail @@ -63,6 +64,7 @@ + org.apache.openjpa.persistence.PersistenceProviderImpl org.apache.camel.examples.SendEmail @@ -80,6 +82,7 @@ + org.apache.openjpa.persistence.PersistenceProviderImpl org.apache.camel.processor.keyvalue.jpa.KeyValueEntry @@ -93,6 +96,7 @@ + org.apache.openjpa.persistence.PersistenceProviderImpl org.apache.camel.processor.idempotent.jpa.MessageProcessed diff --git a/components/camel-jpa/src/test/resources/profiles/hibernate/META-INF/persistence.xml b/components/camel-jpa/src/test/resources/profiles/hibernate/META-INF/persistence.xml index 765e0988daba2..cf73163b0b63e 100644 --- a/components/camel-jpa/src/test/resources/profiles/hibernate/META-INF/persistence.xml +++ b/components/camel-jpa/src/test/resources/profiles/hibernate/META-INF/persistence.xml @@ -79,6 +79,18 @@ + + org.hibernate.jpa.HibernatePersistenceProvider + + org.apache.camel.processor.keyvalue.jpa.KeyValueEntry + + + + + + + + org.hibernate.jpa.HibernatePersistenceProvider From a12a6b0335394b3762fea7b6c60cb568c67477e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20Ondru=C5=A1ek?= Date: Thu, 3 Sep 2026 08:46:10 +0000 Subject: [PATCH 2/2] CAMEL-24615: camel-jpa - Fix the tests failing under the hibernate profile With the hibernate profile runnable again (CAMEL-24604), 7 tests failed and fixing them surfaced further OpenJPA-specific assumptions in the test suite: - the "camel" unit used hbm2ddl.auto=create, which drops and recreates the schema whenever an additional JPA endpoint creates its EntityManagerFactory mid-test; "update" is additive like OpenJPA's SynchronizeMappings - AbstractJpaMethodSupport cleaned up via em.joinTransaction() inside a Spring TransactionTemplate, which enlists only under OpenJPA; under Hibernate it silently began a local transaction that was never committed. A plain resource-local transaction is used instead, and the orphaned Address rows are deleted as well since bulk deletes do not cascade - JpaProducerWithQueryTest asserted positional columns of a "select *" native query whose column order depends on the provider-generated DDL; the query now selects explicit columns - the keyvalueDb unit uses the same hibernate.dialect / hibernate.connection.* property style as the other units in the hibernate persistence.xml (review feedback on #26074) Both profiles are green: 119 tests with -Dhibernate and 119 by default. Co-Authored-By: Claude Fable 5 --- .../jpa/AbstractJpaMethodSupport.java | 29 ++++++++----------- .../jpa/JpaProducerWithQueryTest.java | 5 +++- .../hibernate/META-INF/persistence.xml | 10 +++++-- 3 files changed, 23 insertions(+), 21 deletions(-) diff --git a/components/camel-jpa/src/test/java/org/apache/camel/component/jpa/AbstractJpaMethodSupport.java b/components/camel-jpa/src/test/java/org/apache/camel/component/jpa/AbstractJpaMethodSupport.java index 0e844b0149141..2e4ecce845a69 100644 --- a/components/camel-jpa/src/test/java/org/apache/camel/component/jpa/AbstractJpaMethodSupport.java +++ b/components/camel-jpa/src/test/java/org/apache/camel/component/jpa/AbstractJpaMethodSupport.java @@ -25,8 +25,6 @@ import org.apache.camel.examples.Customer; import org.apache.camel.test.junit6.CamelTestSupport; import org.junit.jupiter.api.AfterEach; -import org.springframework.transaction.TransactionStatus; -import org.springframework.transaction.support.TransactionCallback; import org.springframework.transaction.support.TransactionTemplate; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -53,27 +51,24 @@ protected void setUp(String endpointUri) throws Exception { } entityManager = endpoint.getEntityManagerFactory().createEntityManager(); - transactionTemplate.execute(new TransactionCallback() { - public Object doInTransaction(TransactionStatus status) { - entityManager.joinTransaction(); - entityManager.createQuery("delete from " + Customer.class.getName()).executeUpdate(); - return null; - } - }); + // use a plain resource-local transaction: em.joinTransaction() enlists with the Spring-managed + // transaction only under OpenJPA; under Hibernate it silently begins a local transaction that + // is never committed, so the cleanup would be lost + entityManager.getTransaction().begin(); + entityManager.createQuery("delete from " + Customer.class.getName()).executeUpdate(); + // bulk delete does not cascade, so remove the orphaned addresses explicitly + entityManager.createQuery("delete from " + Address.class.getName()).executeUpdate(); + entityManager.getTransaction().commit(); assertEntitiesInDatabase(0, Customer.class.getName()); assertEntitiesInDatabase(0, Address.class.getName()); } protected void save(final Object persistable) { - transactionTemplate.execute(new TransactionCallback() { - public Object doInTransaction(TransactionStatus status) { - entityManager.joinTransaction(); - entityManager.persist(persistable); - entityManager.flush(); - return null; - } - }); + entityManager.getTransaction().begin(); + entityManager.persist(persistable); + entityManager.flush(); + entityManager.getTransaction().commit(); } protected void assertEntitiesInDatabase(int count, String entity) { diff --git a/components/camel-jpa/src/test/java/org/apache/camel/processor/jpa/JpaProducerWithQueryTest.java b/components/camel-jpa/src/test/java/org/apache/camel/processor/jpa/JpaProducerWithQueryTest.java index f4da5631f6def..64b479acfbc13 100644 --- a/components/camel-jpa/src/test/java/org/apache/camel/processor/jpa/JpaProducerWithQueryTest.java +++ b/components/camel-jpa/src/test/java/org/apache/camel/processor/jpa/JpaProducerWithQueryTest.java @@ -134,7 +134,10 @@ public void configure() { from("direct:namedQuery") .to("jpa://" + Customer.class.getName() + "?namedQuery=findAllCustomersWithName¶meters=#params"); from("direct:nativeQuery") - .to("jpa://" + MultiSteps.class.getName() + "?nativeQuery=select * from MultiSteps where step = 1"); + // explicit column list: the column order of "select *" depends on the DDL the + // JPA provider generated, so positional access to the result would not be stable + .to("jpa://" + MultiSteps.class.getName() + + "?nativeQuery=select id, address, step from MultiSteps where step = 1"); from("direct:nativeQueryWithResultClass") .to("jpa://" + MultiSteps.class.getName() + "?resultClass=org.apache.camel.examples.MultiSteps&nativeQuery=select * from MultiSteps where step = 1"); diff --git a/components/camel-jpa/src/test/resources/profiles/hibernate/META-INF/persistence.xml b/components/camel-jpa/src/test/resources/profiles/hibernate/META-INF/persistence.xml index cf73163b0b63e..91c66d538925b 100644 --- a/components/camel-jpa/src/test/resources/profiles/hibernate/META-INF/persistence.xml +++ b/components/camel-jpa/src/test/resources/profiles/hibernate/META-INF/persistence.xml @@ -35,7 +35,10 @@ - + + @@ -85,8 +88,9 @@ org.apache.camel.processor.keyvalue.jpa.KeyValueEntry - - + + +