Announcement Announcement Module
Collapse
No announcement yet.
Transaction propagation and exception rollback Page Title Module
Move Remove Collapse
X
Conversation Detail Module
Collapse
  • Filter
  • Time
  • Show
Clear All
new posts

  • Transaction propagation and exception rollback

    Hi,

    Here is my problem:
    - I declare in my appCtx file serviceA with a method with transaction attribute "PROPAGATION_REQUIRED,-Exception"
    - I declare another service, serviceB with the same attributes.

    If I call serviceA directly, if I have an Exception, my transaction rollback, everything is ok.
    If I call serviceB, which call serviceA. I have an exception in serviceA, I trap it and manage it in serviceB. ServiceB does not throw an exception : my transaction rollback.

    This is not the behaviour I expect. As my toplevel service is serviceB, is thought that the attributes behing used are those of serviceB. It seems that, after serviceA call, the transactioninterceptor use setRollbackOnly to force transaction rollback.

    Is there any way to avoid this ? If not do you have any solution to solve my problem ?

    Thanx

    Seb

  • #2
    If you want more details I recommend you read the 'Transactions' chapter from Spring reference documentation. A quick explanation would be:

    Your transactions attributes for serviceA instruct Spring to do a rollback on every exception serviceA gives - this is exactly what happens.
    To solve the problema you should restrict the exceptions on which Spring does rollback.

    Comment


    • #3
      I have this kind of code:

      Code:
      // firstly check if the object exist
      try{
          object = dao.findByPrimaryKey(...);
          // no exception thrown, means object in database
          object.setXyz(...);
          dao.update(object);
      }
      catch(ObjectNotFoundException e){
          // if not exist
         object = new AbcObject();
         dao.insert(object);
      }
      
      // commit the transaction
      The above code is so normal and reasonable. But since the findByPrimaryKey throws exception, it will cause the whole transaction roll back. That's not what I want.

      I think it would be better to let the first caller who initiate the transaction to commit or rollback the transaction. Any other callees should not make the decision on behalf of the caller because they don't know whether the original caller can tolerate the exception or not.

      Unfortunately, in my opinion, this is not the behaviour of the current Spring implementation. Maybe I am wrong, please correct.

      Comment


      • #4
        You are right

        I dig into Spring code and found that if you have a Service that rollback on an Exception, it forces rollback even if it is encapsulates in another service that catch the Exception and manage it.

        I think this is also the ejb behaviour. I would like to have a method like 'setRollbackOnly(false)' that my second level service can use to unset the rollbackonly flag when it manages the transaction but I don't know it this is something possible

        seb

        Comment


        • #5
          I just did a test and, fortunately, it is not the EJB behaviour. I have this code:

          Code:
          // this is a method in a stateless session ejb with transaction required
          public void methodA(...){
             // if exception occur, always throw RuntimeException
          }
          
          // another method in the same session ejb with transaction required
          public void methodB(...){
              try{
                  methodA();
              }
              catch(Exception e){
                 // exception situation can be recovered
                 // do something else
              }
              // continue ...
          }
          After methodB returns, transaction get committed. The failure of methodA doesn't cause the whole transaction roll back.

          The test runs on weblogic 8.14 sp4.

          For the time being, I have to use programatic transaction control, like this:

          Code:
          PlatformTransactionManager transactionManager = getTransactionManager();
          DefaultTransactionDefinition def = new DefaultTransactionDefinition();
          def.setPropagationBehavior(this.getTransactionAttribute());
          	
          TransactionStatus status = transactionManager.getTransaction(def);
          try{
          
              // call other methods that use transaction
          
              if ( status.isNewTransaction() ) { // only the originator can commit or rollback
                  transactionManager.commit(status);
              }
          }
          catch (Throwable t) {
              if ( status.isNewTransaction() ) {
                  transactionManager.rollback(status);
              }
              throw t;
          }

          Comment


          • #6
            That test with WebLogic's EJB behavior doesn't look valid to me. You're calling methodA on the implementation class from methodB, so you're circumventing the EJB proxy completely - and thus won't apply the specified transactional behavior.

            You'll get the exact same behavior if you have a Spring-managed DAO where you call methodA on the implementation class from methodB: you'll be circumventing the proxy there as well - and thus won't apply the specified transactional behavior.

            In general, don't use transactional proxies for every fine-granular DAO. It's usually sufficient to demarcate transactions at the service facade level.

            If you still go down to the DAO read method level, you could specify the commit rule "+RuntimeException" in the transaction attribute, which will not trigger a rollback on any RuntimeException that comes out of that read method (which would by default lead to a rollback-only state).

            Note that the current behavior is well-defined: it just doesn't match your expectation. If you intend to proceed even with a RuntimeException, simply specify a corresponding commit rule (whether for a specific exception such as ObjectRetrievalFailureException or for RuntimeException in general).

            Juergen

            Comment


            • #7
              In my personal case, I don't put transactional behavior at DAO level.

              I have an interactive service that rollback on exception when I try to create a domain object. I also have a batch service. This second service call the first one for each object, catch the exception that it may throw and manage this exception. In this case, as my batch service know what to do with individual create exception, at this end of the transaction, I would like to commit my work.
              The actual behavior seems ok to me. I just want to know if it is possible to add a setRollbackOnly(false) that my second service can invoke to override the default behavior.

              Does this seem valid to you ?

              Thanx for the reply

              Seb

              Comment


              • #8
                Make sense. Thanks Juergen!

                Comment


                • #9
                  Oops, give a second thought, it still looks not that perfect. Think about this:

                  A project manager initiates a project, and he assigns the tasks to several team leaders. Then each team leader re-assigns the tasks to his developers... but suddenly a developer encounters a problem and he himself can not go on. He writes a report to his boss (throws an exception) and, by default, cancels the whole project!

                  Although I can declare +Exception to let the transaction continue, I wonder if it is better to by default let the transaction originator to make the decision.

                  Comment


                  • #10
                    I would still argue that you shouldn't put a transactional proxy in front of your fine-grained bean if you don't want it to cause rollback-only status. Why not use a transactional proxy in front of your facade bean only? In that case, that facade would be in full control over the rollback decision.

                    BTW, a DataAccessException thrown by a fine-grained DAO might actually corrupt the persistence context that's associated with the current transaction (for example, the Hibernate Session). It's not generally possible to simply ignore this and continue with the rest of the transaction.

                    Another option would be to keep the fine-granular transactional proxies but use PROPAGATION_NESTED there. That would cause a rollback to the implicit savepoint at the beginning of that operation. However, that generally only works with DataSourceTransactionManager on JDBC 3.0.

                    Regarding setRollbackOnly(false): Such a call would be inconsistent with typical transaction patterns, and couldn't be guaranteed to work in general. A rollback-only marker might also be set by the transaction coordinator itself, for example caused by a transaction timeout.

                    Juergen

                    Comment


                    • #11
                      Thanx for your explanation Juergen.

                      It seems clear to me that this behaviour is driven by transaction best practices. As I am not an expert in that domain, I will follow your recommandation.
                      The only case where the setRollbackOnly(false) seems useful for me is the one mentionned in my previous post. Use of creation method inside a batch. I think I can invoke the DAO directly from the batch instead of invoking a fine grained service. The only drawback is that I may have to duplicate code between my two services.

                      Thanx again for the clarification

                      Comment


                      • #12
                        A facade bean will be usually enough given the context that it is the crucial operation of a transaction. If it fails, everything fails. But when the facade is just a screw in a machine and not supposed to stop the whole thing running, rollback is not suitable. However, that seems to be the way defined in the EJB 2.1 spec, and the current Spring 1.2.2 is actually complying with that behaviour.

                        So I am wondering how you are going to change it in 1.3 RC1 (SPR-1029)? Does the EJB spec have any changes at this point?

                        No matter what, I really appreciate your patience and professional answers!

                        Comment


                        • #13
                          FYI, I've added a "globalRollbackOnParticipationFailure" flag to AbstractPlatformTransactionManager, with default "true". This can be switched to "false" to avoid marking the global transaction rollback-only just because a partipating transaction failed. The rollback decision will be on the transaction originator in this case.

                          However, note that this will only work as long as all participating resources are capable of contiuing towards a transaction commit even after a data access failure: This is generally not the case for a Hibernate Session, for example; neither is it for a sequence of JDBC insert/update/delete operations.

                          The recommended solution for handling failure of a subtransaction is a "nested transaction", where the global transaction can be rolled back to a savepoint taken at the beginning of the subtransaction. PROPAGATION_NESTED provides exactly those semantics; however, it will only work when nested transaction support is available. This is the case with DataSourceTransactionManager, but not with JtaTransactionManager.

                          The "globalRollbackOnParticipationFailure" flag will already be available in Spring 1.2.3, which is finally going to be released tonight.

                          Juergen

                          Comment


                          • #14
                            Thanks a lot, Juergen!

                            It is a pretty nice solution!

                            Comment

                            Working...
                            X