Skip to Content

I had to optimize code execution time, because of long lags browsing web pages. The code was fairly simple. Create new record in one table (Request), some records creation in related by foreign key tables to “Request” table. Preparing and storing log information in separate DB table and prepare some data for web page. Implementation was JPA and WDP consuming EJBs.

My idea was to move all log related code to MDB and make an asynchronous call, because the existing of the log information was important, but not the exact moment of storing it.

I’ve implemented it like this:

// Consumed EJB
public WebPageData getWebPageData() {
       Long requestNumber = createRequestRecord();
       requestEntity.findByRequestNumber(requestNumber);
       // async call to MDB transferring requestNumber
       // prepare data for Web Page
       retrun webPageData;
}
 
private Long createRequestRecord() {
       // create request record….
       return requestNumber;
}
 
private void updateRelatedTables(Long requestNumber) {
       RequestEntity re = requestEntity.findByRequestNumber(requestNumber);
       If (re == null) {
              // Throw an Exception
       } 
       // update related tables ….
}
 
// MDB
public void onMessage() {
       RequestEntity re = requestEntity.findByRequestNumber(requestNumber);
       If (re == null) {
              // Throw an Exception
       } 
       // prepare log and store it …
}

What was the result?

Well there was an improvement in speed, but suddenly I’ve spotted error messages in the Default Trace. The error messages appeared only from time to time and they said that certain request number wasn’t found in MDB.

The MDB asynchronous message handling has a retry count of 3. When exception occurred during handling of asynchronous message it has been resented again and this continue until message successfully handled or after third failed attempt. So in my case usually the exception occurred only first time and after the retry everything looked just fine. But in small number of cases it still appeared even after last retry, so some of the log information was lost.

Well how was that possible after ones request number has been found in getWebPageData() method, but after that it was missing in MDB?!

In order to clear this mystery I’ve used following annotations provided by JPA for its entities:

// REQUEST entity
@PostLoad
public void postLoad() {
       // log PostLoad
}
@PrePersist
public void prePersist() {
       // log prePersist
}
@PreUpdate
public void preupdate() {
       // log preUpdate
}
@PostPersist
public void postactivate() {
       // log postPersist
}

And I’ve obtained some interesting results in Default Trace. When the error occurred there was no postLoad event in MDB and when everything was OK, there was one there. So I’ve asked myself, why?

The answer was simple – sometimes the asynchronous call was executed so fast that it was received in MDB even before getWebPageData() method execution ended. In such case, because by default each EJB method is executed in transaction and this transaction is not yet finished, the data created or updated within this transaction is not available for other transactions. MDB is separate class and it is called asynchronous, so its methods were executed in separate transaction. So now we have 2 different transactions and because the first one is not over yet, the request number that was created in it is not yet available for the second (MDB) transaction.

Ok, but then why second attempt to call MDB usually was a success?

Well the answer is, timing. After first attempt failed it takes some time invoke a second one and usually that time was enough for getWebPageData() method to finish and the first transaction to be closed. Then the data created in it is available for all other transactions and the second call for MDB was successful.

So we need to be sure that the first transaction is finished before we make an asynchronous call.

What is the solution?

I’ve used a very simple solution creating a new method and moving all the code handling data creation in it and put it an annotation “requires new”. This way a new transaction was started every time the method was called and it was closed at the end of the method.

And now the consumed EJB looks like this:

// Consumed EJB
public WebPageData getWebPageData() {
       WebPageData wpd= getWebPageDataInternal();
       // async call to MDB transferring requestNumber
       return wpd;
}
 
@TransactionAttribute(value = TransactionAttributeType.REQUIRES_NEW)
private WebPageData getWebPageDataInternal() {
       Long requestNumber = createRequestRecord();
       requestEntity.findByRequestNumber(requestNumber);
       // prepare data for Web Page
       retrun webPageData;
}
 
private Long createRequestRecord() {
       // create request record….
       return requestNumber;
}
 
private void updateRelatedTables(Long requestNumber) {
       RequestEntity re = requestEntity.findByRequestNumber(requestNumber);
       If (re == null) {
              // Throw an Exception
       } 
       // update related tables ….
}

This way the asynchronous call was executed after the end of the first transaction and the problem was resolved.

To report this post you need to login first.

2 Comments

You must be Logged on to comment or reply to a post.

  1. Franz F
    afaik the call to  WebPageData wpd = getWebPageDataInternal(); won´t start a new transaction – even though it is annotated with the corresponding transaction type. Calling a method directly and not through a ejb instance all ejb and container based functions are not available

    br franz

    (0) 
    1. Ivan Petrov Post author
      Well, you have your point, but actually it worked for me.
      This is a real example and after the fix and I never met the issue with missing request number in MDB.
      (0) 

Leave a Reply