Blog

Monday 24 February 2014

Multiple transactions inside one EJB method

Here is a quick example on how to execute EJB method A in a different transaction than a method B which calls A.

Recently, I had a bit complicated use case to deal with. Here is the scenario:

We have created a system which allows user to compose his own newspaper out of various RSS sources. Each source consists of many, many articles. User is able to add new sources to the newspaper by importing them from XML file. Each source entry in the file contains source's name and url under which articles can be found. Once a file is selected it is sent to the server where it is being processed. The thing is that parsing of the file and downloading all of the articles can take a really long time. We don’t want to make user wait for so long. That’s why after translating file’s entries into sources we need to persist them and return their list to the user, while at the same time we start update process for each source in order to download all of its articles. So from the user’s point of view the import ends as soon as new sources are persisted, he doesn’t have to wait until all articles belonging to those sources are downloaded.

Import method looks somewhat like this:

public void importFromXML(MultipartFormDataInput input)
{
   try {
       List<SourceXMLEntry> xmlEntries = parseXMLEntries(input);

       for (SourceXMLEntry xmlEntry : xmlEntries) {
           Source source = sourceManager.create(xmlEntry);
           if (null != source) {
              sourceUpdater.update(source);
           }
       }
   } catch (Exception e) {
       LOGGER.error("Import from failed! Cause: {0}", e);
   }
}
Here is the method which creates sources out of XML file entries:
@TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
public Source create (SourceXMLEntry xmlEntry)
{
  // Create source from xml entry
}
Finally, we have asynchronous source updater method annotated like this:
@Asynchronous
@TransactionAttribute(TransactionAttributeType.SUPPORTS)
public void update(Source source) throws IOException, FeedException
{
  // Download and persist all of source’s articles from the web
}
As you can see, because we don’t want to wait with finishing importFromXML() method until all of the sources are updated (i.e. their articles get downloaded), the update() method is invoked asynchronously – a separate thread for each source is started (note @Asynchronous annotation). That in turn requires that a source passed to the update method must already exist in a database. By default, in container managed transaction both sources and articles downloaded by update() method would be flushed to the database at the end of importFromXML method as all of the actions would be performed in a single transaction. That’s not acceptable in this case. What we want here, is to flush changes to db every time source is persisted by create() method, hence we need to execute it in a separate transaction. That’s what @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW) annotation is for. With all of that in place each source will appear in db after create() method is finished which means that update() method will operate on already persisted object. And because update() method is asynchronous, importFromXML() does not have to wait until all articles get downloaded. It ends as soon as all sources are created and saved.

The important thing which you need to keep in mind is that a method annotated with @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW) has to be located in a different EJB than a method that is invoking it. So in our example importFromXML() and create() methods have to belong to different EJBs. Otherwise no separate transaction will be created.

If you want to learn more about EJB transactions, check out the following links: http://docs.oracle.com/javaee/5/tutorial/doc/bncij.html http://entjavastuff.blogspot.cz/search/label/EJB3

No comments:

Post a Comment