Using JTA with Spring in OC4J

How-To : Using JTA with Spring in Oracle Container for Java EE 11 Technology Preview

First Created: 01-Feb-2007
Last Updated: 12-Apr-2007
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 bankDataSource 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 brokerageDataSource 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 AssetReports 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

In order to complete the example application, you should be familiar with the following:

Software Requirements

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

Notations

  • %ORACLE_HOME% - The directory where Oracle Container for Java EE 11 Technology Preview is installed.
  • %SPRING_HOME% - The directory where Spring Framework 2.0.5 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 Container for Java EE Technology Preveiw, 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 Container for Java EE Technology Preview is installed.
  • %SPRING_HOME% - The directory where Spring 2.0.5 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/bankDataSource and jdbc/brokerageDataSource ) 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="howtoJTAConnectionPool">

<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="bankDataSource"
   connection-pool-name="howtoJTAConnectionPool"
   jndi-name="jdbc/bankDataSource"
   user="bankuser"
   password="bankuser"
/> 

<managed-data-source name="brokerageDataSource"
   connection-pool-name="howtoJTAConnectionPool"
   jndi-name="jdbc/brokerageDataSource"
   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 oracl