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 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