Handling transactions manually

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.

Getting ready

The steps used to handle transactions manually include:

  1. Using the @TransactionManagement annotation to specify bean-managed transactions
  2. Injecting an instance of the UserTransaction object
  3. Enclosing the transaction code using the begin and commit methods

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

How to do it...

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

How it works...

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's more...

There are two other issues that need to be addressed:

  • General transaction restrictions
  • Using the getStatus method

General transaction restrictions

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.

Using the getStatus Method

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

See also

The Handling transactions the easy way recipe explains how to use CMTs.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.143.203.96