Announcement Announcement Module
No announcement yet.
Spring/Hibernate transaction not working properly Page Title Module
Move Remove Collapse
Conversation Detail Module
  • Filter
  • Time
  • Show
Clear All
new posts

  • Spring/Hibernate transaction not working properly


    I'm getting some strange behaviour in a transactional method with Spring (1.1.5) and Hibernate (2.1). I'm not sure if this is a Spring problem or a Hibernate problem.

    Within the transaction, three operations should be performed (in this order):

    delete old db records
    save new db record
    send an email

    As an email once sent can't be rolled back, this is the last operation. If it fails to send however, the db can be rolled back. (That bit works).

    The problem I'm having is that if the db operations fail, the email is still being sent.

    What seems to me to be happening is that the db operations are not being executed until the whole method has completed. I would expect them to be executed in the order of the code and if they fail, an exception would be thrown and the method would exit. But this is not happening.

    What is more, the transaction is failing when it shouldn't as the delete operation does not seem to be working (it reports that it deletes a row but doesn't actually do so and thus the insert operation gets a 'unique key violation' SQLException.

    Here are the relevant pieces of code and configuration:

    // transaction config
        <bean id="activationCodeManagerTarget" class="bus.ActivationCodeManagerImpl">
            <property name="activationCodeManagerDao">
                <ref bean="activationCodeManagerDao"/>
            <property name="sendMail">
                <ref bean="sendMail"/>
        <bean id="activationCodeManager" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
            <property name="transactionManager">
                <ref bean="transactionManager"/>
            <property name="target">
                <ref bean="activationCodeManagerTarget"/>
            <property name="transactionAttributes">
                    <prop key="saveAndSend">PROPAGATION_REQUIRED,-RemoteException</prop>
    // from service layer &#40;bus.ActivationCodeManagerImpl&#41;
        public void saveAndSend&#40;final ActivationCode ac&#41; throws RemoteException
            sendMail.sendActivationCode&#40;ac&#41;; // proprietary mailer, could throw RemoteException
    // from dao &#40;Hibernate&#41; layer
         * Deletes all expired ActivationCode objects from db
        public void deleteExpired&#40;&#41;
            logger.debug&#40;"Deleting expired Activation Codes"&#41;;
            int numDeleted = super.getHibernateTemplate&#40;&#41;.delete&#40;
                   "bus.ActivationCode as activationCode "
                + "where activationCode.expiryDate < sysdate"&#41;;
            logger.debug&#40;numDeleted + " instances deleted"&#41;;
        public void save&#40;final ActivationCode activationCode&#41;
            logger.debug&#40;"Saving " + activationCode&#41;;
    Appreciate any ideas about
    a/ why the sendMail line is being executed before the save operation
    b/ why the delete operation doesn't actually delete anything (even though it reports that it does).

    Many thanks.

  • #2
    Is it because the transaction does get commited until after the method has finished.

    Sure, hibernate *may* be flushing the session, but the *transaction* won't commit until your method has finished.

    In order to do what you want to do, you will need to wrap each *method* in it's own transaction.


    • #3
      Thanks for the reply Yatesco.

      .I take it you mean that I also need to declare a transaction on each of the dao methods called.

      If I do this, will it still be possible to rollback the dao methods if the sendEmail method fails?

      Also, could you (or anyone else) provide/point to an example of how to do this please. So far I've only ever declared them at the service layer.



      • #4

        Unfortunately, as soon as the transaction has completed, it cannot be rolled back.

        To be honest, I do not know how to solve this. The fundamental problem is that you don't get DB errors until you commit the transaction, but if it commits successfully you cannot roll it back Maybe someone who knows more than me could confirm whether nested transactions would solve this?


        • #5
          I think in this case you have to control the transaction programmatically.
          So you have to perform the commit yourself when appropriate.
          Have a look at the Spring reference manual. There should be a section about programmatic transaction handling.



          • #6
            But what about if transactionA commits, transactionB fails, can you still rollback transactionA?

            I am not sure you can???


            • #7
              You cannot, that's true. But if you control your transaction yourself you can perform all database operations, then send the email and then perform the commit (or rollback if sending the mail failed).



              • #8
                An addition:
                The case that the database commit fails and the email is already out cannot be handled. You would indeed need distributed transactions here (but I'm not aware of transactional email clients).



                • #9
                  Thanks for the feedback, that explains why the send email operation is happening - as the opertations are not executed against the db until commit time, which is not until the whole body of the method has been completed. It is disappointing that it works like this but never mind. You also suggest I use programmatic transactions to workaround this - fair enough, I'll give that a go - it is what I'd have had to have done anyway if I wasn't using Spring.

                  However, I commented out the sendEmail stuff and still there is a problem just with the basic database stuff.

                  My method is now only trying to do this:

                  Delete expired records from the db.
                  Save a new record.

                  However, this gets executed like this:

                  Save new record
                  Delete expired records from the db

                  That creates a problem if the new record violates clashes with any of the old records that should have been deleted first.

                  I'm going to try and test this stuff using Hibernate directly without Spring to see where this problem is coming from.

                  If anyone else has come across anything similar I'd be interested to know.

                  This seems to break one of the basic assumptions of programming in Java, namely that code will be executed in the sequence it is written.



                  • #10
                    Originally posted by derek
                    This seems to break one of the basic assumptions of programming in Java, namely that code will be executed in the sequence it is written.
                    It seems this is common behavior for O/R mappers in their attempt to optimize database operations. Ok, sometimes it's not that optimal :?

                    If I remember correctly, you can specify whether deletions should be performed first (I think in the mapping file).
                    It should be mentioned in the hibernate reference manual.



                    • #11
                      Many thanks Andreas. Spot on.

                      Was definitely a Hibernate thing not a Spring thing, and was as you said, about the order in which the SQL is executed:


                      I didn't find how to change the config so deletions always happen first but I used
                      session.flush() which did the trick in this instance, forcing the delete statement to be executed immediately.


                      • #12
                        Maybe I confused the configuration issue with TopLink and you really cannot do it with Hibernate.
                        Anyway, glad you could solve your problem.



                        • #13
                          Thought it was also worth noting that using Hibernate's session.flush() also solved the other problem - where the email was being sent before the db operations were executed. Thus I didn't need to use programmatic transactions.


                          • #14
                            Try having JMS send the email

                            You could try using JMS message to kick off the email sending since JMS can be included in transaction.


                            • #15
                              Good point, but the *SMTP* server isn't