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.
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 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 contextConfigLocationcontext-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>
The descriptor contains bean definition for the HowToJTASpringController, namely the financialService bean named financial (the property name corresponds to the setter in HowToJTASpringController)
<!-- 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:
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 FinancialServiceprocessFinancials() 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