diff --git a/jpa/eclipselink.jpa.testapps/jpa.test.persistence32/pom.xml b/jpa/eclipselink.jpa.testapps/jpa.test.persistence32/pom.xml
new file mode 100644
index 00000000000..a7617914a93
--- /dev/null
+++ b/jpa/eclipselink.jpa.testapps/jpa.test.persistence32/pom.xml
@@ -0,0 +1,86 @@
+
+
+
+
+
+ org.eclipse.persistence.jpa.testapps
+ org.eclipse.persistence
+ 4.1.0-SNAPSHOT
+ ../pom.xml
+
+ 4.0.0
+
+ org.eclipse.persistence.jpa.testapps.persistence32
+
+ Test - Jakarta Persistence 3.2
+
+
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-dependency-plugin
+
+
+
+ get-test-classpath-to-properties
+ process-test-classes
+
+
+
+
+ org.carlspring.maven
+ derby-maven-plugin
+
+
+ start-derby
+ process-test-classes
+
+
+ stop-derby
+ prepare-package
+
+
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+
+
+ default-test
+
+ -javaagent:${org.eclipse.persistence:org.eclipse.persistence.jpa:jar} @{argLine}
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-jar-plugin
+
+
+ package-model
+ package
+
+
+
+
+
+
+
diff --git a/jpa/eclipselink.jpa.testapps/jpa.test.persistence32/src/main/java/org/eclipse/persistence/testing/models/jpa/persistence32/Persistence32TableCreator.java b/jpa/eclipselink.jpa.testapps/jpa.test.persistence32/src/main/java/org/eclipse/persistence/testing/models/jpa/persistence32/Persistence32TableCreator.java
new file mode 100644
index 00000000000..8b1d2f6387b
--- /dev/null
+++ b/jpa/eclipselink.jpa.testapps/jpa.test.persistence32/src/main/java/org/eclipse/persistence/testing/models/jpa/persistence32/Persistence32TableCreator.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0,
+ * or the Eclipse Distribution License v. 1.0 which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
+ */
+
+package org.eclipse.persistence.testing.models.jpa.persistence32;
+
+import org.eclipse.persistence.testing.framework.TogglingFastTableCreator;
+import org.eclipse.persistence.tools.schemaframework.TableDefinition;
+
+public class Persistence32TableCreator extends TogglingFastTableCreator {
+
+ public Persistence32TableCreator() {
+ setName("Persistence32Project");
+ addTableDefinition(buildTypeTable());
+ addTableDefinition(buildPokemonTable());
+ addTableDefinition(buildPokemonTypeTable());
+ }
+
+ public static TableDefinition buildTypeTable() {
+ TableDefinition table = new TableDefinition();
+ table.setName("PERSISTENCE32_TYPE");
+ table.addField(createNumericPk("ID"));
+ table.addField(createStringColumn("NAME", 64, false));
+ return table;
+ }
+
+ public static TableDefinition buildPokemonTable() {
+ TableDefinition table = new TableDefinition();
+ table.setName("PERSISTENCE32_POKEMON");
+ table.addField(createNumericPk("ID"));
+ table.addField(createStringColumn("NAME", 64, false));
+ return table;
+ }
+
+ public static TableDefinition buildPokemonTypeTable() {
+ TableDefinition table = new TableDefinition();
+ table.setName("PERSISTENCE32_POKEMON_TYPE");
+ table.addField(createNumericFk("POKEMON_ID", "PERSISTENCE32_POKEMON.ID"));
+ table.addField(createNumericFk("TYPE_ID", "PERSISTENCE32_TYPE.ID"));
+ return table;
+ }
+
+}
diff --git a/jpa/eclipselink.jpa.testapps/jpa.test.persistence32/src/main/java/org/eclipse/persistence/testing/models/jpa/persistence32/Pokemon.java b/jpa/eclipselink.jpa.testapps/jpa.test.persistence32/src/main/java/org/eclipse/persistence/testing/models/jpa/persistence32/Pokemon.java
new file mode 100644
index 00000000000..c34d84a33d8
--- /dev/null
+++ b/jpa/eclipselink.jpa.testapps/jpa.test.persistence32/src/main/java/org/eclipse/persistence/testing/models/jpa/persistence32/Pokemon.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0,
+ * or the Eclipse Distribution License v. 1.0 which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
+ */
+
+package org.eclipse.persistence.testing.models.jpa.persistence32;
+
+import java.util.Collection;
+import java.util.Objects;
+
+import jakarta.persistence.Entity;
+import jakarta.persistence.GeneratedValue;
+import jakarta.persistence.GenerationType;
+import jakarta.persistence.Id;
+import jakarta.persistence.JoinColumn;
+import jakarta.persistence.JoinTable;
+import jakarta.persistence.ManyToMany;
+import jakarta.persistence.NamedQuery;
+import jakarta.persistence.Table;
+
+@Entity
+@Table(name="PERSISTENCE32_POKEMON")
+@NamedQuery(name="Pokemon.get", query="SELECT p FROM Pokemon p WHERE p.id = :id")
+public class Pokemon {
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ private int id;
+
+ private String name;
+
+ @ManyToMany
+ @JoinTable(name = "PERSISTENCE32_POKEMON_TYPE",
+ joinColumns = @JoinColumn(
+ name = "POKEMON_ID",
+ referencedColumnName = "ID"
+ ),
+ inverseJoinColumns = @JoinColumn(
+ name = "TYPE_ID",
+ referencedColumnName = "ID"
+ ))
+ private Collection types;
+
+ public Pokemon() {
+ }
+
+ public Pokemon(String name, Collection types) {
+ this.name = name;
+ this.types = types;
+ }
+
+ public Pokemon(int id, String name, Collection types) {
+ this(name, types);
+ this.id = id;
+ }
+
+ public int getId() {
+ return id;
+ }
+
+ public void setId(int id) {
+ this.id = id;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public Collection getTypes() {
+ return types;
+ }
+
+ public void setTypes(Collection types) {
+ this.types = types;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == null || obj.getClass() != this.getClass()) {
+ return false;
+ }
+ return id == ((Pokemon) obj).id
+ && Objects.equals(name, ((Pokemon) obj).name)
+ && Objects.deepEquals(types, ((Pokemon) obj).types);
+ }
+
+ @Override
+ public int hashCode() {
+ int result = Objects.hash(id, name);
+ for (Type type : types) {
+ result = 31 * result + type.hashCode();
+ }
+ return result;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("Pokemon {id=");
+ sb.append(id);
+ sb.append(", name=");
+ sb.append(name);
+ sb.append(", types=[");
+ boolean first = true;
+ for (Type type : types) {
+ if (first) {
+ first = false;
+ } else {
+ sb.append(", ");
+ }
+ sb.append(type.toString());
+ }
+ sb.append("]}");
+ return sb.toString();
+ }
+
+}
diff --git a/jpa/eclipselink.jpa.testapps/jpa.test.persistence32/src/main/java/org/eclipse/persistence/testing/models/jpa/persistence32/Type.java b/jpa/eclipselink.jpa.testapps/jpa.test.persistence32/src/main/java/org/eclipse/persistence/testing/models/jpa/persistence32/Type.java
new file mode 100644
index 00000000000..ce11207e001
--- /dev/null
+++ b/jpa/eclipselink.jpa.testapps/jpa.test.persistence32/src/main/java/org/eclipse/persistence/testing/models/jpa/persistence32/Type.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0,
+ * or the Eclipse Distribution License v. 1.0 which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
+ */
+
+package org.eclipse.persistence.testing.models.jpa.persistence32;
+
+import java.util.Objects;
+
+import jakarta.persistence.Entity;
+import jakarta.persistence.Id;
+import jakarta.persistence.NamedQuery;
+import jakarta.persistence.Table;
+
+@Entity
+@Table(name="PERSISTENCE32_TYPE")
+@NamedQuery(name="Type.all", query="SELECT t FROM Type t")
+public class Type {
+
+ @Id
+ private int id;
+
+ private String name;
+
+ public Type() {
+ }
+
+ public Type(int id, String name) {
+ this.id = id;
+ this.name = name;
+ }
+
+ public int getId() {
+ return id;
+ }
+
+ public void setId(int id) {
+ this.id = id;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == null || obj.getClass() != this.getClass()) {
+ return false;
+ }
+ return id == ((Type) obj).id
+ && Objects.equals(name, ((Type) obj).name);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(id, name);
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("Type {id=");
+ sb.append(id);
+ sb.append(", name=");
+ sb.append(name);
+ sb.append("}");
+ return sb.toString();
+ }
+
+}
diff --git a/jpa/eclipselink.jpa.testapps/jpa.test.persistence32/src/main/resources/META-INF/persistence.xml b/jpa/eclipselink.jpa.testapps/jpa.test.persistence32/src/main/resources/META-INF/persistence.xml
new file mode 100644
index 00000000000..bb963cf1140
--- /dev/null
+++ b/jpa/eclipselink.jpa.testapps/jpa.test.persistence32/src/main/resources/META-INF/persistence.xml
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+ org.eclipse.persistence.jpa.PersistenceProvider
+ org.eclipse.persistence.testing.models.jpa.persistence32.Pokemon
+ org.eclipse.persistence.testing.models.jpa.persistence32.Type
+ true
+
+
+
+
+
+
+
+
+
diff --git a/jpa/eclipselink.jpa.testapps/jpa.test.persistence32/src/test/java/org/eclipse/persistence/testing/tests/jpa/persistence32/EntityManagerFactoryTest.java b/jpa/eclipselink.jpa.testapps/jpa.test.persistence32/src/test/java/org/eclipse/persistence/testing/tests/jpa/persistence32/EntityManagerFactoryTest.java
new file mode 100644
index 00000000000..89095c53eb7
--- /dev/null
+++ b/jpa/eclipselink.jpa.testapps/jpa.test.persistence32/src/test/java/org/eclipse/persistence/testing/tests/jpa/persistence32/EntityManagerFactoryTest.java
@@ -0,0 +1,183 @@
+/*
+ * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0,
+ * or the Eclipse Distribution License v. 1.0 which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
+ */
+
+package org.eclipse.persistence.testing.tests.jpa.persistence32;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import jakarta.persistence.EntityManager;
+import jakarta.persistence.EntityTransaction;
+import junit.framework.Test;
+import junit.framework.TestSuite;
+import org.eclipse.persistence.internal.jpa.EntityManagerFactoryImpl;
+import org.eclipse.persistence.jpa.JpaEntityManagerFactory;
+import org.eclipse.persistence.testing.framework.jpa.junit.JUnitTestCase;
+import org.eclipse.persistence.testing.models.jpa.persistence32.Persistence32TableCreator;
+import org.eclipse.persistence.testing.models.jpa.persistence32.Pokemon;
+import org.eclipse.persistence.testing.models.jpa.persistence32.Type;
+
+public class EntityManagerFactoryTest extends JUnitTestCase {
+
+ // Pokemon types. Array index is ID value. Value of ID = 0 does not exist,
+ // so it's array instance is set to null.
+ private static final Type[] TYPES = new Type[] {
+ null,
+ new Type( 1, "Normal"),
+ new Type( 2, "Fighting"),
+ new Type( 3, "Flying"),
+ new Type( 4, "Poison"),
+ new Type( 5, "Ground"),
+ new Type( 6, "Rock"),
+ new Type( 7, "Bug"),
+ new Type( 8, "Ghost"),
+ new Type( 9, "Steel"),
+ new Type(10, "Fire"),
+ new Type(11, "Water"),
+ new Type(12, "Grass"),
+ new Type(13, "Electric"),
+ new Type(14, "Psychic"),
+ new Type(15, "Ice"),
+ new Type(16, "Dragon"),
+ new Type(17, "Dark"),
+ new Type(18, "Fairy")
+ } ;
+
+ private JpaEntityManagerFactory emf = null;
+
+ public static Test suite() {
+ TestSuite suite = new TestSuite();
+ suite.setName("EntityManagerFactoryTest");
+ suite.addTest(new EntityManagerFactoryTest("testSetup"));
+ suite.addTest(new EntityManagerFactoryTest("testWithApplicationManagedTransaction"));
+ suite.addTest(new EntityManagerFactoryTest("testWithApplicationManagedTransactionVoid"));
+ suite.addTest(new EntityManagerFactoryTest("testWithUserManagedTransaction"));
+ suite.addTest(new EntityManagerFactoryTest("testWithUserManagedTransactionVoid"));
+ return suite;
+ }
+
+ public EntityManagerFactoryTest() {}
+
+ public EntityManagerFactoryTest(String name) {
+ super(name);
+ setPuName(getPersistenceUnitName());
+ }
+
+
+ @Override
+ public String getPersistenceUnitName() {
+ return "persistence32";
+ }
+
+ @Override
+ public void setUp () {
+ super.setUp();
+ emf = getEntityManagerFactory(getPersistenceUnitName()).unwrap(EntityManagerFactoryImpl.class);
+ }
+
+ @Override
+ public void tearDown () {
+ super.tearDown();
+ }
+
+ /**
+ * The setup is done as a test, both to record its failure, and to allow
+ * execution in the server.
+ */
+ public void testSetup() {
+ new Persistence32TableCreator().replaceTables(JUnitTestCase.getServerSession(getPersistenceUnitName()));
+ clearCache();
+ // emf = getEntityManagerFactory(getPersistenceUnitName());
+ try (EntityManager em = emf.createEntityManager()) {
+ EntityTransaction et = em.getTransaction();
+ try {
+ et.begin();
+
+ for (int i = 1; i < TYPES.length; i++) {
+ em.persist(TYPES[i]);
+ }
+ et.commit();
+ } catch (Exception e) {
+ et.rollback();
+ }
+ }
+ }
+
+ public void testWithApplicationManagedTransaction() {
+ Pokemon pokemon = emf.withTransaction((em -> {
+ Map types = new HashMap<>(24);
+ em.createNamedQuery("Type.all", Type.class)
+ .getResultList()
+ .forEach(type -> types.put(type.getId(), type));
+ Pokemon newPokemon = new Pokemon("Pidgey", List.of(types.get(1), types.get(3)));
+ em.persist(newPokemon);
+ return newPokemon;
+ }));
+ verifyObjectInEntityManager(pokemon, getPersistenceUnitName());
+ }
+
+ public void testWithApplicationManagedTransactionVoid() {
+ Pokemon[] pokemon = new Pokemon[1];
+ emf.withTransaction((em -> {
+ Map types = new HashMap<>(24);
+ em.createNamedQuery("Type.all", Type.class)
+ .getResultList()
+ .forEach(type -> types.put(type.getId(), type));
+ Pokemon newPokemon = new Pokemon("Beedrill", List.of(types.get(7), types.get(4)));
+ em.persist(newPokemon);
+ pokemon[0] = newPokemon;
+ }));
+ verifyObjectInEntityManager(pokemon[0], getPersistenceUnitName());
+ }
+
+ public void testWithUserManagedTransaction() {
+ Pokemon pokemon = emf.withTransaction(((em, et) -> {
+ try {
+ Map types = new HashMap<>(24);
+ em.createNamedQuery("Type.all", Type.class)
+ .getResultList()
+ .forEach(type -> types.put(type.getId(), type));
+ Pokemon newPokemon = new Pokemon("Caterpie", List.of(types.get(7)));
+ em.persist(newPokemon);
+ et.commit();
+ return newPokemon;
+ } catch (Exception e) {
+ et.rollback();
+ throw e;
+ }
+ }));
+ verifyObjectInEntityManager(pokemon, getPersistenceUnitName());
+ }
+
+
+ public void testWithUserManagedTransactionVoid() {
+ Pokemon[] pokemon = new Pokemon[1];
+ emf.withTransaction(((em, et) -> {
+ try {
+ Map types = new HashMap<>(24);
+ em.createNamedQuery("Type.all", Type.class)
+ .getResultList()
+ .forEach(type -> types.put(type.getId(), type));
+ Pokemon newPokemon = new Pokemon("Squirtle", List.of(types.get(11)));
+ em.persist(newPokemon);
+ et.commit();
+ pokemon[0] = newPokemon;
+ } catch (Exception e) {
+ et.rollback();
+ throw e;
+ }
+ }));
+ verifyObjectInEntityManager(pokemon[0], getPersistenceUnitName());
+ }
+
+}
diff --git a/jpa/eclipselink.jpa.testapps/pom.xml b/jpa/eclipselink.jpa.testapps/pom.xml
index e339e9c83ff..4067f1b4cdd 100644
--- a/jpa/eclipselink.jpa.testapps/pom.xml
+++ b/jpa/eclipselink.jpa.testapps/pom.xml
@@ -324,6 +324,7 @@
jpa.test.partitioned
jpa.test.performance
jpa.test.performance2
+ jpa.test.persistence32
jpa.test.privateowned
jpa.test.pu with spaces
jpa.test.relationships
diff --git a/jpa/org.eclipse.persistence.jpa/src/main/java/org/eclipse/persistence/jpa/JpaEntityManagerFactory.java b/jpa/org.eclipse.persistence.jpa/src/main/java/org/eclipse/persistence/jpa/JpaEntityManagerFactory.java
index 4969912a4f1..f27d4c97261 100644
--- a/jpa/org.eclipse.persistence.jpa/src/main/java/org/eclipse/persistence/jpa/JpaEntityManagerFactory.java
+++ b/jpa/org.eclipse.persistence.jpa/src/main/java/org/eclipse/persistence/jpa/JpaEntityManagerFactory.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2011, 2021 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2011, 2023 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
@@ -16,9 +16,14 @@
// - 494610: Session Properties map should be Map
package org.eclipse.persistence.jpa;
+import jakarta.persistence.EntityManager;
import jakarta.persistence.EntityManagerFactory;
import java.util.Map;
+import java.util.function.BiConsumer;
+import java.util.function.BiFunction;
+import jakarta.persistence.EntityTransaction;
+import jakarta.persistence.PersistenceException;
import org.eclipse.persistence.internal.jpa.EntityManagerFactoryDelegate;
import org.eclipse.persistence.internal.sessions.DatabaseSessionImpl;
import org.eclipse.persistence.sessions.broker.SessionBroker;
@@ -68,5 +73,123 @@ public interface JpaEntityManagerFactory extends EntityManagerFactory, AutoClose
*/
void refreshMetadata(Map properties);
+ /**
+ * Create a new application-managed {@code EntityManager}, start a resource-local
+ * transaction, and call the given function, passing both the {@code EntityManager}
+ * and the {@code EntityTransaction}.
+ * The given function is responsible for finishing the transaction by calling {@code commit}
+ * or {@code rollback} method.
+ *
+ * @param work a function to be called in the scope of the transaction
+ */
+ default void withTransaction(BiConsumer work) {
+ try (EntityManager em = this.createEntityManager()) {
+ EntityTransaction t = em.getTransaction();
+ t.begin();
+ work.accept(em, t);
+ }
+ }
+
+ /**
+ * Create a new application-managed {@code EntityManager}, start a resource-local
+ * transaction, and call the given function, passing both the {@code EntityManager}
+ * and the {@code EntityTransaction}.
+ * The given function is responsible for finishing the transaction by calling {@code commit}
+ * or {@code rollback} method.
+ *
+ * @param work a function to be called in the scope of the transaction
+ * @return the value returned by the given function
+ */
+ default R withTransaction(BiFunction work) {
+ try (EntityManager em = this.createEntityManager()) {
+ EntityTransaction t = em.getTransaction();
+ t.begin();
+ return work.apply(em, t);
+ }
+ }
+
+ /**
+ * Create a new application-managed {@code EntityManager}, start a resource-local
+ * transaction, and call the given function, passing the {@code EntityManager}.
+ * If the given function does not throw an exception, commit the transaction and
+ * return the result of the function. If the function does throw an exception,
+ * roll back the transaction and rethrow the exception. Finally, close the
+ * {@code EntityManager}.
+ *
+ * @param work a function to be called in the scope of the transaction
+ */
+ default void withTransaction(TransactionVoidWork work) {
+ try (EntityManager em = this.createEntityManager()) {
+ EntityTransaction t = em.getTransaction();
+ try {
+ t.begin();
+ work.work(em);
+ t.commit();
+ } catch (Exception e) {
+ t.rollback();
+ throw new PersistenceException("Application-managed transaction failed", e);
+ }
+ }
+ }
+
+ /**
+ * Create a new application-managed {@code EntityManager}, start a resource-local
+ * transaction, and call the given function, passing the {@code EntityManager}.
+ * If the given function does not throw an exception, commit the transaction and
+ * return the result of the function. If the function does throw an exception,
+ * roll back the transaction and rethrow the exception. Finally, close the
+ * {@code EntityManager}.
+ *
+ * @param work a function to be called in the scope of the transaction
+ * @return the value returned by the given function
+ */
+ default R withTransaction(TransactionWork work) {
+ try (EntityManager em = this.createEntityManager()) {
+ EntityTransaction t = em.getTransaction();
+ try {
+ t.begin();
+ R result = work.work(em);
+ t.commit();
+ return result;
+ } catch (Exception e) {
+ t.rollback();
+ throw new PersistenceException("Application-managed transaction failed", e);
+ }
+ }
+ }
+
+ /**
+ * A task that runs in a transaction, returns no result and may throw an exception.
+ * Implementors define a single method with {@link EntityManager} argument called {@code work}.
+ */
+ @FunctionalInterface
+ interface TransactionVoidWork {
+ /**
+ * Executes the function. Throws an exception if unable to do so.
+ *
+ * @param em the application-managed {@link EntityManager} instance
+ * @throws Exception when unable to compute a result
+ */
+ void work(EntityManager em) throws Exception;
+ }
+
+ /**
+ * A task that runs in a transaction, returns result and may throw an exception.
+ * Implementors define a single method with {@link EntityManager} argument called {@code work}.
+ *
+ * @param the result type of method {@code work}
+ */
+ @FunctionalInterface
+ interface TransactionWork {
+ /**
+ * Executes the function. Throws an exception if unable to do so.
+ *
+ * @param em the application-managed {@link EntityManager} instance
+ * @return the computed result
+ * @throws Exception when unable to compute a result
+ */
+ R work(EntityManager em) throws Exception;
+ }
+
}