JTA provides transaction support to EJB and now in Java EE 7 to CDI managed beans annotated with @Transactional
. The EJB specification provides declarative services to session and singleton beans. In the EJB container, the transactions are either
Container-Managed Transactions (CMT) or Bean-Managed Transactions (BMT).
Container-Managed Transactions are normally annotated on EJBs with the declaration @javax.ejb.TransactionAttribute
. This annotation can be applied to the type or to individual methods. It accepts a @javax.ejb.TransactionAttributeType
enumeration value.
For CDI managed beans the JTA 1.2 specification provides a brand new annotation @javax.transactional.Transactional
, to elevate ordinary POJOs into the transactional instances. The annotation accepts a nested enumerated class @javax.transactional.Transactional.TxType
as a value.
Transaction demarcation in the older J2EE specification was outlined in an XML deployment descriptor (/META-INF/ejb-jar.xml
) as part of the EJB module. It is still possible to override annotated EJBs with customized transaction service and using XML.
We now outline the CMT services available in Java EE 7 in the following table:
CDI managed beans that take part in CMT rely on the existence of a dedicated interceptor that lies behind the scenes handling the transactional services and communicates with JTA and Java Transaction Services (JTS). This implementation-defined interceptor is provided by Java EE 7 product.
Container-managed EntityManager
instances must be JTA types so that they can join transactions.
Bean-managed transactions are types of transactions where the application EJB or CDI managed bean takes control of transaction management itself. First of all, UserTransaction
is injected into the bean or it is retrieved by dependency lookup through JNDI.
For EJB that are using legacy J2EE constraint that you are upgrading to Java EE 7, it may help to inject javax.ejb.EJBContext
as a resource. Working with the BMT is a matter of working with the UserTransaction
object. Ensure that you demarcate the transaction boundaries accordingly with the begin()
call. At the end of the transaction, either you call
commit()
for normal termination or call
rollback()
for abnormal circumstances.
Here is a stateless session bean that demonstrates how to apply the transaction services:
@Stateless @TransactionManagement(TransactionManagementType.BEAN) public class InvoiceServiceBMT { @Resource EJBContext context; public Invoice saveInvoice( Invoice invoice ) throws Exception { UserTransaction tx = context.getUserTransaction(); try { tx.begin(); em.persist(invoice); tx.commit(); } catch (Exception e) { tx.rollback(); } return invoice; } /* ... */ }
To achieve BMT, we declare the EJB InvoiceServiceBMT
with the @TransactionManagement
annotation with the value BEAN
to override the default value CONTAINER
. Inside the method, we explicitly create a new transaction context and execute the necessary business logic.
We can also achieve a halfway house solution, where the EJB is still known to the container, but it creates its own transaction context. Here is an alternative implementation:
@Stateless @TransactionAttribute(TransactionAttributeType.SUPPORTS) public class InvoiceServiceBMTAlt { @Resource EJBContext context; public Invoice saveInvoice( Invoice invoice ) throws Exception { /* same impl */ } /* ... */ }
We set transaction context to SUPPORTS
, because it is immaterial whether this InvoiceServiceBMTAlt
has an existing transaction context or not before the saveInvoice()
target method is called. Inside the method, we create a new transaction context. So effectively this BMT bean is creating its own REQUIRES_NEW
feature.
The same technique can be achieved with CDI managed beans by injecting the @UserTransaction
object instance. CDI managed beans, by default, do not take part in EJB transactions, and therefore @TransactionManagement
does not apply to them.
Bean-managed EntityManager
instances must be either JTA or non-JTA types. If they are of the latter type then they cannot participate in JTA transactions.
Isolation levels are an important part of transaction management, because they allows administrators to configure interference between concurrency operations in the enterprise application. Transaction isolation affects the overall consistency. There are different levels of isolation, namely: dirty reads, non-repeatable reads, phantom reads, and serializable. To understand these levels, one needs at least two concurrent transactions: TX1 and TX2.
A dirty read occurs when a transaction TX1 reads uncommitted changes made by another concurrency transaction TX2. The situation is especially unpalatable when TX2 roll backs it transaction, which means that TX1 reads something it shouldn't have.
A non-repeatable read occurs when a transaction TX1 reads shared data, at least twice, in order of sequence for time intervals: t1 and t3. Where t1 happens before t2, which happens before t3. The other concurrent transaction TX2 meanwhile updates shared data at time interval t2 and then commits, thereby invalidating TX1 second read at time interval t3.
A phantom read occurs when a transaction TX1 reads shared data at least twice in order of sequence time intervals t1 and t3 just like an unrepeatable read. Except this time, the number of rows read by TX at t1 is [A1] and at t3 they are [A1, A2]. Meanwhile, the other concurrent transaction TX2 inserts a new row of data [A2] at time interval t2 and commits, which causes the ghostly apparition: a phantom record.
When two transactions like TX1 and TX2 are sequentially processed, one after the other, such that there is no such of interference, their concurrency operations are then serializable.
JDBC provides four transaction isolation levels: Read Uncommited, Read Committed, Repeatable Read, and Serializable. Depending on the level chosen they will eliminate the issues around dirty reads, non-repeatable reads, and phantom reads.
The following diagram describes the various isolation levels:
JDBC allows a Java application to query the isolation level of the database through the java.sql.Connection
and the
getTransactionIsolation()
method. An application can also call
setTransactionIsolation()
to set up a new isolation, say from TRANSACTION_READ_COMMITTED
to TRANSACTION_SERIALIZABLE
as long as the database server and the JDBC driver can support it.
For a Java SE application this is acceptable, however, for Java EE application the data sources and entity manager configurations are generally shared across EJB and web modules. Changing the resource dependencies programmatically can lead to trouble for shared applications and modules. Java EE 7 products typically furnish an administration console, which allow a data source's isolation levels to be configured at deployment.
Here is a summary of the isolation levels and their impact on consistency:
Isolation Level |
Dirty Reads |
Non-Repeatable Reads |
Phantom Reads |
---|---|---|---|
READ_UNCOMMITED |
Can occur |
Can occur | |
READ_COMMITED |
Can occur |
Can occur | |
REPEATABLE_READ |
Prevented |
Can occur | |
SERIALIZABLE |
Prevented |
Prevented |
Generally, for Java EE 7 applications that inject EntityManagers
and DataSource
instance, it may be unwise to configure isolation level programmatically.
Nevertheless, there are some techniques to show and here is one that unwraps the JDBC connection behind EntityManager
:
@Stateless public class ReceiptGenerator { @PersistenceContext("mastersAtWorkDB") EntityManager em; public void generateReceipt() { em.flush(); Connection conx = em.unwrap(Connection.class); int savedLevel = conx.getTransactionLevel(); conx.setTransactionLevel(Connection.TRANSACTION_SERIALIZABLE); doMoreWork(); em.flush(); conx.setTransactionLevel(savedLevel); /* ... */ } /* ... */ }
The entity manager's unwrap method allows the application to gain access to the vendor provider classes for the JPA implementation. In the example, we only want access to the JDBC connection. Given vendor provider like Hibernate JPA, we could reveal EntityManager
and retrieve Hibernate's session object instance using the unwrap facility. There are inherent dangers to changing isolation levels in mid flow with concurrency transaction operations, because we are reliant on the JPA provider's synchronization pending instances to the database at the time of the flush()
calls.
In summary, JPA in the form of Java EE 7 does not support custom isolation levels. However, different vendors of JPA providers and Java EE 7 products do provide extensions, which may be configured through an additional XML deployment descriptor and annotations. The buck stops with the underlying database server and isolation levels that they actually do support. For example, if you use Oracle and attempt to configure
REPEATABLE_READ
, it will downgrade the isolation level to READ_COMMITTED
instead (http://docs.oracle.com/cd/B12037_01/server.101/b10743/consist.htm#i5702).
Developers can always perform a dependency lookup of the current transaction context. The UserTransaction
instance inside a Java EE application is as follows:
InitialContext jndiContext = new InitialContext(); UserTransaction tx = jndiContext.lookup( "java:comp/UserTransaction")
The preceding example of code is found commonly in legacy J2EE applications based on JDK 1.4 and predates Java annotations.
This concludes the section on Java EE 7 transactions.
3.145.202.27