PDA

View Full Version : Having issues with Spring transaction management (rollbacks)



vka2b
Aug 1st, 2006, 03:18 PM
Hi,

I apologize if this is covered elsewhere, but I feel that I’ve done a pretty thorough job trying to read what was applicable in the documentation and existing posts in the forum. Any advice anybody can give me would be greatly appreciated.

Let me explain the context in which I am trying to use Spring and then describe the details of my problem. We currently have a web application that is tied to specific web servers (Weblogic and Websphere). In order to break this dependency, we are taking a few steps, one of which is converting our existing EJB classes to POJO.

With the elimination of the EJB framework from our code, we need to implement some other form of transaction management. This is where we were hoping for Spring to come in. For now, all we want to use it for is transaction management and nothing else (with the hopes that in future releases of our application, we can integrate more and more of the Spring framework).

In order to achieve this, I am testing out Spring transaction management with just a few classes/methods. I have set up an applicationContext.xml that defines the beans for these classes/methods as well as ties them to a transaction manager. In dataAccessContext.xml I have defined the data source and transaction manager beans. I have added these to my web.xml, and can see based on the debug messages that these are getting properly initialized when I initialize my web server. The trouble I am having is that when I throw an exception from one of the methods defined in applicationContext.xml, nothing happens (i.e. the transaction doesn’t rollback). I don’t even see anything in my log file that indicates that Spring is even aware that an exception was thrown.

I know that something has to be wrong with my dataAccessContext.xml. Even though I can see that it is getting read in properly, I can put in completely invalid arguments (i.e. an incorrect URL) but I don’t get any errors or warnings, so clearly it’s not being used. I know that I must be missing something, but I am at a loss as for what. There is some sort of disconnect between my dataAccessContext.xml and my code that is actually getting a connection from the database, executing SQL across that connection, etc.

I am posting my applicationContext.xml, dataAccessContext.xml, web.xml, and excerpts from my logfile. If anything jumps out at anybody or you need anymore information to help me diagnose this, please let me know. Thanks in advance.

applicationContext.xml:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
<bean id="alertAssignmentTarget" class="com.mantas.platform.admin.alertassign.AlertAssignm entRuleEntity"/>
<bean id="alertScoreTarget" class="com.mantas.platform.admin.alertscoreeditor.AlertSc oreStrategyEntity"/>
<bean id="matchScoreTarget" class="com.mantas.platform.admin.alertscoreeditor.MatchSc oreEntity"/>
<bean id="thresholdTarget" class="com.mantas.platform.admin.thresholdeditor.Threshol dEntity"/>
<bean id="appControllerTarget" class="com.mantas.platform.control.AppController"/>
<bean id="txProxyTemplate" abstract="true" class="org.springframework.transaction.interceptor.Transa ctionProxyFactoryBean">
<property name="transactionManager" ref="transactionManager"/>
<property name="transactionAttributes">
<props>
<prop key="create">PROPAGATION_REQUIRED,-java.lang.Exception</prop>
<prop key="findByPrimaryKey">PROPAGATION_REQUIRED,readOnly,-RemoteException</prop>
<prop key="store">PROPAGATION_REQUIRED,-RemoteException</prop>
<prop key="load">PROPAGATION_REQUIRED,readOnly,-RemoteException</prop>
<prop key="remove">PROPAGATION_REQUIRED,-RemoteException</prop>
</props>
</property>
</bean>
<bean id="appController" class="org.springframework.transaction.interceptor.Transa ctionProxyFactoryBean">
<property name="transactionManager" ref="transactionManager"/>
<property name="target" ref="appControllerTarget"/>
<property name="transactionAttributes">
<props>
<prop key="execute">PROPAGATION_REQUIRED,-java.lang.Exception</prop>
<prop key="callJavaMethod">PROPAGATION_REQUIRED,-Exception</prop>
</props>
</property>
</bean>
<bean id="alertAssignment" parent="txProxyTemplate">
<property name="target" ref="alertAssignmentTarget"/>
</bean>
<bean id="alertScore" parent="txProxyTemplate">
<property name="target" ref="alertScoreTarget"/>
</bean>
<bean id="matchScore" parent="txProxyTemplate">
<property name="target" ref="matchScoreTarget"/>
</bean>
<bean id="threshold" parent="txProxyTemplate">
<property name="target" ref="thresholdTarget"/>
</bean>
</beans>

As you can see, I am using both an abstract transaction proxy bean that the other beans inherit from as well as a non-template one (the latter being added to test a "normal" case in the chance that I was doing something wrong in setting up the relationship between parent/child beans).

Here is my dataAccessContext.xml:


<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerD ataSource" destroy-method="close">
<property name="driverClassName"><value>oracle.jdbc.driver.OracleDriver</value></property>
<property name="url"><value>jdbc:oracle:oci:@D5O9S10</value></property>
<property name="username"><value>KDD_WEB</value></property>
<property name="password"><value>KDD_WEB</value></property>
</bean>
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTran sactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
</beans>

Here is my web.xml:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.2//EN" "http://java.sun.com/j2ee/dtds/web-app_2.2.dtd">
<web-app id="WebApp_1046465055226">
<servlet id="Servlet_1046464107434">
<servlet-name>RequestServlet</servlet-name>
<servlet-class>com.mantas.platform.web.RequestServlet</servlet-class>
</servlet>
<servlet>
<servlet-name>BaseInitServlet</servlet-name>
<display-name>BaseInitServlet</display-name>
<servlet-class>com.mantas.platform.servlet.BaseInitServlet</servlet-class>
<init-param>
<param-name>WebAppType</param-name>
<param-value>@APPSERVER_NAME@</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet>
<servlet-name>context</servlet-name>
<servlet-class>org.springframework.web.context.ContextLoaderServl et</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/dataAccessContext.xml,/WEB-INF/applicationContext.xml</param-value>
</context-param>
<servlet-mapping id="ServletMapping_1046465055267">
<servlet-name>RequestServlet</servlet-name>
<url-pattern>/app/request/*</url-pattern>
</servlet-mapping>
<session-config id="SessionConfig_1046465055267">
<session-timeout>30</session-timeout>
</session-config>
<welcome-file-list id="WelcomeFileList_1046465055277">
<welcome-file>./platform/workflow/web/content/index.jsp</welcome-file>
</welcome-file-list>
<taglib id="TagLibRef_1046465055277">
<taglib-uri>/WEB-INF/ui-taglib.tld</taglib-uri>
<taglib-location>/WEB-INF/ui-taglib.tld</taglib-location>
</taglib>
</web-app>

My logfiles are huge, so I am just posting a few snippets to show that the beans actually are getting read in and initialized:

2006-07-31 16:31:15,119 [Thread-6] DEBUG Found 2 <bean> elements in ServletContext resource [/WEB-INF/dataAccessContext-local.xml]

2006-07-31 16:31:15,333 [Thread-6] DEBUG Loaded 11 bean definitions from location pattern [/WEB-INF/applicationContext.xml]
2006-07-31 16:31:15,334 [Thread-6] INFO Bean factory for application context [Root WebApplicationContext]: org.springframework.beans.factory.support.DefaultL istableBeanFactory defining beans [dataSource,transactionManager,alertAssignmentTar
get,alertScoreTarget,matchScoreTarget,thresholdTar get,appControllerTarget,txProxyTemplate,appControl ler,alertAssignment,alertScore,matchScore,threshol d]; root of BeanFactory hierarchy

As you can see, the beans are being loaded. However, that's where the success stops! Let's say, for example, that AlertAssignmentRuleEntity.create() inserts a record into the database and then throws an Exception. Based on my understanding of my configuration, the insert should get rolled back. However, this does not happen, and I do not see anything that tells me that Spring is even aware of this Exception being thrown.

If it helps diagnose the problem at all, here is something that has been confusing me: Our existing database code doesn't obtain a connection through a DataSource, it uses the DriverManager (this is why I thought DriverManagerDataSource was the right one to use). As such, I'm not entirely sure how to map it to a DataSource. This is also where I am unclear as to how Spring is aware of the database connection being used in the code (i.e. where exactly is the mapping between Spring and the connection obtained through the code)? I'm not sure if I articulated that question properly, but I guess I'll find out soon...

Again, my plea for help, and I'll close with that! Thanks.

wpoitras
Aug 1st, 2006, 08:34 PM
If it helps diagnose the problem at all, here is something that has been confusing me: Our existing database code doesn't obtain a connection through a DataSource, it uses the DriverManager (this is why I thought DriverManagerDataSource was the right one to use). As such, I'm not entirely sure how to map it to a DataSource. This is also where I am unclear as to how Spring is aware of the database connection being used in the code (i.e. where exactly is the mapping between Spring and the connection obtained through the code)? I'm not sure if I articulated that question properly, but I guess I'll find out soon...

You hit the nail right on the head. In order for your database code to take part of the transaction started by the TransactionFactoryBean it needs to use the same DataSource object used by the transaction manager.

You need to modify your database objects to accept a javax.sql.DataSource as a property that you initialize in Spring XML files. Exactly what you initialize it with will depend on how much you can change your code to use the DataSource.

If you can change your database code to obtain a connection from the DataSource with org.springframework.jdbc.datasource.DataSourceUtil s.getConnection then you can just pass the dataSource object from your dataAccessContext.xml into your database objects.

If for some reason you want to use javax.sql.DataSource.getConnection in your database code because you don't want to put Spring code in them, then you should wrap your dataSource object with an instance of org.springframework.jdbc.datasource.TransactionAwa reDataSourceProxy before setting the dataSource property in your database objects. See JavaDoc for detail.

In the long run I would suggest refactoring your database code to use more Spring JDBC classes. It tends to make the code simpler and less boilerplate. It tends to have less try/catch/finally/try/catch in it.

vka2b
Aug 2nd, 2006, 03:46 PM
Bill,

Thanks so much for your message. I appreciate the help.

I think I understand what you're saying. However, if you don't mind, I just need a few points of clarification:

I guess what I'm still a little fuzzy on is how to initialize my code (database objects) with objects from the Spring XML files, as in your suggestion:


If you can change your database code to obtain a connection from the DataSource with org.springframework.jdbc.datasource.DataSourceUtil s.getConnection then you can just pass the dataSource object from your dataAccessContext.xml into your database objects.

Can you show me an example of passing an object from the Spring XML to the Java application code?

If I were to not follow that route but instead use your suggestion below:


If for some reason you want to use javax.sql.DataSource.getConnection in your database code because you don't want to put Spring code in them, then you should wrap your dataSource object with an instance of org.springframework.jdbc.datasource.TransactionAwa reDataSourceProxy before setting the dataSource property in your database objects. See JavaDoc for detail.

Do I just initialize a TransactionAwareDataSourceProxy by passing it an instance of my DataSource object, and then proceed with calls to that DataSource as normal (i.e. does anything else have to change)? Does the type of my dataSource in dataAccessContext.xml now become a TransactionAwareDataSourceProxy?

As for this:


In the long run I would suggest refactoring your database code to use more Spring JDBC classes. It tends to make the code simpler and less boilerplate. It tends to have less try/catch/finally/try/catch in it.

I definitely intend to further integrate Spring in the long run...this transaction management task is sort of a proof-of-concept project to get our foot in the door, and then over time I plan to rip out our exisiting framework and replace with Spring.

Thanks again for your help.

vka2b
Aug 2nd, 2006, 04:47 PM
I would still like some input on my previous reply to Bill's post, but I wanted to post another piece on information to clarifiy my issue.

I guess the main source of all the problems I'm having is that we're not actually instantiating a DataSource object anywhere in our code. We're using a DriverManager to get our connections. The engineer who is working on this code tells me that the reason we're using a DriverManager is that we use a connection pool, and we use the DriverManager to connect the driver to that connection pool to ensure that our calls to getConnection() only come from that pool. He says that as long as we still have the ability to register with the pool, then it should be okay to switch from using DriverManager to a DataSource.

Here is my question:

Is doing this switch the only way I'm going to get Spring transaction management to work? In other words, without using a DataSource (and instead using a DriverManager), am I just plain out of luck, or is there still a way to do this through Spring?

wpoitras
Aug 2nd, 2006, 06:36 PM
Can you show me an example of passing an object from the Spring XML to the Java application code?

Just like you passed a dataSource to your transaction manager

<bean id="alertAssignmentTarget" class="com.mantas.platform.admin.alertassign.AlertAssignm entRuleEntity"/>
<bean id="alertScoreTarget" class="com.mantas.platform.admin.alertscoreeditor.AlertSc oreStrategyEntity"/>
<bean id="matchScoreTarget" class="com.mantas.platform.admin.alertscoreeditor.MatchSc oreEntity"/>
<bean id="thresholdTarget" class="com.mantas.platform.admin.thresholdeditor.Threshol dEntity"/>
<bean id="appControllerTarget" class="com.mantas.platform.control.AppController"/>

becomes:

<bean id="alertAssignmentTarget" class="com.mantas.platform.admin.alertassign.AlertAssignm entRuleEntity">
<property name="dataSource" ref="dataSource"/>
</bean>
<bean id="alertScoreTarget" class="com.mantas.platform.admin.alertscoreeditor.AlertSc oreStrategyEntity">
<property name="dataSource" ref="dataSource"/>
</bean>
<bean id="matchScoreTarget" class="com.mantas.platform.admin.alertscoreeditor.MatchSc oreEntity">
<property name="dataSource" ref="dataSource"/>
</bean>
<bean id="thresholdTarget" class="com.mantas.platform.admin.thresholdeditor.Threshol dEntity">
<property name="dataSource" ref="dataSource"/>
</bean>
<bean id="appControllerTarget" class="com.mantas.platform.control.AppController">
<property name="dataSource" ref="dataSource"/>
</bean>


In your database code you would add:

private javax.sql.DataSource dataSource;
public void setDatasource(javax.sql.DataSource dataSource) {
this.dataSource = dataSource;
}


Do I just initialize a TransactionAwareDataSourceProxy by passing it an instance of my DataSource object, and then proceed with calls to that DataSource as normal (i.e. does anything else have to change)? Does the type of my dataSource in dataAccessContext.xml now become a TransactionAwareDataSourceProxy?

Yes, you pass your existing datasource to TransactionAwareDataSourceProxy. Not much would change. Your "dataSource" object would remain the same. You would create another datasource:


<bean id="taDataSource" class="org.springframework.jdbc.datasource.TransactionAwa reDataSourceProxy">
<constructor-arg ref="dataSource"/>
</bean>

You would only need to pass this datasource to your database code, you could still use "dataSource" for your transaction manager. TransactionAwareDataSourceProxy just allows you to use getConnection. This is often used for legacy code that can't be changed to use the DataSourceUtils class.

I hear you about slowly introducing more Spring stuff. Building confidence with the rest of your organization can be a long road.

BTW, I would recommend you replace using a DriverManagerDataSource with some sort of pool. Like C3P0. Check the forums for its usage.


Is doing this switch the only way I'm going to get Spring transaction management to work? In other words, without using a DataSource (and instead using a DriverManager), am I just plain out of luck, or is there still a way to do this through Spring?

I'm pretty sure its the only way it will work. What happens is when a transaction is started, the transaction code gets a connection from the dataSource and then stores it in a ThreadLocal object. During the transaction DataSourceUtils.getConnection against the same DataSource returns the exact same connection in use by the transaction. That is why you can make several database calls and have them all part of the same transaction without passing the same Connection object to each piece of database code.

vka2b
Aug 3rd, 2006, 12:01 PM
Bill,

Thanks so much for your input -- that's exactly the information I was looking for and I appreciate your guidance.

I unfortunately can't tell you, "It worked!!" because I am waiting for other pieces of code to fall into place before I can test this out, but with my new understanding of how this all works, I can't imagine that I will experience the same problem as before.

Thanks again, if you're interested I'll send out an update once I've got it all working.

wpoitras
Aug 3rd, 2006, 12:19 PM
I'd certainly be interested in an update.