Announcement Announcement Module
Collapse
No announcement yet.
How to commit or roll back separate transactions in one go Page Title Module
Move Remove Collapse
X
Conversation Detail Module
Collapse
  • Filter
  • Time
  • Show
Clear All
new posts

  • How to commit or roll back separate transactions in one go

    I use the class below to devise a strategy/code structure to perform several operations sequentially, from a single method, but have each operation performed in a separate transaction.

    It doesn't seem to work, however. What I want is that whatever happens inside actualTransaction() is rolled back if there's an exception in that method. What I expect is that when entering doSeparateTransaction() a transaction should be started, which should be commited in case the call to actualTransaction completes without an error, and rolled back if any error is generated in that function. What actually happens is quite different.

    A transaction is started only inside the method call in line 017. This method being called on __provider actually is the first one, down the call hierarchy, wrapped by a proxy which I can see in the call stack is named something like TransactionWrapper. How come? I have annotated all classes with @Transactional, provided the propagation which I deemed right for each point, and I raise an exception which I do not catch in the caling method, which also has a @Transactional annotation.

    In the static void main() from where I call entryPoint() I obtain the bean from a Spring context, with __provider already properly initialized by the context. The context being able to initialize __provider based on explicit bean property definition is the only reason there are a getter and a setter provided for __provider.

    I have read the forums, and while I was able to find posts on transactions and rollback, I was not able to find something pertinent to my problem.

    I can provide additional files or log output, if useful, I just don't know what is indeed useful, so I'd rather upload only what's required after it is asked for than create a uselessly long post right from the start.

    Code:
    000        @Transactional
    001        public class WithDistinctTransactionMethod implements IWithDistinctTransaction
    002        {
    003            private dbOperationsProvider __provider;
    004            public dbOperationsProvider getProvider()                                      
    005            {                                                                              
    006                return __provider;                                                         
    007            }                                                                              
    008            public void setProvider(dbOperationsProvider provider)                         
    009            {                                                                              
    010                __provider = provider;                                                     
    011            }                                                                              
    012            @Transactional                                                                 
    013            private void actualTransaction(int toWrite)                                    
    014            {                                                                              
    015                String arrayName = "transaction " + toWrite;                               
    016                Object[] parameters = {toWrite};                                           
    017                __provider.writeQueryParameters(arrayName, parameters);                    
    018                if (toWrite % 2 == 0)                                                      
    019                    throw new RuntimeException("Intentionally thrown to fail transaction");
    020            }                                                                              
    021            @Override                                                                      
    022            @Transactional(propagation = Propagation.REQUIRES_NEW)                         
    023            public void doSeparateTransaction(int toWrite)                                 
    024            {                                                                              
    025                actualTransaction(toWrite);                                                
    026            }                                                                              
    027            @Override                                                                      
    028            @Transactional(propagation = Propagation.NEVER)                                
    029            public void entryPoint()                                                       
    030            {                                                                              
    031                for (int i = 0; i < 10; i++)                                               
    032                {                                                                          
    033                    try                                                                    
    034                    {                                                                      
    035                        doSeparateTransaction(i);                                          
    036                    }                                                                      
    037                    catch(RuntimeException ex)                                             
    038                    {                                                                      
    039                        System.out.println("Exception for " + i);                          
    040                    }                                                                      
    041                }                                                                          
    042            }
    043        }
    Last edited by flj; Mar 4th, 2011, 05:18 PM. Reason: To mark as solved

  • #2
    [s]
    That is never going to happen. It basically breaks ACID (and especially the Durable part here)

    When a transaction commits it commits.. You cannot have 1 large tx, and 100 smaller tx and when tx 99 fails rollback all previous 98 records also. They are already committed.
    [/s]

    Next to that I suggest a read of the aop chapter and especially the part that explains proxies. In short internal method calls are NEVER intercepted they don't pass through the proxy.

    If you want this to work either move the doSeperateTransaction to another object so that it becomes an external method call or use manual tx management by wrapping it a TransactionTemplate.

    (Ignore the initial comment I should read closer)
    Last edited by Marten Deinum; Mar 4th, 2011, 03:23 AM.

    Comment


    • #3
      Roll back can occur if transaction management could be done. You can invoke method calls in service layer from your controllers.

      You can set the boundaries for rolling back of operations, provided you use hibernate cache to store intermediate data and JTA transaction management.

      Comment


      • #4
        Originally posted by Marten Deinum View Post
        [s]
        That is never going to happen. It basically breaks ACID (and especially the Durable part here)

        When a transaction commits it commits.. You cannot have 1 large tx, and 100 smaller tx and when tx 99 fails rollback all previous 98 records also. They are already committed.
        [/s]

        Next to that I suggest a read of the aop chapter and especially the part that explains proxies. In short internal method calls are NEVER intercepted they don't pass through the proxy.

        If you want this to work either move the doSeperateTransaction to another object so that it becomes an external method call or use manual tx management by wrapping it a TransactionTemplate.
        Sorry, I'm sometimes stupid, and not that good at explaining what I mean. I don't mean having some transactions already commited, then rolling back all of the commited ones when one additional transaction rolls back. What I need is this: start an operation in its own transaction, then have it commit or roll back, then start a new operation in its own transaction, have it commit or roll back, and so on, until all operations are finished, but not have them all executed in the same transaction, such that failure of one operation rolls back all operations. Is this better understandable?

        Second, sorry again, I was considering things in a very shallow way- now that you mention it, I don't even need to re-read the documentation to understand my error. I was expecting any public method to be proxied, even for internal calls (which is where my stupidity lies), when in fact it is proxied only for calls from outside the class. Of course when called from another method inside the class, a public method isn't proxied - all calls happen inside the proxied object, which of course knows its own methods, and has no way of routing calls through a proxy of which it isn't aware, whereas a call from outside runs through the Spring-injected proxy. This also explains why the transaction wrapper is only applied around the foreign call at line 017 in the code above.

        I will default to manual transaction management in this particular part of the app (a quartz job running asynchronous tasks), if I can't make it work using annotations and by moving the transaction to a separate class. I wouldn't like this, however, since I think the annotation-based mechanism is the better readable and easier to maintain variant.

        I'll post the results of my experiments here.

        Comment


        • #5
          Got it!

          We were having transactions configured redundantly, using both <tx:advice /> in the XML file, and <tx:annotation-driven />, which created a mess. Furthermore, we had @Transactional without a specified propagation on each and every possible method in our classes, which caused the mess to be even bigger - everything was transactional, and everything was using the same transaction, and even if someone started a new transaction in between, everything outside the particular method starting the transaction was still executing in the old transaction. Even more, stupid me was assuming that MySQL MEMORY tables are transactional - which they aren't.

          I removed all @Transactional annotations on all methods I use in the test, placed just a @Transactional(propagation=Propagation.REQUIRES_NE W) on doSeparateTransaction(), re-created the table I was using for the test to be INNODB instead of MEMORY, and it suddenly worked. Unfortunately, I'll have to do this on the entire application

          Comment

          Working...
          X