Handling a transaction manually provides the developer with more control over the transaction, but requires more work. This type of transaction control is called a BMT.
It is possible to begin and start a transaction anywhere within a method. The boundaries of a transaction are explicitly set using the begin
and commit
transaction methods. For CMT the boundaries of a transaction are effectively the method. BMTs are only possible for session- and message-driven beans. They cannot be used for entities.
The steps used to handle transactions manually include:
UserTransaction
object begin
and commit
methodsWe specify that an EJB uses BMT by using the @TransactionManagement annotation and setting its TransactionManagementType element to BEAN.
@TransactionManagement(TransactionManagementType.BEAN)
Within methods using BMT, an instance of a UserTransaction
object is needed. This class possesses the methods used to control a transaction. The class can be injected as an instance variable of the class.
// Injects UserTransaction @Resource private UserTransaction userTransaction;
Next, the transaction operations are enclosed in a code sequence starting with the UserTransaction's begin
method and ending with the commit
method. Since any transaction may encounter problems requiring the transaction to be rolled backed, the setRollbackOnly
method can be used in one or more places to affect the rollback. This method is frequently found in catch blocks. The basic structure of the process is illustrated below.
try { // Starts the transaction userTransaction.begin; ... // Commits the transaction userTransaction.commit(); } catch (FirstException fe ) { userTransaction.setRollbackOnly(); } catch (SecondException se ) { userTransaction.setRollbackOnly(); }
It is not possible for one EJB to support both CMT and BMT. If the @TransactionManagement annotation is not used, the EJB will default to CMT.
To illustrate BMT using the PopulationApplication
, we will add another class to the packt
package called BeanManagedPopulationManager
. This class will use essentially the same changePopulation
methods as used in the CityFacade
class.
Once you have created the BeanManagedPopulationManager
stateful session bean, add the @TransactionManagement annotation for BMT. Use dependency injection to provide access to a UserTransaction
object and an EntityManager
object.
@Stateful @TransactionManagement(TransactionManagementType.BEAN) public class BeanManagedPopulationManager { @Resource private UserTransaction userTransaction; @PersistenceContext(unitName = "PopulationApplication-ejbPU") private EntityManager em;
The persistence unit name, PopulationApplication-ejbPU, was created when the entity was defined. Next, add a changePopulation
method which is passed the name of the city and a population count to add to its current population. In a try block, add code for beginning and committing a transaction where a JPQL UPDATE command is used to change the population of a city. Use println
methods to show the execution of the method.
public void changePopulation(String cityName, long count) { try { System.out.println("Executing changePopulation"); userTransaction.begin(); Query query = em.createQuery( "UPDATE City c " + "SET c.population = c.population+:count " + "WHERE c.name = :cityName"); query.setParameter("count", count); query.setParameter("cityName", cityName); int result = query.executeUpdate(); userTransaction.commit(); } catch (Exception e) { e.printStackTrace(); } } }
In this example, we don't use the return value from the executeUpdate
method. Other println
statements can be used to clarify the operation of the method.
To illustrate the use of this method, we will modify the PopulationServlet
. First, inject an instance of the BeanManagedPopulationManager
. Failure to use injection will result in errors when trying to inject resources in the changePopulation
method.
@EJB BeanManagedPopulationManager bean;
Replace the body of the try block in the processRequest
method with the following statements:
clearTables(); bean.changePopulation("Tokyo", 1000); List<City> cities = cityFacade.findAll(); out.println("<html>"); out.println("<head>"); out.println("<title>Servlet PopulationServlet</title>"); out.println("</head>"); out.println("<body>"); for (City c : cities) { out.println("<h5>Rio: " + c.getName() + " - " + c.getPopulation() + "</h5>"); } out.println("</body>"); out.println("</html>");
Execute the servlet. Notice the population has increased and the output of the println
method does not show the use of CMT for the query execution.
...
INFO: Executing changePopulation
INFO: CityFacade afterBegin
INFO: --- AbstractFacade findAll - _CityFacade_Serializable
INFO: CityFacade beforeCompletion
INFO: CityFacade afterCompletion
The begin
method specified the start of a transaction. All operations that constituted the transaction were included between this method and the commit
method. The CityFacade
output lines reflect the use of the SessionSynchronization
methods that are discussed in the Using the SessionSynchronization interface with session beans recipe. They reflected the execution sequence of the transaction. The JPQL UPDATE
command used a named query as discussed in Chapter 5, Using a Named query recipe.
There are two other issues that need to be addressed:
Transactions are supported for either JDBC or JTA transactions. JTA transactions can work with multiple databases whereas a particular JDBC transaction manager may not work with multiple databases. JTA does not support nested transactions.
A transaction can be maintained across multiple client calls using session beans. When using a transaction with message-driven beans, the transaction must commit or rollback before the method returns.
Do not use the EJBContext methods, getRollbackOnly
and setRollbackOnly
from BMTs. These methods are only valid for CMTs. Instead, use the getStatus
and rollback methods of the UserTransaction
class.
The UserTransaction
class possesses a getStatus
method which returns the status of a transaction. To demonstrate the use of this method, create a getTransactionStateString
method. This method returns a string based on an integer value passed to it. This value corresponds to the values found in the javax.transaction.Status
interface and explains the meaning of the status values.
private String getTransactionStateString (int state) { switch (state) { case Status.STATUS_ACTIVE: return "STATUS_ACTIVE: The transaction is active"; case Status.STATUS_COMMITTED: return "STATUS_COMMITTED: The transaction has been committed"; case Status.STATUS_COMMITTING: return "STATUS_COMMITTING: The transaction is being committed"; case Status.STATUS_MARKED_ROLLBACK: return "STATUS_MARKED_ROLLBACK: The transaction is marked for rollback"; case Status.STATUS_NO_TRANSACTION: return "STATUS_NO_TRANSACTION: There is not transaction"; case Status.STATUS_PREPARED: return "STATUS_PREPARED: The transaction is in a prepared state, ready to commit"; case Status.STATUS_PREPARING: return "STATUS_PREPARING: The transaction is preparing to commit"; case Status.STATUS_ROLLEDBACK: return "STATUS_ROLLEDBACK: The transaction has been rollbacked"; case Status.STATUS_ROLLING_BACK: return "STATUS_ROLLING_BACK: The transaction is being rollbacked"; case Status.STATUS_UNKNOWN: return "STATUS_UNKNOWN: The transaction is in a unknown state"; default: return "Status is not available"; } }
Next, augment the changePopulation
method to use the getTransactionStateString
to display the status of the transaction at various points in the method.
public void changePopulation(String cityName, long count) { try { System.out.println("Executing changePopulation"); System.out.println("Transaction State: " + getTransactionStateString (userTransaction.getStatus())); userTransaction.begin(); System.out.println("Transaction State: " + getTransactionStateString (userTransaction.getStatus())); Query query = em.createQuery( "UPDATE City c " + "SET c.population = c.population+:count " + "WHERE c.name = :cityName"); query.setParameter("count", count); query.setParameter("cityName", cityName); int result = query.executeUpdate(); userTransaction.commit(); System.out.println("Transaction State: " + getTransactionStateString (userTransaction.getStatus())); System.out.println("result: " + result); System.out.println("--- end changePopulation"); } catch (Exception e) { e.printStackTrace(); } }
Execute the PopulationServlet
. Its output should contain:
...
INFO: Executing changePopulation
INFO: Transaction State: STATUS_NO_TRANSACTION: There is no transaction
INFO: Transaction State: STATUS_ACTIVE: The transaction is active
INFO: Transaction State: STATUS_NO_TRANSACTION: There is no transaction
3.143.203.96