Java Persistence/Transactions

From Wikibooks, open books for an open world
Jump to navigation Jump to search

Transactions[edit | edit source]

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 to 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[edit | edit source]

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 DataSource, the <non-jta-data-source> element should be used to reference a server DataSource that has been configured to not be JTA managed.

Local JPA transactions are defined through the EntityTransaction class. It contains basic transaction API including begin, commit and rollback.

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 persist, merge, 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 commit the EntityManager can continue to be used, and all of the managed objects remain managed. However it is normally best to close or clear the 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.

The 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[edit | edit source]

<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>acme</non-jta-data-source>
    </persistence-unit>
</persistence>

Example resource local transaction[edit | edit source]

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[edit | edit source]

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 DataSource, the <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 each SessionBean method defaults to a TransactionAttribute TransactionAttributeType.REQUIRED and hence its invocation starts a JTA transaction, if no JTA transaction is in progress. If a method is the first called that requires a transaction, it will commit the changes upon completion.

JTA transaction can be shared among SessionBean methods, hence if a method is called through it's business interface from another method that already started a transaction, the second method will work inside the existing transaction. Moreover the second method can only change the state of the EntityManager by persisting objects or doing other operations, and when it returns control to the calling method it delegates to it the execution of the actual commit. To perform DB operations in a transaction isolated from any transaction in progress a SessionBean method can be annotated as TransactionAttribute TransactionAttributeType.REQUIRES_NEW. The TransactionAttributeTypes that can be associate to SessionBean methods are:

Type Description Exceptions Commit policy
REQUIRED Requires a transaction: if the client invoking the method is already associated with a transactional context, the method is hosted in the client's context, otherwise a new transaction is started None Executed upon completion, if not hosted in another's method transactional context
REQUIRES_NEW Requires a separate transaction: starts a transaction in any case, if the method is called by a client already associated with a transactional context, the existing transaction will be suspended None Executed upon completion
MANDATORY Requires an existing transaction: it requires that the client invoking the method is already associated with a transactional context, and the method is hosted in the client's context An exception is thrown if a client invokes the method while not associated with a transaction Delegated to the hosting context
NOT_SUPPORTED Does not use any transaction: no transaction is started, if the client invoking the method is already associated with a transactional context, the existing transaction will be suspended none directly. An exception is thrown if inside the method and operation like persist() is attempted Not done
NEVER Cannot execute if a transaction is in place: the client is required to call outside of any transactional context An exception is thrown if the client invoking the method is associated with a transactional context. An exception is thrown if inside the method and operation like persist() is attempted Not done
SUPPORTS Does not require a transaction, but if the client invoking the method is already associated with a transactional context the method will execute in the transaction context None directly. An exception is thrown if inside the method and operation like persist() is attempted and the client invoking the method is not in already associated with a transactional context Not required

UserTransaction can be obtained through a JNDI lookup in most application servers, or from the EJBContext in EJB 2.0 style SessionBeans.

JTA transactions can be used in two modes in Java EE. In Java EE managed mode, such as an EntityManager injected into a SessionBean, the 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 EntityManager.

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 EntityManager.joinTransaction()

Example JTA transaction persistence.xml[edit | edit source]

<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[edit | edit source]

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();

Advanced[edit | edit source]

Join Transaction[edit | edit source]

The 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 an 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 merge or remove until you are ready to commit the transaction.

joinTransaction is only used with JTA managed EntityManagers (JTA transaction-type in persistence.xml). For RESOURCE_LOCAL EntityManagers you can just commit the JPA transaction whenever you desire.

Example joinTransaction usage[edit | edit source]

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[edit | edit source]

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 EntityManager was 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.

Nested Transactions[edit | edit source]

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.

Transaction Isolation[edit | edit source]