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