How-To : Using JTA with Spring in OC4J

Date: 2/01/07
Author: Paul Parkinson and Frances Zhao

Introduction

Excerpt from the Spring Framework Transaction Management documentation:
One of the most compelling reasons to use the Spring Framework is the comprehensive transaction support. The Spring Framework provides a consistent abstraction for transaction management that delivers the following benefits:

  • Provides a consistent programming model across different transaction APIs such as JTA, JDBC, Hibernate, JPA, and JDO.

  • Supports declarative transaction management.

  • Provides a simpler API for programmatic transaction management than a number of complex transaction APIs such as JTA.

  • Integrates very well with Spring's various data access abstractions

This example application demonstrates Oracle's support for Spring with JTA with Spring's OC4JJtaTransactionManager. The application demonstrates the classic distributed two-phase commit transaction use case requiring ACID properties: the bank account transfer. Funds are debited from one account and credited to another. Either both the debit and credit must occur or neither must occur. In the case of this how-to, the transfer is from a bank account to a brokerage acount in order to purchase individual stocks. The how-to consists of a very simple MVC style application consisting of a test controller, financial service, asset managment service, and two DAO objects representing a bank and a brokerage. Container-manager transactions are used. This how-to adds a couple additional aspects to this scenario to demonstrate the extended features of the OC4JJtaTransactionManager which include named transactions and per-transaction isolation-level designation.

HowToJTASpringController

 

The HowToJTASpringController implements the Spring Controller and InitializingBean interfaces (see source for javadoc).

Note that the setFinancial method will provide the FinancialService implementation (as specified in applicationContext.xml)

public class HowToJTASpringController implements InitializingBean, Controller {
    private FinancialService m_financial;

    public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
        try {
            FinancialReport financialReport = m_financial.processFinancials();
            System.out.println("Successfully processed financials");
            request.setAttribute("financialReport", financialReport);
            return new ModelAndView("/jsp/success.jsp");
        }
        catch (Exception e) {
            e.printStackTrace();
            request.setAttribute("error", e.getMessage());
            return new ModelAndView("/jsp/error.jsp");
        }
    }

FinancialServiceImpl

 

The FinancialServiceImpl class implements the Spring InitializingBean interface as well as the FinancialService interface.

The setAssetManagement method will be called by the Spring framework providing the AssetManagementService implementation (as specified in applicationContext.xml) using dependency injection.

The Transactional class-level annotation ( tx annotation support is specified in applicationContext.xml) designates that business methods of this class, namely processFinancials, have a propagation value of Propagation.REQUIRED. That is, the methods execute within a transaction if one exists or a transaction started if none exists. The annotation also specifies that the transaction is to be readOnly and the isolation-level of any connections used within the transaction set to SERIALIZABLE.

@Transactional(readOnly = true, propagation = Propagation.REQUIRED, isolation = Isolation.SERIALIZABLE)
public class FinancialServiceImpl implements InitializingBean, FinancialService {
    AssetManagementService m_assetManagementService;


    public FinancialReport processFinancials() {
        AssetReport assetReportBeforeStockPurchase = m_assetManagementService.reportAllAssets();
        StockPurchaseReport stockPurchaseReport = m_assetManagementService.purchaseNewStockAndReport();
        AssetReport assetReportAfterStockPurchase = m_assetManagementService.reportAllAssets();
        return new FinancialReport(assetReportBeforeStockPurchase,  stockPurchaseReport, assetReportAfterStockPurchase);
    }


    public final void afterPropertiesSet() throws Exception {
                          

        if (m_assetManagementService == null)
                          

            throw new BeanCreationException("NoAssetManagementService was set.  Verify context xml.");
    }

    public void setAssetManagement(AssetManagementService assetManagementService) {
        m_assetManagementService = assetManagementService;
    }
}
                        

AssetManagementServiceImpl

 

The AssetManagementServiceImpl class implements the Spring InitializingBean interface as well as the AssetManagementService interface.

The setBank and setBrokerage methods are called by the Spring framework providing the Bank and Brokerage DAO implementations (as specified in applicationContext.xml) using dependency injection.

The Transactional method-level annotation ( tx annotation support is specified in applicationContext.xml) designates that the purchaseNewStockAndReport method has a propagation value of Propagation.REQUIRED, that is, will execute within a transaction if one exists or a transaction will be started if none exists. The annotation also specifies the isolation (that is isolation-level) of any connections used within the transaction will be set to READ_COMMITTED.

Another method-level Transactional annotation designates that the reportAllAssets method will have a propagation value of Propagation.SUPPORTS. That method executes within a transaction if one exists but will not through an exception or start a transaction if none exists. The annotation also specifies the noRollbackFor be set to ConcurrencyFailureException.class, which indicates that if a transaction exists and this Spring DAO RuntimeException is thrown, the transaction should not rollback as a result.

public class AssetManagementServiceImpl implements InitializingBean, AssetManagementService {
    private Bank m_bank;
    private Brokerage m_brokerage;

    @Transactional(propagation = Propagation.SUPPORTS, noRollbackFor = ConcurrencyFailureException.class)
    public AssetReport reportAllAssets() {
        return new AssetReport(m_bank.selectBalance(), m_brokerage.selectAllStocks());
    }

    @Transactional(propagation = Propagation.REQUIRES_NEW, isolation = Isolation.READ_COMMITTED)
    public StockPurchaseReport purchaseNewStockAndReport() {
        int stockAmount = 10;
        String stockSymbol = "ABC";
        m_bank.updateBalance(m_bank.selectBalanceForUpdate() - stockAmount);
        m_brokerage.insertStock(stockSymbol, stockAmount);
        return new StockPurchaseReport(stockSymbol, stockAmount);
    }

    public final void afterPropertiesSet() throws Exception {
                          

        if (m_bank == null) throw new BeanCreationException("No Bank was set.  Verify context xml.");
                          

        if (m_brokerage == null) throw new BeanCreationException("No Brokerage was set.  Verify context xml.");
    }

    public void setBank(Bank bank) {
        m_bank = bank;
    }

    public void setBrokerage(Brokerage brokerage) {
        m_brokerage = brokerage;
    }

}
                        

BankImpl

 

The BankImpl class extends the Spring JdbcDaoSupport class and uses the Spring JdbcTemplate to act upon the bankDS datasource .

public class BankImpl extends JdbcDaoSupport implements Bank {

    public int selectBalance() {
        return getJdbcTemplate().queryForInt("select balance from bank where account = '101'");
    }

    public int selectBalanceForUpdate() {
        return getJdbcTemplate().queryForInt("select balance from bank where account = '101' for update");
    }

    public void updateBalance(int amount) {
        getJdbcTemplate().execute("update bank set balance = " + amount + " where account = '101'");
    }
}

BrokerageImpl

 

The BrokerageImpl class extends the Spring JdbcDaoSupport class and uses the Spring JdbcTemplate to act upon the brokerageDS datasource .

public class BrokerageImpl extends JdbcDaoSupport implements Brokerage {

    public List selectAllStocks() {
        return getJdbcTemplate().queryForList("select * from brokerage");
    }

    public void insertStock(String symbol, int amount) {
        getJdbcTemplate().execute("insert into brokerage values ('"+symbol+"', '"+amount+"' )");
    }
}

 

AssetReport, StockPurchaseReport, and FinancialReport



These classes are DTOs (Data Transfer Objects) that hold report data:

  • The AssetReport class holds the bank balance and list of stocks.
  • The StockPurchaseReport class holds the stock symbol and amount of the stock purchase.
  • The FinancialReport class holds the StockPurchaseReport as well as AssetReport s taken before and after the stock purchase.

 

web.xml

 

The J2EE standard web-app descriptor includes a Spring ContextLoaderListener listener. The ContextLoaderListener causes the WEB-INF/applicationContext.xml specified by the contextConfigLocation context-param to be loaded by the Spring framework.

The Spring DispatcherServlet servlet deployed with the servlet-name jta-spring causes the jta-spring-servlet.xml to be loaded by the Spring framework.

<web-app>
   <display-name>OC4J JTA Spring Integration WebApp</display-name>

   <context-param>
      <param-name>contextConfigLocation</param-name>
      <param-value>WEB-INF/applicationContext.xml</param-value>
   </context-param>

   <listener>
      <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
   </listener>

   <servlet>
      <servlet-name>jta-spring</servlet-name>
      <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
      <load-on-startup>2</load-on-startup>
   </servlet>

   <servlet-mapping>
      <servlet-name>jta-spring</servlet-name>
      <url-pattern>JTADispatcherServlet</url-pattern>
   </servlet-mapping>

   <welcome-file-list>
      <welcome-file>index.html</welcome-file>
   </welcome-file-list>
</web-app>

 

jta-spring-servlet.xml

 

The descriptor contains bean definition for the HowToJTASpringController, namely the financialService bean named financial (the property name corresponds to the setter in HowToJTASpringController)

<beans>

   <bean id="urlMapping" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
      <property name="urlMap">
      <map>
         <entry key="*"><ref local="testController"/></entry>
      </map>
      </property>
   </bean>

   <bean id="testController" class="how.to.spring.tx.HowToJTASpringController">
      <property name="financial"><ref bean="financialService"/></property>
   </bean>

</beans>

applicationContext.xml

 

The descriptor contains bean definitions for the FinancialServiceImpl, AssetManagementServiceImpl, BankImpl, and BrokerageImpl classes.

The <tx:annotation-driven/> element specifies support for annotation driven demarcation of transactions.

Finally, the descriptor specifies OC4JJtaTransactionManager as the transactionManager to be used.

<beans xmlns="http://www.springframework.org/schema/beans"
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xmlns:aop="http://www.springframework.org/schema/aop"
   xmlns:tx="http://www.springframework.org/schema/tx"
   xsi:schemaLocation="
   http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
   http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd
   http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd">

   <bean id="attributes" class="org.springframework.metadata.commons.CommonsAttributes"/>

   <bean id="bankDAO" class="how.to.spring.tx.BankImpl">
      <property name="dataSource">
         <ref bean="bankDataSource"/>
      </property>
   </bean>

   <bean id="bankDataSource" class="org.springframework.jndi.JndiObjectFactoryBean">
      <property name="jndiName">
         <value>jdbc/bankDataSource</value>
      </property>
   </bean>

   <bean id="brokerageDAO" class="how.to.spring.tx.BrokerageImpl">
      <property name="dataSource">
         <ref bean="brokerageDataSource"/>
      </property>
   </bean>

   <bean id="brokerageDataSource" class="org.springframework.jndi.JndiObjectFactoryBean">
      <property name="jndiName">
         <value>jdbc/brokerageDataSource</value>
      </property>
   </bean>

   <bean id="assetManagementService" class="how.to.spring.tx.AssetManagementServiceImpl">
      <property name="brokerage">
         <ref local="brokerageDAO"/>
      </property>
      <property name="bank">
         <ref local="bankDAO"/>
      </property>
   </bean>

   <bean id="financialService" class="how.to.spring.tx.FinancialServiceImpl">
      <property name="assetManagement">
         <ref local="assetManagementService"/>
      </property>
   </bean>

   <!-- enable the configuration of transactional behavior based on annotations -->
   <tx:annotation-driven/>

   <bean id="transactionManager" class="org.springframework.transaction.jta.OC4JJtaTransactionManager">
      <!-- for pre-10.1.3.2 OC4J use <property name="transactionManagerName" value="java:comp/UserTransaction"/>
         10.1.3.2 provides direct access to the transaction manager without the need for JNDI -->
   </bean>

   <!-- enable the configuration of transactional behavior based on xml
   <aop:config>
      <aop:pointcut id="financialOperations" expression="execution(* how.to.spring.tx.*.*(..))"/>
      <aop:advisor pointcut-ref="financialOperations" advice-ref="txAdvice"/>
   </aop:config>
   <tx:advice id="txAdvice">
      <tx:attributes>
         <tx:method name="processFinancials" read-only="true" propagation = "REQUIRED" isolation = "SERIALIZABLE"/>
         <tx:method name="reportAllAssets" propagation = "SUPPORTS" no-rollback-for="org.springframework.dao.ConcurrencyFailureException"/>
         <tx:method name="purchaseNewStockAndReport" propagation = "REQUIRES_NEW" isolation = "READ_COMMITTED"/>
      </tx:attributes>
   </tx:advice>
-->
</beans>

 

Prerequisites

What you need to know

Software Requirements

This demonstration requires that the following software components are installed and configured correctly:

  • Oracle Application Server 10g 10.1.3.2.
  • Sun JDK version 1.5 or above, available here
  • Spring 2.0.3
  • An HTML browser
  • A relational database such as Oracle.

Notations

  • %ORACLE_HOME% - The directory where Oracle Application Server 10g 10.1.3.2 is installed.
  • %SPRING_HOME% - The directory where Spring Framework 2.0.3 is installed.
  • %JAVA_HOME% - The directory where your JDK is installed.
  • %HOWTO_HOME% - The directory where this demo is unzipped.

Install SQL Scripts

The jta-spring-howto.sql file is located in the %HOWTO_HOME%/scripts directory.

Running the Application

To run the sample application on a standalone instance of Oracle Application Server 10g 10.1.3.2, follow these steps:

1. Examine the Sample File Directories

  • build - temporary directory created during the build
  • log - temporary directory holding build/deploy logs
  • etc - all necessary files to package the application
  • lib - holds the application archives to be deployed
  • doc - the How-to document and Javadoc's
    • how-to-jta-spring.html - this How-to page
  • src - the source of the demo
    • web- contains application

2. Configure the Environment

Ensure the following environment variables are defined:

  • %ORACLE_HOME% - The directory where Oracle Application Server 10g 10.1.3.2 is installed.
  • %SPRING_HOME% - The directory where Spring 2.0.3 is installed.
  • %JAVA_HOME% - The directory where J2SE 5.0 is installed
  • %PATH% - includes %ORACLE_HOME% /ant/bin

Configure Data Source

This example requires two DataSources (with jndi-location as jdbc/bank and jdbc/brokerage ) to connect to the database(s).

For OC4J, configure a datasource in the %ORACLE_HOME%/j2ee/home/config/data-sources.xml file and point it at the schema.
For example:

<connection-pool name="OracleConnectionPool">

<connection-factory factory-class="oracle.jdbc.pool.OracleDataSource"
   user="scott"
   password="tiger"
   url="jdbc:oracle:thin:@localhost:1521:ORCL" >
</connection-factory>

</connection-pool> <managed-data-source name="bankDS"
   connection-pool-name="OracleConnectionPool"
   jndi-name="jdbc/bank"
   user="bankuser"
   password="bankuser"
/> 

<managed-data-source name="brokerageDS"
   connection-pool-name="OracleConnectionPool"
   jndi-name="jdbc/brokerage"
   user="brokerageuser"
   password="brokerageuser"
/>

You can use Application Server Control to create or modify an existing DataSource.

You can also create the JDBC resources by using ANT tasks. Make sure you change the database configurations (db.host, db.sid, db.port, db.user, db.password) in ant-oracle.properties file.

Ensure $ORACLE_HOME/ant/bin is included in your PATH environment variable and then use the following command:

>ant configure-ds

3. Start the Server

Start OC4J stand alone using the following command after you make the above changes.

>%ORACLE_HOME%/bin/oc4j -start


Optionally, increase the oracle.j2ee.transaction logger level using Application Server Control or by starting OC4J with the -Dtransaction.debug=true system property. 
Depending on the log handler being used you may need to increase the log level on the handler as well.  This can be specified in j2ee-logging.xml. For example:
<log_handler name='console-handler' class='java.util.logging.ConsoleHandler' formatter='oracle.core.ojdl.logging.SimpleFormatter' level='FINEST'/>

 

4. Generate, Compile, and Deploy the Application

The OC4J distribution includes Ant 1.6.2. Set your PATH environment variable to $ORACLE_HOME/ant/bin. On some operating systems, Ant does not currently support the use of environment variables. If this is the case for your operating system, please modify the ant-oracle.xml file located in the %HOWTO_HOME% directory.

Edit ant-oracle.properties (in the demo directory) and ensure the following properties are set to the correct values, as indicated below for your OC4J instance:

  • oc4j.host: host where OC4J is running (default localhost)
  • oc4j.admin.port: RMI port number (default 23791)
  • oc4j.admin.user: admin user name (default oc4jadmin)
  • oc4j.admin.password: admin user password (default welcome)
  • oc4j.binding.module: website name where deployed web modules are bound (default http-web-site)

Uncomment the appropriate deployer.uri in the ant-oracle.properties based on your environment (i.e. a single instance OC4J or a clustered OC4J instance/group managed by OPMN).

To build the application, type the following command from the %HOWTO_HOME% directory:

>ant

The newly created jtaspring.ear is located in the %HOWTO_HOME%/lib directory.

This command attempts to deploy the application archive if the build is successful. It will first test whether OC4J is running before attempting the deployment operation.

Note that you can also deploy the application separately. Ensure the %ORACLE_HOME% environment variable is defined, and from the %HOWTO_HOME% directory, type the command:

>ant deploy

5. Run the Application

Run the sample by providing invoking the following URL from your favorite browser:

http://localhost:8888/jtaspring

The test application runs through the following procedure:

1. The HowToJTASpringController calls the processFinancials() method of the FinancialService.

2. As the FinancialService class is annotated with
@Transactional(readOnly = true, propagation = Propagation.REQUIRED, isolation = Isolation.SERIALIZABLE)
and no JTA transaction currently exists, a transaction is started by the OC4JJtaTransactionManager. This transaction can be viewed in the Enterprise Management consoled as how.to.spring.tx.FinancialServiceImpl.processFinancials(), demonstrating the named transactions feature.

3. The processFinancials() method calls the reportAllAssets() method of the AssetManagementService which in turn queries the Bank and Brokerage Data Access Objects for current bank account balance and stock information. The connections used by these DAOs will have an isolation-level of SERIALIZABLE demonstrating the per-transaction isolation-level feature.

4. The processFinancials() method then calls the purchaseNewStockAndReport() method of the AssetManagementService.

5. Because the purchaseNewStockAndReport() method is annotated with
@Transactional(propagation = Propagation.REQUIRES_NEW, isolation = Isolation.READ_COMMITTED)
the current JTA transaction is suspended and a new JTA transaction is begun. This demonstrates proper suspend of transactions.

6. The purchaseNewStockAndReport() in turn withdraws from the Bank and purchases stock from the Brokerage DAO.

7. Upon returning from the purchaseNewStockAndReport() method, the JTA transaction is committed and the previously suspended transaction is resumed. This demonstrates proper resume of JTA transactions.

8. The processFinancials() method then calls the reportAllAssets() method of the AssetManagementService again (again the connections used by it's Bank and Brokerage DAO objects have an isolation-level of SERIALIZABLE).

9. The FinancialService processFinancials() method returns the report to the HowToJTASpringController which in turn displays it using Spring's ModelAndView. The asset report before and after the stock purchase is the same due to the per-transaction isolation-level functionality. In this case the SERIALIZABLE level has avoided the phantom read. If the FinancialService were annotated with a READ_COMMITTED level instead, the stock purchase would be reflexed in the report.

Summary

In this document, you learned how to:

  • Develop a simple Spring enabled JTA application
  • Use the OC4JJtaTransactionManager to created named transactions, specify per-transaction isolation level, and properly suspend and resume transactions.
  • Deploy an execute a sample application using Spring-enabled JTA on Oracle Application Server 10g 10.1.3.2