diff --git a/api/src/main/java/jakarta/persistence/EntityManagerFactory.java b/api/src/main/java/jakarta/persistence/EntityManagerFactory.java index faf7f36b..0cc74156 100644 --- a/api/src/main/java/jakarta/persistence/EntityManagerFactory.java +++ b/api/src/main/java/jakarta/persistence/EntityManagerFactory.java @@ -11,12 +11,16 @@ */ // Contributors: +// Gavin King - 3.2 // Linda DeMichiel - 2.1 // Linda DeMichiel - 2.0 package jakarta.persistence; import java.util.Map; +import java.util.function.Consumer; +import java.util.function.Function; + import jakarta.persistence.metamodel.Metamodel; import jakarta.persistence.criteria.CriteriaBuilder; @@ -221,4 +225,54 @@ public interface EntityManagerFactory extends AutoCloseable { */ public void addNamedEntityGraph(String graphName, EntityGraph entityGraph); + /** + * Create a new application-managed {@code EntityManager} with an active + * transaction, and execute the given function, passing the {@code EntityManager} + * to the function. + *

+ * If the transaction type of the persistence unit is JTA, and there is a JTA + * transaction already associated with the caller, then the {@code EntityManager} + * is associated with this current transaction. If the given function throws an + * exception, the JTA transaction is marked for rollback, and the exception is + * rethrown. + *

+ * Otherwise, if the transaction type of the persistence unit is resource-local, + * or if there is no JTA transaction already associated with the caller, then + * the {@code EntityManager} is associated with a new transaction. If the given + * function returns without throwing an exception, this transaction is committed. + * If the function does throw an exception, the transaction is rolled back, and + * the exception is rethrown. + *

+ * Finally, the {@code EntityManager} is closed before this method returns + * control to the client. + * + * @param work a function to be executed in the scope of the transaction + */ + public void runInTransaction(Consumer work); + /** + * Create a new application-managed {@code EntityManager} with an active + * transaction, and call the given function, passing the {@code EntityManager} + * to the function. + *

+ * If the transaction type of the persistence unit is JTA, and there is a JTA + * transaction already associated with the caller, then the {@code EntityManager} + * is associated with this current transaction. If the given function returns + * without throwing an exception, the result of the function is returned. If the + * given function throws an exception, the JTA transaction is marked for rollback, + * and the exception is rethrown. + *

+ * Otherwise, if the transaction type of the persistence unit is resource-local, + * or if there is no JTA transaction already associated with the caller, then + * the {@code EntityManager} is associated with a new transaction. If the given + * function returns without throwing an exception, this transaction is committed + * and the result of the function is returned. If the function does throw an + * exception, the transaction is rolled back, and the exception is rethrown. + *

+ * Finally, the {@code EntityManager} is closed before this method returns + * control to the client. + * + * @param work a function to be called in the scope of the transaction + * @return the value returned by the given function + */ + public R callInTransaction(Function work); } diff --git a/spec/src/main/asciidoc/appendixes.adoc b/spec/src/main/asciidoc/appendixes.adoc index 97d1c276..f2f2f5fe 100644 --- a/spec/src/main/asciidoc/appendixes.adoc +++ b/spec/src/main/asciidoc/appendixes.adoc @@ -105,3 +105,5 @@ Clarified `SqlResultSetMapping` with multiple ``EntityResult``s and conflicting === Jakarta Persistence 3.2 Added `||` string concatenation operator + +Added _runInTransaction()_ and _callInTransaction()_ to _EntityManagerFactory_ diff --git a/spec/src/main/asciidoc/ch07-entitymanagers-and-persistence-contexts.adoc b/spec/src/main/asciidoc/ch07-entitymanagers-and-persistence-contexts.adoc index 640b5dfd..a4011730 100644 --- a/spec/src/main/asciidoc/ch07-entitymanagers-and-persistence-contexts.adoc +++ b/spec/src/main/asciidoc/ch07-entitymanagers-and-persistence-contexts.adoc @@ -412,6 +412,56 @@ public interface EntityManagerFactory extends AutoCloseable { */ public void addNamedEntityGraph(String graphName, EntityGraph entityGraph); + /** + * Create a new application-managed {@code EntityManager} with an active + * transaction, and execute the given function, passing the {@code EntityManager} + * to the function. + *

+ * If the transaction type of the persistence unit is JTA, and there is a JTA + * transaction already associated with the caller, then the {@code EntityManager} + * is associated with this current transaction. If the given function throws an + * exception, the JTA transaction is marked for rollback, and the exception is + * rethrown. + *

+ * Otherwise, if the transaction type of the persistence unit is resource-local, + * or if there is no JTA transaction already associated with the caller, then + * the {@code EntityManager} is associated with a new transaction. If the given + * function returns without throwing an exception, this transaction is committed. + * If the function does throw an exception, the transaction is rolled back, and + * the exception is rethrown. + *

+ * Finally, the {@code EntityManager} is closed before this method returns + * control to the client. + * + * @param work a function to be executed in the scope of the transaction + */ + public void runInTransaction(Consumer work); + /** + * Create a new application-managed {@code EntityManager} with an active + * transaction, and call the given function, passing the {@code EntityManager} + * to the function. + *

+ * If the transaction type of the persistence unit is JTA, and there is a JTA + * transaction already associated with the caller, then the {@code EntityManager} + * is associated with this current transaction. If the given function returns + * without throwing an exception, the result of the function is returned. If the + * given function throws an exception, the JTA transaction is marked for rollback, + * and the exception is rethrown. + *

+ * Otherwise, if the transaction type of the persistence unit is resource-local, + * or if there is no JTA transaction already associated with the caller, then + * the {@code EntityManager} is associated with a new transaction. If the given + * function returns without throwing an exception, this transaction is committed + * and the result of the function is returned. If the function does throw an + * exception, the transaction is rolled back, and the exception is rethrown. + *

+ * Finally, the {@code EntityManager} is closed before this method returns + * control to the client. + * + * @param work a function to be called in the scope of the transaction + * @return the value returned by the given function + */ + public R callInTransaction(Function work); } ---- @@ -577,14 +627,13 @@ public class PasswordChanger { EntityManager em = emf.createEntityManager(); em.getTransaction().begin(); - User user = (User)em.createQuery - ("SELECT u FROM User u WHERE u.name=:name AND u.pass=:pass") + User user = em.createQuery + ("SELECT u FROM User u WHERE u.name=:name AND u.pass=:pass", User.class) .setParameter("name", args[0]) .setParameter("pass", args[1]) .getSingleResult(); - if (user!=null) - user.setPassword(args[2]); + user.setPassword(args[2]); em.getTransaction().commit(); em.close(); @@ -593,6 +642,60 @@ public class PasswordChanger { } ---- +=== The runInTransaction and callInTransaction methods + +The _runInTransaction_ and _callInTransaction_ methods of the +_EntityManagerFactory_ provide a shortcut for persistence context +and transaction management with an application-managed _EntityManager_. + +[source,java] +---- +entityManagerFactory.runInTransaction(entityManager -> { + User user = em.createQuery + ("SELECT u FROM User u WHERE u.name=:name AND u.pass=:pass", User.class) + .setParameter("name", args[0]) + .setParameter("pass", args[1]) + .getSingleResult(); + + user.setPassword(args[2]); +}) +---- + +The argument function passed to _runInTransaction_ or +_callInTransaction_ must be called and passed a new instance of +_EntityManager_. When the argument function returns or throws an +exception, this _EntityManager_ must be closed before _runInTransaction_ +or _callInTransaction_ returns. + +The argument function is executed in the context of a transaction +associated with this new _EntityManager_. + +- If the transaction type of the persistence unit is JTA, and there + is a JTA transaction already associated with the caller, then the + _EntityManager_ is associated with this current transaction. If the + argument function throws an exception, the JTA transaction must be + marked for rollback, and the exception must be rethrown by + _runInTransaction_ or _callInTransaction_. Otherwise, + _callInTransaction_ must return the same value returned by the + argument function. + +- Otherwise, if the transaction type of the persistence unit is + resource-local, or if there is no JTA transaction already associated + with the caller, then the _EntityManager_ is associated with a new + transaction. If the argument function throws an exception, this + transaction must be rolled back, and then the exception must be + rethrown by _runInTransaction_ or _callInTransaction_. If the argument + function returns, then _runInTransaction_ or _callInTransaction_ must + attempt to commit the transaction. If the attempt to commit the + transaction fails, the exception must be rethrown. Otherwise, + _callInTransaction_ must return the same value returned by the argument + function. + +The application should not attempt to manage the lifecycle of the +transaction or _EntityManager_ directly. If the application calls an +operation of _EntityTransaction_ from within a call to _runInTransaction_ +or _callInTransaction_, the behavior is undefined. + === Container-managed Persistence Contexts [[a11791]] When a container-managed entity manager is