ONJava.com -- The Independent Source for Enterprise Java
oreilly.comSafari Books Online.Conferences.

advertisement

AddThis Social Bookmark Button

Wire Hibernate Transactions in Spring
Pages: 1, 2, 3

Design for Managing Transactions in Multiple Components

Let us now discuss what we mean by "assembling component transactions." You might have noted the different TX attributes specified for OrderListManager, which is a service-level component in our domain. The main objects identified for our domain are shown in the Business Domain Object Model (BDOM) shown in Figure 2:

Business Domain Object Model (BDOM)
Figure 2. Business Domain Object Model (BDOM)

For demonstration purposes, let us list out some Non-Functional Requirements (NFR) for the objects in our domain:

  • Business objects needs to be persisted in a database, appfuse1.
  • Audit needs to be logged in a separate database, appfuse2, behind a firewall, for security reasons.
  • Business components should be reused.
  • Every attempt should be made to audit all activities in the business services layer.

Taking into account of the above requirements, we have decided that the OrderListManager services will delegate any audit log calls to the already-available AuditManager component. This leads us to the detailed design shown in Figure 3:

Design of Component Services
Figure 3. Design of component services

The notable point here is that, due to our NFR, we are mapping OrderListManager-related objects to the appfuse1 database, and Audit-related objects to appfuse2. Then, for any auditing purposes, the OrderListManager component calls the AuditManager component. We all will agree that the methods in OrderListManager component need to be transactional, since we are creating orders and line items using the services. What about the services in the AuditManager component? Since the service we are considering in the AuditManager component is doing an audit trace, we are interested in persisting as much of an audit trail as we can, for every possible business activity in the system. This leads to a requirement like, "We need to make audit entry, even if the main business activity fails." The AuditManager component also needs to be in its own transaction, since it is also interacting with its own database. This is shown below:

<beans>

<!-- other code goes here... -->
<bean id="auditManager"
        class="org.springframework.transaction.
         interceptor.TransactionProxyFactoryBean">
        <property name="transactionManager">
                <ref local="transactionManager2"/>
        </property>
        <property name="target">
                <ref local="auditManagerTarget"/>
        </property>
        <property name="transactionAttributes">
                <props>
                        <prop key="log">
                                PROPAGATION_REQUIRES_NEW
                        </prop>
                </props>
        </property>
</bean>

</beans>

We will now concentrate in two business services, viz. createOrderList and addLineItem, for our demo purposes. Also note that we are not concentrating on best design strategies--you might have noted that the addLineItem method throws a FacadeException, but createOrderList doesn't. In a production design, you might want every service method to deal with exception scenarios.

public class OrderListManagerImpl
        implements OrderListManager{

private AuditManager auditManager;

public Long createOrderList
 (OrderList orderList){
        Long orderId = orderListDAO.
                createOrderList(orderList);
       auditManager.log(new AuditObject
                (ORDER + orderId, CREATE));
 
        return orderId;
}

public void addLineItem
 (Long orderId, LineItem lineItem)
        throws FacadeException{

        Long lineItemId = orderListDAO.
                addLineItem(orderId, lineItem);
       auditManager.log(new AuditObject
                (LINE_ITEM + lineItemId, CREATE));
 
        int numberOfLineItems = orderListDAO.
                queryNumberOfLineItems(orderId);
        if(numberOfLineItems > 2){
                log("Added LineItem " + lineItemId +
                        " to Order " + orderId + ";
                        But rolling back *** !");
                throw new FacadeException("Make a new
                        Order for this line item");
        }
        else{
                log("Added LineItem " + lineItemId +
                        " to Order " + orderId + ".");
        }
}

//Other code goes here...

}

To create an exception scenario for the purposes of our demonstration, we have introduced another business rule that states that a particular order cannot contain more than two line items. We should now note the call to auditManager.log() method from within createOrderList and addLineItem. You should have already noted the transaction attributes specified for the above methods.

<bean id="orderListManager"
        class="org.springframework.transaction.
         interceptor.TransactionProxyFactoryBean">
        <property name="transactionAttributes">
                <props>
<prop key="createOrderList">
                                PROPAGATION_REQUIRED
                        </prop>
                        <prop key="addLineItem">
                                PROPAGATION_REQUIRED,-com.
                                example.exception.FacadeException
                        </prop>
 
                </props>
        </property>
</bean>

<bean id="auditManager" class="org.
        springframework.transaction.interceptor.
                TransactionProxyFactoryBean">
        <property name="transactionAttributes">
                <props>
<prop key="log">
                                PROPAGATION_REQUIRES_NEW
                        </prop>
 
                </props>
        </property>
</bean>

PROPAGATION_REQUIRED is equivalent to TX_REQUIRED, and PROPAGATION_REQUIRES_NEW is equivalent to TX_REQUIRES_NEW in EJB. If we want the service methods to always run in a transaction, we can use PROPAGATION_REQUIRED. When we use PROPAGATION_REQUIRED, if a TX is already running, the bean method joins in that TX, or else the Spring lightweight TX manager starts one for you. If we always want a new transaction to begin when the component services are called, we can use the PROPAGATION_REQUIRES_NEW attribute.

We have also specified that addLineItem should always roll back the transaction if the method throws any exception of the type FacadeException. This is another level of granularity by which we can finely control how a TX should end, in case we have an exception scenario. Prefixing a - sign specifies to roll back the TX, and a + sign specifies to commit the TX.

The next question is, why we have given PROPAGATION_REQUIRES_NEW for the log method? This is driven by our requirement that, whatever happens to our main service methods, we need to log audit trails for every attempt of order creation and adding line items in our system. This means that even if we come across an exception scenario at any point from within the implementation of createOrderList and addLineItem, we have to log an audit trail. This is possible if and only if we start a new TX and call log in this new TX context. That's why log is given the PROPAGATION_REQUIRES_NEW TX attribute: if the call to

auditManager.log(new AuditObject(LINE_ITEM +
            lineItemId, CREATE));

is successful, auditManager.log() will happen in a new TX context, which will be committed anyway if auditManager.log() is successful by itself (i.e., doesn't throw an exception).

Setting up the Demo Environment

To prepare the demonstration environment, I have been following the reference book Spring Live.

  1. Download and install the following components. When doing so, do note the exact versions, otherwise you may have to sort out any version mismatch incompatibilities.
  2. Set up the following environment variables in the system:
    • JAVA_HOME
    • CATALINA_HOME
    • ANT_HOME
  3. Add the following to your PATH environment variable, or use full paths to execute your scripts:
    • JAVA_HOME\bin
    • CATALINA_HOME\bin
    • ANT_HOME\bin
  4. To set up Tomcat, open the $CATALINA_HOME/conf/tomcat-users.xml file in a text editor and verify that the following lines exist. If they do not, you have to add them:
           <role rolename="manager"/>
    
            <user username="admin" password="admin" roles="manager"/>
    
    
  5. To create a web application based on Struts, Spring, and Hibernate, we have to use Equinox to make a bare-bones starter application--this will have the predefined folder structure, all of the required .jar files, and the Ant build script. Extract Equinox into a folder, so that it will create an equinox folder. Change the directory to the equinox folder, and type the command ANT_HOME\bin\ant new -Dapp.name=myusers. This will create a folder called myusers at the same level as equinox. The contents of it are shown below:

    Equinox Myusers Application Folder Template
    Figure 4. Equinox myusers application folder template

  6. Delete all .xml files from the myusers\web\WEB-INF folder.
  7. Copy equinox\extras\struts\web\WEB-INF\lib\struts*.jar files to the myusers\web\WEB-INF\lib, folder so that the example application is Struts-ready, too.
  8. From the sample code in the Resources section on page 3, unzip myusersextra.zip to some convenient location. Change the directory to the newly created myusersextra folder. Copy all of the contents in the myusersextra folder, and paste (overwrite) them into the myusers folder.
  9. Open up a command prompt and change the directory to myusers. Execute CATALINA_HOME\bin\startup. It is important to start Tomcat from the myusers folder, otherwise the database will not be created inside of the myusers folder, which will cause problems when we execute some tasks defined in build.xml.
  10. Open up a second command prompt, and change the directory to myusers. Execute ANT_HOME\bin\ant install. This will build the application and deploy it in Tomcat. When we do this we can note that a directory db is created in myusers, for the databases appfuse1 and appfuse2.
  11. Open up the browser and verify that the myusers application is deployed at http://localhost:8080/myusers/
  12. To reinstall the application, execute ANT_HOME\bin\ant remove, and then shut down Tomcat by executing CATALINA_HOME\bin\shutdown. Now delete any myusers folder from the CATALINA_HOME\webapps folder. Then restart Tomcat by executing CATALINA_HOME\bin\startup and install the application by executing ANT_HOME\bin\ant install.

Pages: 1, 2, 3

Next Pagearrow