JavaEE: Caching costly data across a transaction with TransactionSynchronizationRegistry

By | September 13, 2016

Everybody knows that a database query is extremely costly in enterprise application and usually has the biggest impact in process time of a transaction. There are plenty of best practice rules a developer has to follow:

1. Retrieve as much data as possible in a single query, avoid to make a data retrieve from the database with very small granularity.
2. Avoid as much as possible frequent queries to database views or database nicknames.
3. Try not to exceed the database transaction memory. By implementing rule 1. people tend to retrieve too much data in one shot. This has to be controlled as well because you can hit memory or database limits which is much more dangerous than a slow transaction.
4. Avoid retrieving the same data over and over again from the database. This implies using a sort of caching mechanism.
5. Avoid very generic queries that retrieves data that is not necessary. Try to do all the data filtering in the SQL query not in the application layer.

Using a caching mechanism together with very clear defined queries (not to restrictive and not to generic) will avoid almost all the problems.

Lucky for us Java EE already provides a very efficient way to do this data caching when working with a database layer.

In our example we are processing bulks of XML messages in the same transaction. It is obviously that due to the XML format we are going to use the same data over and over again while parsing and validation the XMLs. In this case we can use the very efficient TransactionSynchronizationRegistry.

As defined in Java Docs:

This interface is intended for use by system level application server components such as persistence managers, resource adapters, as well as EJB and Web application components. This provides the ability to register synchronization objects with special ordering semantics, associate resource objects with the current transaction, get the transaction context of the current transaction, get current transaction status, and mark the current transaction for rollback. This interface is implemented by the application server by a stateless service object. The same object can be used by any number of components with thread safety.

The main processing of XML bulks is done in a EJB where we are injecting the transaction registry.


@Stateless(name = "XMLInputHandlerImpl", mappedName = "XMLInputHandlerImpl/remote")
public class XMLInputHandlerImpl extends HandlerDefaultImpl implements InputHandler {
....
/**
* Transaction synch registry
*/
@Resource(mappedName = "java:comp/TransactionSynchronizationRegistry")
TransactionSynchronizationRegistry txSynchReg;
...
public void processXMLBulkMessage(Message m){
...
for (xmlFile:XMLFiles){
String userName = getUserFromFileName(xmlFile.filename);
User requestUser = (User) txSynchReg.getResource("User_" + userName);
if (requestUser == null) {
requestUser = (User) userMng.getUser(requestUserName);
if (requestUser != null)
txSynchReg.putResource("User_" + requestUserName, requestUser);
}
...
printUserInfo(userName);
} //end of for
...

}

public void printUserInfo(String username){
User requestUser = (User) txSynchReg.getResource("User_" + userName);
if (requestUser == null) {
requestUser = (User) userMng.getUser(requestUserName);
if (requestUser != null)
txSynchReg.putResource("User_" + requestUserName, requestUser);
}
log.info("Full Name: " + requestUser.getFullName);
log.info("Address: " + requestUser.getAddress());
}
...
}

Note 1: In the above code we are caching a costly database query to find the user object (requestUser = (User) userMng.getUser(requestUserName);). Instead of always calling the user manager bean method to get the user from the database only the first time we need it the actual query to the database is made and the result is saved as a resource in the transaction synchronization registry.

Note 2: As long as we are in the same transaction we can always obtain the user from the transaction synchronization registry.

Note 3: You do not have to stay in the same method, as long as you are in the same transaction the saved information is accessible. So method call “printUserInfo(userName);” will be able to print the correct enformation without passing any other parameter than the cache key (userName) or the saved resource.

Note 4: In case of transaction roll-back the stored values are lost

Advertisements