Java Persistence/Transactions

From Wikibooks, open books for an open world
< Java Persistence
Jump to: navigation, search

Transactions[edit]

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 JEE JPA provides integration with JTA (Java Transaction API). JPA also provides its own EntityTransaction implementation for JSE and for use in a non-managed mode in JEE. 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]

Resource local transactions are used in JSE, or in application managed (non-managed) mode in JEE. 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 configure 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]

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

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]

JTA transactions are used in JEE, 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 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 SessionBeans.

JTA transactions can be used in two modes in JEE. In JEE 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]

<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]

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]

Join Transaction[edit]

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 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 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]

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]

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]

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]