Many Java Enterprise Edition (EE) applications access more than one resource when executing a user's request. In such cases, you should use a global transaction to ensure the resources' data integrity. If access to multiple data resources doesn't occur under the same transactional context, one of the resources could be corrupted when an error occurs. Your application can include global transactions by using the Java Transaction API (JTA) transaction manager your application server provides and XA-compliant drivers to connect to the data resources. But your application's requirements might also call (for a variety of technical reasons beyond this article's scope) for a custom isolation level in all data access during the execution of a global transaction -- and JTA transaction managers don't support custom isolation levels.
For example, as part of a use case implementation, the application might query two different database tables and place a message on a message-oriented middleware queue. The design for this use case might require that the two database
READ operations be performed using an isolation level of "read committed." It can also be true that during the execution of a different use case, the application should execute those same two database
READ operations using a different isolation level, such as "repeatable read." During the execution of the two use cases, the application executes the same database operations and portions of the same code, but it must use different isolation levels. This article explains how you can accomplish this -- thanks to the Spring framework -- even though JTA transaction managers don't support custom isolation levels.
To benefit from the article, you should be familiar with the Spring framework and understand Spring configuration files. Familiarity with your application server, Java EE design patterns, and the concept of global/distributed transactions is also assumed.
A not-so-neat approach
If you need a custom isolation level during the execution of a global transaction and use the Spring framework just to wire your objects together (and don't implement one of this article's solutions), you could end up giving the responsibility for applying the corresponding isolation level to your application's data access objects (DAOs). (As you may recall from experience, DAOs are mainly used to separate business logic from storage-access or persistence code.) For instance, take a look at the following scenario. The data source bean definitions in your application might look like those in Listing 1:
Listing 1. Data source bean definitions
<!-- read committed data source --> <bean id="dataSourceRC" class="org.springframework.jndi.JndiObjectFactoryBean"> <property name="jndiName"> <value>java:comp/env/jdbc/my_datasource_rc</value> </property> </bean> <!-- repeatable read data source --> <bean id="dataSourceRR" class="org.springframework.jndi.JndiObjectFactoryBean"> <property name="jndiName"> <value>java:comp/env/jdbc/my_datasource_rr</value> </property> </bean> ...
In Listing 1, you can see two data source definitions. One points to a data source object configured to have a "read committed" isolation level, and the other points to a data source configured to have a "repeatable read" isolation level value. One data source definition exists for each isolation level your application uses, so your configuration file could include four data source definitions if your application uses all four isolation levels.
Also, you might use the data source beans in Listing 1 to create instances of the
JdbcTemplate class, as shown in Listing 2:
Listing 2. JdbcTemplate bean definitions
<!-- read committed jdbcTemplate --> <bean id="jdbcTemplateRC" class="org.springframework.jdbc.core.JdbcTemplate" singleton="true"> <property name="dataSource"> <ref bean="dataSourceRC" /> </property> </bean> <!-- repeatable read jdbcTemplate --> <bean id="jdbcTemplateRR" class="org.springframework.jdbc.core.JdbcTemplate" singleton="true"> <property name="dataSource"> <ref bean="dataSourceRR" /> </property> </bean> ...
Given this scenario, your DAOs could include logic that determines which
JdbcTemplate instance to use depending on the isolation level value for the current running transaction. This value could, for instance, be passed in as a method parameter to your application's DAOs. The caller of the methods in your DAOs would then need to specify the corresponding isolation level depending on the use case being executed.
This approach would work, but including this logic inside the Java code is not the best approach, and maintaining the code would be difficult. In the following sections of this article, I discuss two solutions you can implement to have custom isolation levels in global transactions without needing to turn the objects in the data access layer inside out. Both solutions apply to applications deployed on any application server that uses the data source definition as the location for specifying the isolation level value for database access.
Taking advantage of the Spring framework's transactional features would seem, on the face of it, a better approach. Spring has strong capabilities for defining transactions for an application. The framework lets you specify, in a very clear way, transactional attributes such as isolation levels, propagation behavior, and exception-handling behavior (for example, whether a transaction should be rolled back automatically when particular exceptions are thrown).
The original published version of this article presented a solution based on an older version of the Spring framework. That Spring version always threw an exception if your Spring transaction definition specified a custom isolation level other than
ISOLATION_DEFAULT and you used a JTA transaction manager. I showed how to work around this issue by defining custom isolation levels for transactions in a separate proxy rather than in the Spring standard transaction definition. The current Spring framework version lets you state a custom isolation level in your transaction definition when using a JTA transaction manager. But this capability by itself doesn't guarantee that you'll get the specified custom isolation level during the execution of a global JTA transaction. For this to work correctly, you need to perform the additional steps outlined in the following sections.
I present two solutions that allow custom isolation levels in global transactions. The first is generic solution -- not specific to any application server -- and the second is tailored for IBM WebSphere Application Server. The second solution can serve as a template for any other application servers that also offer a proprietary API to manage their resource connection handlers. The first solution requires the latest production-ready version of the Spring framework (2.0.2 at the time of this writing). The second requires the latest Spring nightly snapshot (2.0.3, build 90, at the time of this writing).
You can choose the solution that best fits your needs. Also, using the ideas presented here as a starting point, you may think of alternate solutions that are specifically tailored to your own application's requirements.
Initial Spring configuration for both solutions
Both solutions require four steps. The first step for both is to configure your Spring transaction manager bean. Your transaction manager's
allowCustomIsolationLevels flag must be set to
true, as shown in bold in Listing 3:
Listing 3. Configuration of a Spring transaction manager bean
<bean id="transactionManager" class="org.springframework.transaction.jta.JtaTransactionManager"> <constructor-arg> <ref local="jtaTransactionManager" /> </constructor-arg> <property name="allowCustomIsolationLevels"> <value>true</value> </property> </bean>
If you don't set the
allowCustomIsolationLevels flag to
true, its value is
false by default. This causes an exception to be thrown if a custom isolation level is specified for a transaction. When you set it to
true, you're letting the transaction manager instance know that you'll have a component in your application -- more on this component soon -- that somehow knows how to apply the specified custom isolation levels in your transactions.
Also, note that the
transactionManager bean might depend on another bean that implements the
javax.transaction.TransactionManager interface. The need to pass in an instance of the
javax.transaction.TransactionManager interface as a constructor argument depends on the application server you're using. In many cases, a plain
JtaTransactionManager definition is all you need. In the bean definition in Listing 3, a reference to a bean identified as
jtaTransactionManager that implements the
TransactionManager interface is passed in as a constructor argument. The definition of the
jtaTransactionManager bean also depends on the application server you are working with. For instance, if you are using IBM WebSphere Application Server, the definition of the
jtaTransactionManager bean should look like Listing 4:
Listing 4. JTA transaction manager definition for IBM WebSphere Application Server
<bean id="jtaTransactionManager" class="org.springframework.transaction.jta.WebSphereTransactionManagerFactoryBean" singleton="true" />
For an application-server-independent way to specify custom isolation levels in global transactions, you can use an instance of the
org.springframework.jdbc.datasource.lookup.IsolationLevelDataSourceRouter class as the component that knows how to apply the isolation levels. It's quite simple to create and configure an instance of this class. One nice thing about it is that it implements the
javax.sql.DataSource interface. This means that you can easily modify your current Spring configuration file so that all beans that include a reference to your data source objects now use an instance of this class instead; you needn't write or change a single line of Java code for this purpose. You'll see soon why you would want to take advantage of this capability.
The second step in this solution is to add this component in your global transactions. To do this, you can route all the method calls that would otherwise be sent to your data source objects to an instance of the
IsolationLevelDataSourceRouter class. During the execution of a global transaction, this instance then routes the method calls to the corresponding data source object (such as
dataSourceRR in Listing 1) based on the isolation level specified for the current running transaction. So you need to modify your configuration file so that all references to your original data source objects now point to an instance of the
IsolationLevelDataSourceRouter class. (You can think of this as a before aspect-oriented advice applied to your application's data source beans.) Listing 5 shows the bean definition for the
Listing 5. IsolationLevelDataSourceRouter bean definition
<bean id="dataSourceRouter" class="org.springframework.jdbc.datasource.lookup.IsolationLevelDataSourceRouter" singleton="true"> <property name="targetDataSources"> <map> <entry key="ISOLATION_REPEATABLE_READ" value="java:comp/env/jdbc/my_datasource_rr /> <entry key="ISOLATION_SERIALIZABLE" value="java:comp/env/jdbc/my_datasource_ser" /> <entry key="ISOLATION_READ_UNCOMMITTED" value="java:comp/env/jdbc/my_datasource_ru" /> <entry key="ISOLATION_READ_COMMITTED" value="java:comp/env/jdbc/my_datasource_rc" /> </map> </property> <property name="defaultTargetDataSource" value="java:comp/env/jdbc/my_ds_rc" /> </bean>
IsolationLevelDataSourceRouter instance uses the mappings (that is, isolation level value to data source object association) specified in its definition to determine how to switch transparently to the appropriate data source object based on the current transaction's isolation level. (Remember that a current transaction's isolation level is specified in the properties of a Spring transaction definition.) A direct benefit of using the
IsolationLevelDataSourceRouter class is that now you need only one
JdbcTemplate instance that uses the
IsolationLevelDataSourceRouter instance as its
The third step, then, is to update your
JdbcTemplate bean definition to reference the
IsolationLevelDataSourceRouter bean defined in the preceding step, as shown in Listing 6:
Listing 6. Single JdbcTemplate definition referencing the IsolationLevelDataSourceRouter bean
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate" singleton="true"> <property name="dataSource"> <ref bean="dataSourceRouter" /> </property> </bean>
The fourth and final step is to define your transactional proxy. The bean definition for this proxy should state the transactional attributes for each method of your service object (see What are service objects?) and the transaction manager that should be used (which must be a reference to the bean defined in Listing 3). For instance, Listing 7 shows the definition of a transactional proxy for a service object named
Listing 7. Transactional proxy definition using a JTA transaction manager
<bean id="orderService" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean"> <property name="transactionManager"> <ref local="transactionManager" /> </property> <property name="proxyInterfaces"> <list> <value>sample.services.OrderService</value> </list> </property> <property name="target"> <ref local="orderServiceTarget" /> </property> <property name="transactionAttributes"> <props> <prop key="save*"> PROPAGATION_REQUIRED, ISOLATION_READ_COMMITTED</prop> <prop key="delete*"> PROPAGATION_REQUIRED, ISOLATION_READ_UNCOMMITTED</prop> <prop key="find*"> PROPAGATION_REQUIRED, ISOLATION_REPEATABLE_READ,readOnly</prop> </props> </property> </bean>
IsolationLevelDataSourceRouter instance and this transactional proxy bean results in the following scenario when the
save() method is invoked:
- The transaction manager starts a transaction.
- A custom isolation level of
ISOLATION_READ_COMMITTEDis set as the current transaction's isolation level value.
- All method calls for retrieving database connections are routed to the
- Whenever the
IsolationLevelDataSourceRouterbean receives a method call, it determines which data source it should route the call to by examining the current isolation level (
ISOLATION_READ_COMMITTEDin this case) specified in the Spring transaction definition.
IsolationLevelDataSourceRouterinstance delegates the method calls it receives to the corresponding data source. In this case it's the data source identified by the Java Naming and Directory Interface (JNDI) name
java:comp/env/jdbc/my_datasource_rc, because this is the data source that was bound to the "read committed" isolation level. (See the
IsolationLevelDataSourceRouterbean definition in Listing 5.) This guarantees that the connection objects used during the execution of this transaction have an isolation level of "read committed."
The sequence diagram in Figure 1 illustrates the scenario I've just described. There you can see the flow of execution when a client object invokes the
find() methods. The
IsolationLevelDataSourceRouter instance handles the task of determining which data source should be used to get a resource connection. All DAOs can reference a single
JdbcTemplate instance, while the
IsolationLevelDataSourceRouter instance is responsible for obtaining a connection object from the corresponding data source object.
Figure 1. Sequence diagram for the save() and find() methods with use of IsolationLevelDataSourceRouter
See the full figure here.
Note that a benefit of using the
IsolationLevelDataSourceRouter class is that you can use the data sources' JNDI names to reference them. You don't need separate bean definitions that point to the data source objects that exist in your application server's JNDI namespace. (Of course, if you'd like to, you can still have them.) As the Spring documentation says, "this allows for a single concise definition without the need for separate
DataSource bean definitions."
To summarize, you can use the
IsolationLevelDataSourceRouter class to route to a corresponding underlying data source object based on the isolation level specified in the Spring transaction definition. This lets you have custom isolation levels in your global JTA transactions. Using a
IsolationLevelDataSourceRouter instance also lets you reference your data source objects without including separate bean definitions for them in your configuration file. However, note that even though you might not need multiple bean definitions for your data source objects in the Spring configuration file, at the Web deployment descriptor level, you still must define a data source for each isolation level you use (see Data sources in deployment descriptors).
The proprietary solution requires a little less setup work: you don't need to define a data source object in your Web deployment descriptor for each isolation level you intend to use in your application; you need just one. The downside of this approach is that it requires the use of a proprietary API that's specific to your application server. I prefer this option because the reduced setup requirements can benefit environments where no development team members have administrative access rights. IBM WebSphere Application Server is the commercial application server that I'm most familiar with, so I used that in this solution.
As in the generic solution, you need a component that receives all the method calls that otherwise would be sent to the original data source instances. It's then up to this component to apply the custom isolation level. In this case, the component that knows how to apply the custom isolation levels is an instance of the
org.springframework.jdbc.datasource.DelegatingDataSource class. This class also implements the
javax.sql.DataSource interface, which makes it extremely easy to substitute all references to your original data source bean definitions with an instance of this class.
A class named
WebSphereDataSourceAdapter (see Resources), which inherits methods from the
org.springframework.jdbc.datasource.IsolationLevelDataSourceAdapter classes, was originally written by Lari Hotari and this author and then further modified by Juergen Hoeller. (The original class was submitted as a feature to the Spring framework and, at the time of this writing, it was available in the latest 2.0.3 nightly snapshot; see Resources.) An instance of this class can serve as the component that knows how to apply custom isolation levels for transactions running in WebSphere Application Server.
All method calls that would be sent to your data source objects must now be routed to an instance of the
WebSphereDataSourceAdapter class. During the execution of a transaction, this WebSphere data source adapter relies on the
getCurrentIsolationLevel() method to determine the isolation level value that should be used for the current running global transaction. (This value, just as in the generic solution, is determined by the properties of the Spring transaction definition.) The implementation of the
getCurrentIsolationLevel() method invokes
org.springframework.transaction.support.TransactionSynchronizationManager class's static
getCurrentTransactionIsolationLevel() method to get this value.
Once the isolation level value is retrieved, it's used to obtain a connection object with the required isolation level value from the WebSphere Application Server data source class implementation (
WSDataSource class requires callers of its methods to use an instance of the
com.ibm.websphere.rsadapter.JDBCConnectionSpec class (see Resources) to state the transactional properties of the connection that should be returned. An instance of the
JDBCConnectionSpec class can be used to specify which isolation level is needed on the connection returned from the
WSDataSource instance. The
setTransactionIsolation() method is used to specify the isolation level that is needed for the current transaction. The implementation of
WebSphereDataSourceAdapter is quite simple; it just serves as a "smart" proxy that knows how to obtain a connection handler with the right isolation level.
The proprietary API from IBM lets you write application-server-specific code that asks for database connections with a custom isolation level. The main benefit of this approach is that you don't need to define a data source (at the deployment descriptor level) for each isolation level you use in your application's transactions. Instead, you define only one data source and then ask that data source for a connection with the corresponding isolation level.
This solution's second step, then, is to add a bean definition to your Spring configuration file for the
WebSphereDataSourceAdapter class, as shown in Listing 8:
Listing 8. Definition of the WebSphereDataSourceAdapter bean
<bean id="dataSourceAdapter" class="org.springframework.jdbc.datasource.WebSphereDataSourceAdapter"> <property name="targetDataSource"> <bean class="org.springframework.jndi.JndiObjectFactoryBean"> <property name="jndiName" value="java:comp/env/jdbc/my_datasource" /> </bean> </property> <property name="isolationLevel"> <bean class="org.springframework.beans.factory.config.FieldRetrievingFactoryBean"> <property name="staticField"> <value>java.sql.Connection.TRANSACTION_READ_COMMITTED</value> </property> </bean> </property> <!-- Note: Username and password are used only when the application resource reference is using res-auth = Application. If the username is "empty", then this class will simply delegate to the standard getConnection() method. --> <property name="username"> <value>username</value> </property> <property name="password"> <value>password</value> </property> </bean>
targetDataSource property (which is inherited from the
DelegatingDataSource class) must be an instance of the proprietary
WSDataSource class. (Otherwise, an exception is thrown.) As Listing 8 shows, this is the data source object that exists for your application in the WebSphere Application Server JNDI namespace. The
isolationLevel property (which is inherited from the
IsolationLevelDataSourceAdapter class) states what the isolation level value should be if no transaction is currently running, and consequently there's no isolation level value to use. For the bean definition in Listing 8, this default isolation level value is
WebSphereDataSourceAdapter class also lets application developers specify a username and password for cases when the application, not the container, handles authentication. You use the username and password properties to specify these credentials.
The third step is to update your
JdbcTemplate bean definition as shown in Listing 9. This bean definition should reference the
WebSphereDataSourceAdapter bean that was defined in the preceding step. This guarantees that the
WebSphereDataSourceAdapter instance "intercepts" all calls before they are dispatched to the WebSphere Application Server data source object.
Listing 9. Single JdbcTemplate definition referencing the WebSphereDataSourceAdapter bean
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate" singleton="true"> <property name="dataSource"> <ref bean="dataSourceAdapter" /> </property> </bean>
The last step is the same as the generic solution's: define a transactional proxy bean. So a bean definition like the one shown in Listing 7 is also needed for this solution.
The sequence diagram shown in Figure 2 illustrates the flow of execution for the
find() methods when an instance of the
WebSphereDataSourceAdapter class is used as the component that applies the corresponding isolation level value for transactions:
Figure 2. Sequence diagram for the save() and find() methods with use of WebSphereDataSourceAdapter
See the full figure here.
To summarize, this second solution requires the implementation of a delegating data source that knows how to obtain a connection from the underlying application server's data source with the corresponding isolation level. This option requires the use of an API that's specific to the application server you're using. You need to define only one data source at the deployment descriptor level. Then, when retrieving connections from that data source, you use your application server's proprietary API to specify the isolation level value for that connection.
A similar solution to the one I've presented here can be developed for other application servers that offer a proprietary API to manage their resource connections and use the data source definition as the location for specifying the isolation level value for database access. You would just need to extend the
IsolationLevelDataSourceAdapter class and write the server-specific code there. You can easily use the WebSphere solution I've presented as a template for building solutions specific to other application servers.
Using the Spring framework, your application can easily use a custom isolation level during the execution of a distributed transaction. The two solutions shown in this article achieve this goal using Spring's dependency-injection capabilities and Spring's standard transaction definition. Both eliminate the need to specify the isolation level for a distributed transaction in a separate proxy or component. You also achieve total decoupling of the classes that implement an application's business and persistence logic from the components that ensure that the same isolation level is used during the execution of the global transaction.
The author would like to thank Coraly Romero-Principe for her help in making this article possible and Lari Hotari for starting the effort and design for the
JtaTransactionManagerclass: Learn how to configure an instance of the
JtaTransactionManagerclass for your application server.
- Spring framework: Learn more about the Spring framework from the project site.
- "Introduction to the Spring framework" (Rod Johnson, TheServerSide.com, May 2005): A great introductory article on the Spring framework.
- "Understanding JTS -- An introduction to transactions", "Understanding JTS -- The magic behind the scenes", "Understanding JTS -- Balancing safety and performance" (Brian Goetz, developerWorks, March-May 2002): Explore the world of Java transactions in these articles from the Java theory and practice series.
- "Understanding JTA -- the Java Transaction API" (DevX, 2002): A good article on distributed transactions.
- Core J2EE Patterns - Session Facade: Read about the Session Facade design pattern.
JDBCConnectionSpecinterface: Learn more about this IBM API.
"Sharing connections in WebSphere Application Server V5" (Teresa Kan, Jian Tang, developerWorks, April 2004): Read more about the uses of the
WebLogicJTATransactionManagerclass: This class supports the full power of Spring's transaction definitions on WebLogic's transaction coordinator.
WebSphereDataSourceAdapter: Spring feature page for this class.
- developerWorks Java technology zone: Hundreds of articles about every aspect of Java programming.
Get products and technologies
- Download IBM product evaluation versions and get your hands on application development tools and middleware products from DB2®, Lotus®, Rational®, Tivoli®, and WebSphere®.