Announcement Announcement Module
Collapse
No announcement yet.
Declared Transactions still contain multiple commits Page Title Module
Move Remove Collapse
X
Conversation Detail Module
Collapse
  • Filter
  • Time
  • Show
Clear All
new posts

  • Declared Transactions still contain multiple commits

    Hi,

    I am having some problems with a declarative transaction which appears to be ignoring my declaration and updating the database as a number of smaller transactions.
    I have a class - similar to the following - that contains a method that should perform a number of data operations within a single transaction:

    Code:
    public ModifyService extends IModifyService
    {
       public void modifyRecord( Entry entry )
       {
          Record rec = mHibernateDAO.getRecord();
    
          record.updateFromEntry( entry );
          this.deleteStuff( record );
          this.regenerateStuff( record );
       }
    }
    I have another class that separates the main processing loop from the data modification method provided by the previous class:
    Code:
    public Process
    {
       public IModifyService mModifyService;
       ...
       public void setModifyService( IModifyService service )
       {
          mModifyService = service;
       }
    
       public void doProcess()
       {
          while( moreEntries() )
          {
             Entry entry = getEntry();
             mModifyService.modifyRecord( entry )
          }
       }
    }
    The spring configuration file declares two beans, with the ModifyService class wrapped in a transaction proxy, and the complex modifyRecord method declared as a transaction attribute:
    HTML Code:
    <bean id="modifyService" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
       <property name="transactionManager" ref="transactionManager" />
       <property name="target">
          <bean class="app.service.ModifyService" >
               ... set Hibernate DAOs ...
          </bean>
       </property>
       <property name="transactionAttributes">
          <props>
              <prop key="modifyRecord">PROPAGATION_REQUIRED</prop>
          </props>
       </property>
    </bean>
    
    <bean id="process" class="app.process.Process">
       <property name="modifyService" ref="modifyService" />
    </bean>
    When executed, I can see from the log files that a transaction is initiated when the modifyRecord method is called from the modifyService bean, but when the application is forcibly terminated in the middle of the this.deleteStuff() method, things do not work as I expected:
    1. The changes made by record.updateFromEntry() are committed to DB
    2. The changes made by this.deleteStuff() are rolled back.
    Can anyone shed any light on why this ModifyService::modifyRecord method might be applied as multiple db transactions even though it is declared within a single transaction?

    Any help is appreciated greatly!
    Thanks,
    Ben

  • #2
    Your configuration looks fine... Which database are you using?

    Comment


    • #3
      Our database is Oracle 10g

      If it adds anything to the conversation we are using Hibernate, and the application structure is a little more complex.
      There is a third class:
      Code:
      public ModifyAction
      {
         public void doModifyAction()
         {
            Record rec = mHibernateDAO.getRecord();
      
            record.updateFromEntry( entry );
            this.deleteStuff( record );
            this.regenerateStuff( record );      
         }
      }
      The body of the ModifyService::modifyRecord() method actually looks like this:
      Code:
         public void modifyRecord()
         {
            ModifyAction action = new ModifyAction();
            action.doModifyAction()
         }
      The fact that the data changes are made within the instance of another class created by the modifyService bean and not directoy by the modifyService bean shouldn't make any difference to where the transactions occur, should it?

      Thanks again,
      Ben
      Last edited by pokeefb; Nov 25th, 2007, 05:22 PM. Reason: To provide more information

      Comment


      • #4
        This is driving me nuts now!
        Could this be a problem between the Hibernate Session and Transaction?
        If I have declared a method as transactional, and am definitely invoking it correctly (log files include begin and commit transaction messages in the right places when the app is left alone), how can this partial update be happening?

        Is it possible that the shutdown hook for the application context is terminating the application, but not throwing an exception that would cause the transaction to roll back?

        Comment


        • #5
          Ehrm... You are creating new instances of the ModifyAction bean, how is this bean getting its references? Are the beans it references spring managed beans or also beans created by new!

          Comment


          • #6
            The constructor for the ModifyAction bean actually accepts the DAO bean as a parameter - sorry, I forgot to include that in my previous post.

            Just to clear things up, maybe I should start again. My application has the following classes:
            Code:
            public Process
            {
               private IModifyService mModifyService;
               
               public void setModifyService( IModifyService service )
               {
                  mModifyService = service;
               }
            
               public void doProcess()
               {
                  Entry entry = mModifyService.getNextEntry();
                  while( entry != null )
                  {
                     mModifyService.modifyRecord( entry );
                     entry = mModifyService.getNextEntry();
                  }
               }
            }
            
            public ModifyService extends IModifyService
            {
               private IEntryDAO   mEntryDAO;
               private IRecordDAO mRecordDAO;
            
               public void setEntryDAO( IEntryDAO dao )
               {
                  mEntryDAO = dao;
               }
            
               public void setRecordDAO( IRecordDAO dao )
               {
                  mRecordDAO = dao;
               }
            
               public Entry getNextEntry()
               {
                  Entry nextEntry = mEntryDAO.getNextEntry();
                  if( nextEntry != null )
                  {
                     nextEntry.setStatus( Entry.STATUS_PROCESSING );
                     mEntryDAO.updateEntry( nextEntry );
                  }
               }
            
               public void modifyRecord( Entry entry )
               {
                  ModifyAction action = new ModifyAction( mRecordDAO );
                  action.doAction( entry );
            
                  entry.setStatus( Entry.STATUS_COMPLETE );
                  mEntryDAO.updateEntry( entry );
               }
            }
            
            public ModifyAction
            {
               private IRecordDAO mRecordDAO;
            
               public ModifyAction( IRecordDAO dao )
               {
                   mRecordDAO = dao;
               }
            
               public void doAction( Entry entry );
               {
                  Record rec = mRecordDAO.getRecord( entry.getRecordID() );
            
                  rec.updateFromEntry( entry );
                  this.deleteStuff( rec );
                  this.regenerateStuff( rec );
            
                  mRecordDAO.updateRecord( rec );
               }
            }
            The Spring configuration file includes the following bean definitions. Note that modifyService is wrapped with a transaction proxy, with the two main methods identified as transactions:
            HTML Code:
            <bean id="dataSource" class="oracle.jdbc.pool.OracleConnectionCacheImpl" destroy-method="close">
               <property name="URL" value="${database.url}"/>
               <property name="user" value="${database.user}"/>
               <property name="password" value="${database.password}"/>
               <property name="maxLimit" value="${database.maxActive}"/>
            </bean>
            
            <bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
               <property name="dataSource" ref="dataSource"/>
               <property name="mappingLocations" value="classpath*:*/*.hbm.xml"/>
               <property name="hibernateProperties">
                  <props>
                     <prop key="hibernate.dialect">org.hibernate.dialect.Oracle9Dialect</prop>
                     <prop key="hibernate.jdbc.batch_size">100</prop>
                  </props>
               </property>
            </bean>
            
            <bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
               <property name="sessionFactory" ref="sessionFactory"/>
            </bean>
            
            <bean id="entryDAO" class="infrastructure.match.HibernateEntryDAO">
               <property name="sessionFactory" ref="sessionFactory" />
            </bean>
            
            <bean id="recordDAO" class="infrastructure.match.HibernateRecordDAO">
               <property name="sessionFactory" ref="sessionFactory" />
            </bean>
            
            <bean id="modifyService" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
               <property name="transactionManager" ref="transactionManager" />
               <property name="target">
                  <bean class="service.match.MatchService" >
                     <property name="entryDAO" ref="entryDAO"/>
                     <property name="recordDAO" ref="recordDAO"/>
                  </bean>
               </property>
               <property name="transactionAttributes">
                  <props>
                     <prop key="getNextEntry">PROPAGATION_REQUIRED</prop>
                     <prop key="modifyRecord">PROPAGATION_REQUIRED</prop>
                  </props>
               </property>
            </bean>
            
            <bean id="process" class="service.match.Process">
               <property name="modifyService" ref="modifyService" />
            </bean>
            The entire application is run as a Windows service using Java Service Wrapper When I stop the service part way through the ModifyAction::doAction method, I get the following results:
            1. The status update made by the previous call to ModifyService::getNextEntry is rolled back
            2. The call to rec.updateFromEntry() is committed to the database
            3. Changes made by ModifyAction.deleteStuff are rolled back
            4. Changes made by ModifyAction.regenerateStuff are rolled back
            I don't understand what is happening. First, how could the changes made by ModifyService::getNextEntry be rolled back, as they were made within - and committed by - a separate database transaction. Secondly, why is rec.updateFromEntry committed, while other changes made within the same transaction are rolled back?

            Comment


            • #7
              There must be something off with your transaction configuration or in your dao, can you post code for that? For some more information about starting transactions etc. you might want to crank up the logging to debug level.

              Also it looks like an overly complex architecture, but that is from my point of view.

              Comment


              • #8
                I agree the architecture looks pretty complex, but that's probably because I am simplifying the code for the purpose of my example (ie: ModifyService::ModifyRecord will perform one of many actions - not just ModifyAction - based on the contents of the Entry object.

                The code for the DAOs are quite simple:
                Code:
                public HibernateEntryDAO extends HibernateDaoSupport
                {
                   public Entry getNextEntry()
                   {
                      HibernateTemplate template = getHibernateTemplate();
                      List queueEntries = template.find( HQL_NEXT_QUEUE_ENTRY );
                
                      if( !queueEntries.isEmpty() )
                         return (Entry)(queueEntries.get( 0 ));
                
                      return null;
                   }
                
                   public void updateEntry( Entry entry )
                   {
                      getHibernateTemplate().saveOrUpdate( entry );
                   }
                }
                
                public HibernateRecordDAO extends HibernateDaoSupport
                {
                   public Record getRecord( Long recordID );
                   {
                      RecordSearchData searchData = new RecordSearchData();
                      searchData.setRecordID( recordID );
                      List records = getHibernateTemplate().executeFind( new RecordsCallback(searchData) );
                
                      if( records.isEmpty() )
                         return null;
                
                      return (Record)( records.get( 0 ) );
                   }
                
                   public void updateRecord( Record rec )
                   {
                      getHibernateTemplate().saveOrUpdate( rec );
                   }
                }
                I have tried setting the log level to debug, and the transactions appear to be beginning and committing at the correct places.

                Comment


                • #9
                  I have discovered something else that may be of interest:

                  As mentioned previously, the application is deployed as a Windows Service using Java Service Wrapper. When I stop the Service using the Windows Service management console, the transaction problems I describe occur.

                  However, if I terminate the JVM directly through Windows Task Manager, the transactions are rolled back correctly.

                  The only difference I am able to pick is that when the service is stopped, a shutdown hook is executed to clean up the beans. When the JVM is terminated, the shutdown hook is not executed.

                  My understanding is that Spring will rollback a transaction if an exception is thrown. Is it possible that the shutdown hook is interrupting the transactional method without raising an exception, leading to the transaction being committed rather than rolled back?

                  If this is the case, how do I get around it?

                  Comment


                  • #10
                    Database should never commit changes for transactions that were never explicitly commited. You should try to debug stuff on jdbc level to see what's going on really.

                    Comment


                    • #11
                      Originally posted by dejanp View Post
                      You should try to debug stuff on jdbc level to see what's going on really.
                      This might sound a little stupid, but how do I set up Craftsman Spy to work for me (the website doesn't provide much info)? I've tried this:
                      1. Make sure spy.jar is in the class path
                      2. Update the log4j file to include the "craftsman.spy" category
                      3. Run the application using the -Dspy.driver=oracle.jdbc.driver.OracleDriver option
                      and nothing much happens.

                      Comment


                      • #12
                        1. is ok.
                        2. is not needed.
                        3. is not needed.
                        4. is change the driver in your datasource to "craftsman.spy.SpyDriver"
                        5. is change the jdbc url to "jdbc:spy:originaldriverhere:restoftheoriginal url"

                        Comment


                        • #13
                          I'm still not getting this right. My original data source configuration is:
                          HTML Code:
                          <bean id="dataSource" class="oracle.jdbc.pool.OracleConnectionCacheImpl" destroy-method="close">
                              <property name="URL" value="jdbc:oracle:thin@(... url ...)>
                              <property name="user" value="${database.user}"/>
                              <property name="password" value="${database.password}"/>
                              <property name="maxLimit" value="${database.maxActive}"/>
                          </bean>
                          To get Craftsman Spy to work, changed it to the following:
                          HTML Code:
                          <bean id="dataSource" class="oracle.jdbc.pool.OracleConnectionCacheImpl" destroy-method="close">
                              <property name="URL" value="jdbc:spy:oracle:thin@(... url ...)" >
                              <property name="user" value="${database.user}"/>
                              <property name="password" value="${database.password}"/>
                              <property name="maxLimit" value="${database.maxActive}"/>
                              <property name="driverType" value="craftsman.spy.SpyDriver" />
                          </bean>
                          When I run the application, I get an error stating "Invalid Oracle URL specified".

                          Any idea what I'm doing wrong?

                          Thanks again,
                          Ben

                          Comment


                          • #14
                            <property name="URL" value="jdbc:spy:theoriginaldriverhere:oracle:thin@ (... url ...)" >

                            Comment


                            • #15
                              Hmmm... How do I find the original driver?

                              As far as I can tell, the application only ever specifieds jdbc:oracle:thin, which is the name of the thin client driver, yes?

                              Comment

                              Working...
                              X