- 1 Transactions
- 2 Advanced
A transaction is a set of operations that either fail or succeed as a unit. Transactions are a fundamental part of persistence. A database transaction consists of a set of SQL DML (Data Manipulation Language) operations that are committed or rolled back as a single unit. An object level transaction is one in which a set of changes made to a set of objects are committed to the database as a single unit.
JPA provides two mechanisms for transactions. When used in Java EE JPA provides integration with JTA (Java Transaction API). JPA also provides its own
EntityTransaction implementation for Java SE and for use in a non-managed mode in Java EE. Transactions in JPA are always at the object level, this means that all changes made to all persistent objects in the persistence context are part of the transaction.
Resource Local Transactions
Resource local transactions are used in JSE, or in application managed (non-managed) mode in Java EE. To use resource local transactions the
transaction-type attribute in the
persistence.xml is set to
RESOURCE_LOCAL. If resource local transactions are used with a
<non-jta-data-source> element should be used to reference a server
DataSource that has been configure to not be JTA managed.
Local JPA transactions are defined through the
EntityTransaction class. It contains basic transaction API including
Technically in JPA the
EntityManager is in a transaction from the point it is created. So
begin is somewhat redundant. Until
begin is called, certain operations such as
remove cannot be called. Queries can still be performed, and objects that were queried can be changed, although this is somewhat unspecified what will happen to these changes in the JPA spec, normally they will be committed, however it is best to call
begin before making any changes to your objects. Normally it is best to create a new
EntityManager for each transaction to avoid have stale objects remaining in the persistence context, and to allow previously managed objects to garbage collect.
After a successful
EntityManager can continue to be used, and all of the managed objects remain managed. However it is normally best to
EntityManager to allow garbage collection and avoid stale data. If the commit fails, then the managed objects are considered detached, and the
EntityManager is cleared. This means that commit failures cannot be caught and retried, if a failure occurs, the entire transaction must be performed again. The previously managed object may also be left in an inconsistent state, meaning some of the objects locking version may have been incremented. Commit will also fail if the transaction has been marked for rollback. This can occur either explicitly by calling
setRollbackOnly or is required to be set if any query or find operation fails. This can be an issue, as some queries may fail, but may not be desired to cause the entire transaction to be rolled back.
rollback operation will rollback the database transaction only. The managed objects in the persistence context will become detached and the
EntityManager is cleared. This means any object previously read, should no longer be used, and is no longer part of the persistence context. The changes made to the objects will be left as is, the object changes will not be reverted.
Example resource local transaction persistence.xml
<persistence xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence persistence_1_0.xsd" version="1.0"> <persistence-unit name="acme" transaction-type="RESOURCE_LOCAL"> <non-jta-data-source>amce</non-jta-data-source> </persistence-unit> </persistence>
Example resource local transaction
EntityManager em = createEntityManager(); em.getTransaction().begin(); Employee employee = em.find(Employee.class, id); employee.setSalary(employee.getSalary() + 1000); em.getTransaction().commit(); em.close();
JTA transactions are used in Java EE, in managed mode (EJB). To use JTA transactions the
transaction-type attribute in the
persistence.xml is set to
JTA. If JTA transactions are used with a
<jta-datasource> element should be used to reference a server
DataSource that has been configure to be JTA managed.
JTA transactions are defined through the JTA
UserTransaction class, or more likely implicitly defined through
SessionBean usage/methods. In a
SessionBean normally each
SessionBean method invocation defines a JTA transaction.
UserTransaction can be obtained through a JNDI lookup in most application servers, or from the
EJBContext in EJB 2.0 style
JTA transactions can be used in two modes in Java EE. In Java EE managed mode, such as an
EntityManager injected into a
EntityManager reference, represents a new persistence context for each transaction. This means objects read in one transaction become detached after the end of the transaction, and should no longer be used, or need to be merged into the next transaction. In managed mode, you never create or close an
The second mode allows the
EntityManager to be application managed, (normally obtained from an injected
EntityManagerFactory, or directly from JPA
Persistence). This allows the persistence context to survive transaction boundaries, and follow the normal
EntityManager life-cycle similar to resource local. If the
EntityManager is created in the context of an active JTA transaction, it will automatically be part of the JTA transaction and commit/rollback with the JTA transaction. Otherwise it must join a JTA transaction to commit/rollback using
Example JTA transaction persistence.xml
<persistence xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence persistence_1_0.xsd" version="1.0"> <persistence-unit name="acme" transaction-type="JTA"> <jta-data-source>acme</jta-data-source> </persistence-unit> </persistence>
Example JTA transaction
UserTransaction transaction = (UserTransaction)new InitialContext().lookup("java:comp/UserTransaction"); transaction.begin(); EntityManager em = getEntityManager(); Employee employee = em.find(Employee.class, id); employee.setSalary(employee.getSalary() + 1000); transaction.commit();
EntityManager.joinTransaction() API allows an application managed
EntityManager to join the active JTA transaction context. This allows an
EntityManager to be created outside the JTA transaction scope and commit its changes as part of the current transaction. This is normally used with a
stateful SessionBean, or with a JSP or Servlet where an
EXTENDED EntityManager is used in a stateful architecture. A stateful architecture is one where the server stores information on a client connection until the client's session is over, it differs from a stateless architecture where nothing is stored on the server in between client requests (each request is processed on its own).
There are pros and cons with both stateful and stateless architectures. One of the advantages with using a stateful architecture and and
EXTENDED EntityManager, is that you do not have to worry about merging objects. You can read your objects in one request, let the client modify them, and then commit them as part of a new transaction. This is where
joinTransaction would be used. One issue with this is that you normally want to create the
EntityManager when there is no active JTA transaction, otherwise it will commit as part of that transaction. However, even if it does commit, you can still continue to use it and join a future transaction. You do have to avoid using transactional API such as
remove until you are ready to commit the transaction.
joinTransaction is only used with JTA managed
EntityManagers (JTA transaction-type in persistence.xml). For
EntityManagers you can just commit the JPA transaction whenever you desire.
Example joinTransaction usage
EntityManager em = getEntityManagerFactory().createEntityManager(); Employee employee = em.find(Employee.class, id); employee.setSalary(employee.getSalary() + 1000); UserTransaction transaction = (UserTransaction)new InitialContext().lookup("java:comp/UserTransaction"); transaction.begin(); em.joinTransaction(); transaction.commit();
Retrying Transactions, Handling Commit Failures
Sometimes it is desirable to handle persistence errors, and recover and retry transactions. This normally requires a lot of application knowledge to know what failed, what state the system is in, and how to fix it.
Unfortunately JPA does not provide a direct way of handling commit failures or error handling. When a transaction commit fails, the transaction is automatically rolled back, and the persistence context cleared, and all managed objects detached. Not only is there no way to handle a commit failure, but if any error occurs in an query before the commit, the transaction will be marked for rollback, so there is no real way to handle any error. This is because any query could potentially change the state of the database, so JPA does not know if the database is in an invalid or inconsistent state so must rollback the transaction. As well if the commit fails, the state of the objects registered in the persistence context may also be inconsistent (such as partially committed objects having their optimistic lock versions incremented), so the persistence context is cleared to avoid further errors.
Some JPA providers may provide extended API to allow handling commit failures, or handling errors on queries.
There are some methods to generically handle commit failures and other errors. Error handling in general is normally easier when using
RESOURCE_LOCAL transactions, and not when using JTA transactions.
One method of error handling is to call
merge for each managed object after the commit fails into a new
EntityManager, then try to commit the new
EntityManager. One issue may be that any ids that were assigned, or optimistic lock versions that were assigned or incremented may need to be reset. Also, if the original
EXTENDED, any objects that were in use would still become detached, and need to be reset.
Another more involved method to error handling is to always work with a non-transactional
EntityManager. When it's time to commit, a new
EntityManager is created, the non-transactional objects are merged into it, and the new
EntityManager is committed. If the commit fails, only the state of the new
EntityManager may be inconsistent, the original
EntityManager will be unaffected. This can allow the problem to be corrected, and the
EntityManager re-merged into another new
EntityManager. If the commit is successful any commit changes can be merged back into the original
EntityManager, which can then continue to be used as normal. This solution requires a fair bit of overhead, so should only be used if error handling is really required, and the JPA provider provides no alternatives.
JPA and JTA do not support nested transactions.
A nested transaction is used to provide a transactional guarantee for a subset of operations performed within the scope of a larger transaction. Doing this allows you to commit and abort the subset of operations independently of the larger transaction.
The rules to the usage of a nested transaction are as follows:
While the nested (child) transaction is active, the parent transaction may not perform any operations other than to commit or abort, or to create more child transactions.
Committing a nested transaction has no effect on the state of the parent transaction. The parent transaction is still uncommitted. However, the parent transaction can now see any modifications made by the child transaction. Those modifications, of course, are still hidden to all other transactions until the parent also commits.
Likewise, aborting the nested transaction has no effect on the state of the parent transaction. The only result of the abort is that neither the parent nor any other transactions will see any of the database modifications performed under the protection of the nested transaction.
If the parent transaction commits or aborts while it has active children, the child transactions are resolved in the same way as the parent. That is, if the parent aborts, then the child transactions abort as well. If the parent commits, then whatever modifications have been performed by the child transactions are also committed.
The locks held by a nested transaction are not released when that transaction commits. Rather, they are now held by the parent transaction until such a time as that parent commits.
Any database modifications performed by the nested transaction are not visible outside of the larger encompassing transaction until such a time as that parent transaction is committed.
The depth of the nesting that you can achieve with nested transaction is limited only by memory.