Monday, July 3, 2017

JavaEE: Container Managed Transaction Boundary

On a previous post, I talked about a common transaction problem and a way to prevent it by controlling concurrent access to Entity data with a locking mechanism.  On this post, let's talk about container managed transaction boundary within the JavaEE.

In a Java EE application, a transaction is a series of actions that must all complete successfully, or else all the changes in each action are backed out.  The transaction can be managed by a JavaEE container, or by an application (bean).  Because of the simplicity and less coding, the container managed transaction can be more easily used and enterprise beans use container managed transaction by default if no transaction demarcation is specified.

In an enterprise bean with container-managed transaction demarcation, the EJB container set the boundary of the transaction.  Typically, the container begins a transaction immediately before an enterprise bean method starts and commits the transaction just before the method exists.  Let 's see what it means.

I will use the same Account Entity shown at the bottom of this post and an AccountService shown on this post.   This is also the same AccountRestEnd class, but let me show it again here.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
@Logging
@Path("/account")
public class AccountRestEnd {
   @Inject
   private Logger logger;
   
   @Inject
   private AccountService accountService;
   
   @POST
   @Path("/balance")
   @Produces(MediaType.APPLICATION_JSON)
   public Response changeBalance(@QueryParam("id") Long acctId,
         @DefaultValue("0") @QueryParam("amount") Integer amount){
      Account acct = null;
      try{
         acct = accountService.updateBalance(acctId, amount);
      }catch(Exception ex){
         return Response.status(Response.Status.CONFLICT).build();
      }
      Response rs = Response.ok(acct).build();
      logger.info("Finished a balance endpoint synchronously.");
      return rs;
   }
}

This RESTful API endpoint is a stateless class, but doesn't have a @Stateless annotation, which makes this object an enterprise bean. (Therefore, this class is not an enterprise bean) When a HTTP request hits this method, this method is not in a boundary of the container managed transaction.  Unlike this class, the AccountService is annotated with the @Stateless and is an EJB managed object.

Understanding the Transaction Boundary
Let's put a breakpoint on the line 19 and run the same test case described under the Problem with Concurrent Access on this post.  After the second thread run, you will experience the the exception on the line 19 of the AccountRestEnd class. You are able to catch an exception inside your code and can control how to handle the exception.   In the matter of the transaction boundary, the transaction started right before entering the updateBalance method of an entity bean, AccountService, and the commit/rollback occurs after the completion of the method.

For demonstration, let's make the AccountRestEnd class an enterprise bean by adding the @Stateless annotation (after the line 2).  It means the container transaction starts before the changeBalance method.  When you run the same test case, the EJBTransactionRollbackException will NOT be caught on the line 19  (or 20 after adding the Stateless annotation) because the rollback occurs after the method of the JEB managed object, AccountRestEnd.  The client will receive a 500 Internal Server Error with the TomEE instead of an error status that developers handles.

Controlling the Scope of a Transaction
A transaction attribute controls the scope of a transaction and it can be one of the followings: Required, RequiresNew, Mandatory, NotSupported, Supports, or Never.   More description of each attributes can be found on this  Java EE 7 tutorial.  Let's use an attribute and see how the transaction boundary changes within an entity bean.

Our changeBalance method in the AccountRestAccount doesn't do any database operation by itself and there is no reason to have longer transaction boundary especially any database locking is used.

Now, this AccountRestEnd class is an enterprise bean by having a @Stateless annotation, but the @TransactionAttribute changes a transaction boundary with a NOT_SUPPORTED attribute. This attribute makes a container managed transaction not to start before the changeBalance method. 

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
@Logging
@Path("/account")
@Stateless
public class AccountRestEnd {
   @Inject
   private Logger logger;
   
   @Inject
   private AccountService accountService;
   
   @POST
   @Path("/balance")
   @Produces(MediaType.APPLICATION_JSON)
   @TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED)
   public Response changeBalance(@QueryParam("id") Long acctId,
         @DefaultValue("0") @QueryParam("amount") Integer amount){
      Account acct = null;
      try{
         acct = accountService.updateBalance(acctId, amount);
      }catch(EJBTransactionRolledbackException ex){
         return Response.status(Response.Status.CONFLICT).build();
      }
      Response rs = Response.ok(acct).build();
      logger.info("Finished a balance endpoint synchronously.");
      return rs;
   }
}

If you run the same test case, the transaction rollback is triggered after the updateBalance method and the changeBalance method of the enterprise bean, AccountRestEnd' can catch the EJBTransactionRolledbackException at the line 21.  When you use several breakpoints in these code during the test, the debug stack trace on your IDE can show you the calling process more clearly.

No comments:

Post a Comment

Java 9: Flow - Reactive Programming

Programming world has always been changed fast enough and many programming / design paradigms have been introduced such as object oriented p...